hiera 0.3.0 → 1.0.0rc4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of hiera might be problematic. Click here for more details.

@@ -1,174 +1,187 @@
1
- class Hiera
2
- module Backend
3
- class << self
4
- # Data lives in /var/lib/hiera by default. If a backend
5
- # supplies a datadir in the config it will be used and
6
- # subject to variable expansion based on scope
7
- def datadir(backend, scope)
8
- backend = backend.to_sym
9
- default = "/var/lib/hiera"
10
-
11
- if Config.include?(backend)
12
- parse_string(Config[backend][:datadir] || default, scope)
13
- else
14
- parse_string(default, scope)
15
- end
16
- end
1
+ require 'hiera/util'
17
2
 
18
- # Finds the path to a datafile based on the Backend#datadir
19
- # and extension
20
- #
21
- # If the file is not found nil is returned
22
- def datafile(backend, scope, source, extension)
23
- file = File.join([datadir(backend, scope), "#{source}.#{extension}"])
3
+ class Hiera
4
+ module Backend
5
+ class << self
6
+ # Data lives in /var/lib/hiera by default. If a backend
7
+ # supplies a datadir in the config it will be used and
8
+ # subject to variable expansion based on scope
9
+ def datadir(backend, scope)
10
+ backend = backend.to_sym
11
+ default = Hiera::Util.var_dir
12
+
13
+ if Config.include?(backend)
14
+ parse_string(Config[backend][:datadir] || default, scope)
15
+ else
16
+ parse_string(default, scope)
17
+ end
18
+ end
24
19
 
25
- unless File.exist?(file)
26
- Hiera.debug("Cannot find datafile #{file}, skipping")
20
+ # Finds the path to a datafile based on the Backend#datadir
21
+ # and extension
22
+ #
23
+ # If the file is not found nil is returned
24
+ def datafile(backend, scope, source, extension)
25
+ file = File.join([datadir(backend, scope), "#{source}.#{extension}"])
27
26
 
28
- return nil
29
- end
27
+ unless File.exist?(file)
28
+ Hiera.debug("Cannot find datafile #{file}, skipping")
30
29
 
31
- return file
32
- end
30
+ return nil
31
+ end
33
32
 
34
- # Returns an appropriate empty answer dependant on resolution type
35
- def empty_answer(resolution_type)
36
- case resolution_type
37
- when :array
38
- return []
39
- when :hash
40
- return {}
41
- else
42
- return nil
43
- end
44
- end
33
+ return file
34
+ end
35
+
36
+ # Constructs a list of data sources to search
37
+ #
38
+ # If you give it a specific hierarchy it will just use that
39
+ # else it will use the global configured one, failing that
40
+ # it will just look in the 'common' data source.
41
+ #
42
+ # An override can be supplied that will be pre-pended to the
43
+ # hierarchy.
44
+ #
45
+ # The source names will be subject to variable expansion based
46
+ # on scope
47
+ def datasources(scope, override=nil, hierarchy=nil)
48
+ if hierarchy
49
+ hierarchy = [hierarchy]
50
+ elsif Config.include?(:hierarchy)
51
+ hierarchy = [Config[:hierarchy]].flatten
52
+ else
53
+ hierarchy = ["common"]
54
+ end
45
55
 
46
- # Constructs a list of data sources to search
47
- #
48
- # If you give it a specific hierarchy it will just use that
49
- # else it will use the global configured one, failing that
50
- # it will just look in the 'common' data source.
51
- #
52
- # An override can be supplied that will be pre-pended to the
53
- # hierarchy.
54
- #
55
- # The source names will be subject to variable expansion based
56
- # on scope
57
- def datasources(scope, override=nil, hierarchy=nil)
58
- if hierarchy
59
- hierarchy = [hierarchy]
60
- elsif Config.include?(:hierarchy)
61
- hierarchy = [Config[:hierarchy]].flatten
62
- else
63
- hierarchy = ["common"]
64
- end
65
-
66
- hierarchy.insert(0, override) if override
67
-
68
- hierarchy.flatten.map do |source|
69
- source = parse_string(source, scope)
70
- yield(source) unless source == "" or source =~ /(^\/|\/\/|\/$)/
71
- end
72
- end
56
+ hierarchy.insert(0, override) if override
73
57
 
74
- # Parse a string like '%{foo}' against a supplied
75
- # scope and additional scope. If either scope or
76
- # extra_scope includes the varaible 'foo' it will
77
- # be replaced else an empty string will be placed.
78
- #
79
- # If both scope and extra_data has "foo" scope
80
- # will win. See hiera-puppet for an example of
81
- # this to make hiera aware of additional non scope
82
- # variables
83
- def parse_string(data, scope, extra_data={})
84
- return nil unless data
85
-
86
- tdata = data.clone
87
-
88
- if tdata.is_a?(String)
89
- while tdata =~ /%\{(.+?)\}/
90
- var = $1
91
- val = scope[var] || extra_data[var] || ""
92
-
93
- # Puppet can return this for unknown scope vars
94
- val = "" if val == :undefined
95
-
96
- tdata.gsub!(/%\{#{var}\}/, val)
97
- end
98
- end
99
-
100
- return tdata
101
- end
58
+ hierarchy.flatten.map do |source|
59
+ source = parse_string(source, scope)
60
+ yield(source) unless source == "" or source =~ /(^\/|\/\/|\/$)/
61
+ end
62
+ end
63
+
64
+ # Parse a string like '%{foo}' against a supplied
65
+ # scope and additional scope. If either scope or
66
+ # extra_scope includes the varaible 'foo' it will
67
+ # be replaced else an empty string will be placed.
68
+ #
69
+ # If both scope and extra_data has "foo" scope
70
+ # will win. See hiera-puppet for an example of
71
+ # this to make hiera aware of additional non scope
72
+ # variables
73
+ def parse_string(data, scope, extra_data={})
74
+ return nil unless data
75
+
76
+ tdata = data.clone
77
+
78
+ if tdata.is_a?(String)
79
+ while tdata =~ /%\{(.+?)\}/
80
+ begin
81
+ var = $1
82
+
83
+ val = ""
84
+
85
+ # Puppet can return :undefined for unknown scope vars,
86
+ # If it does then we still need to evaluate extra_data
87
+ # before returning an empty string.
88
+ if scope[var] && scope[var] != :undefined
89
+ val = scope[var]
90
+ elsif extra_data[var]
91
+ val = extra_data[var]
92
+ end
93
+ end until val != "" || var !~ /::(.+)/
94
+
95
+ tdata.gsub!(/%\{(::)?#{var}\}/, val)
96
+ end
97
+ end
102
98
 
103
- # Parses a answer received from data files
104
- #
105
- # Ultimately it just pass the data through parse_string but
106
- # it makes some effort to handle arrays of strings as well
107
- def parse_answer(data, scope, extra_data={})
108
- if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
109
- return data
110
- elsif data.is_a?(String)
111
- return parse_string(data, scope, extra_data)
112
- elsif data.is_a?(Hash)
113
- answer = {}
114
- data.each_pair do |key, val|
115
- answer[key] = parse_answer(val, scope, extra_data)
116
- end
117
-
118
- return answer
119
- elsif data.is_a?(Array)
120
- answer = []
121
- data.each do |item|
122
- answer << parse_answer(item, scope, extra_data)
123
- end
124
-
125
- return answer
126
- end
99
+ return tdata
100
+ end
101
+
102
+ # Parses a answer received from data files
103
+ #
104
+ # Ultimately it just pass the data through parse_string but
105
+ # it makes some effort to handle arrays of strings as well
106
+ def parse_answer(data, scope, extra_data={})
107
+ if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
108
+ return data
109
+ elsif data.is_a?(String)
110
+ return parse_string(data, scope, extra_data)
111
+ elsif data.is_a?(Hash)
112
+ answer = {}
113
+ data.each_pair do |key, val|
114
+ answer[key] = parse_answer(val, scope, extra_data)
115
+ end
116
+
117
+ return answer
118
+ elsif data.is_a?(Array)
119
+ answer = []
120
+ data.each do |item|
121
+ answer << parse_answer(item, scope, extra_data)
122
+ end
123
+
124
+ return answer
125
+ end
126
+ end
127
+
128
+ def resolve_answer(answer, resolution_type)
129
+ case resolution_type
130
+ when :array
131
+ [answer].flatten.uniq.compact
132
+ when :hash
133
+ answer # Hash structure should be preserved
134
+ else
135
+ answer
136
+ end
137
+ end
138
+
139
+ # Calls out to all configured backends in the order they
140
+ # were specified. The first one to answer will win.
141
+ #
142
+ # This lets you declare multiple backends, a possible
143
+ # use case might be in Puppet where a Puppet module declares
144
+ # default data using in-module data while users can override
145
+ # using JSON/YAML etc. By layering the backends and putting
146
+ # the Puppet one last you can override module author data
147
+ # easily.
148
+ #
149
+ # Backend instances are cached so if you need to connect to any
150
+ # databases then do so in your constructor, future calls to your
151
+ # backend will not create new instances
152
+ def lookup(key, default, scope, order_override, resolution_type)
153
+ @backends ||= {}
154
+ answer = nil
155
+
156
+ Config[:backends].each do |backend|
157
+ if constants.include?("#{backend.capitalize}_backend") || constants.include?("#{backend.capitalize}_backend".to_sym)
158
+ @backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
159
+ new_answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
160
+
161
+ if not new_answer.nil?
162
+ case resolution_type
163
+ when :array
164
+ raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
165
+ answer ||= []
166
+ answer << new_answer
167
+ when :hash
168
+ raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
169
+ answer ||= {}
170
+ answer = new_answer.merge answer
171
+ else
172
+ answer = new_answer
173
+ break
174
+ end
127
175
  end
176
+ end
177
+ end
128
178
 
129
- def resolve_answer(answer, resolution_type)
130
- case resolution_type
131
- when :array
132
- [answer].flatten.uniq.compact
133
- when :hash
134
- answer # Hash structure should be preserved
135
- else
136
- answer
137
- end
138
- end
179
+ answer = resolve_answer(answer, resolution_type) unless answer.nil?
180
+ answer = parse_string(default, scope) if answer.nil? and default.is_a?(String)
139
181
 
140
- # Calls out to all configured backends in the order they
141
- # were specified. The first one to answer will win.
142
- #
143
- # This lets you declare multiple backends, a possible
144
- # use case might be in Puppet where a Puppet module declares
145
- # default data using in-module data while users can override
146
- # using JSON/YAML etc. By layering the backends and putting
147
- # the Puppet one last you can override module author data
148
- # easily.
149
- #
150
- # Backend instances are cached so if you need to connect to any
151
- # databases then do so in your constructor, future calls to your
152
- # backend will not create new instances
153
- def lookup(key, default, scope, order_override, resolution_type)
154
- @backends ||= {}
155
- answer = nil
156
-
157
- Config[:backends].each do |backend|
158
- if constants.include?("#{backend.capitalize}_backend")
159
- @backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
160
- answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
161
-
162
- break if answer
163
- end
164
- end
165
-
166
- answer = resolve_answer(answer, resolution_type)
167
- answer = parse_string(default, scope) if answer.nil?
168
-
169
- return default if answer == empty_answer(resolution_type)
170
- return answer
171
- end
172
- end
182
+ return default if answer.nil?
183
+ return answer
184
+ end
173
185
  end
186
+ end
174
187
  end
@@ -1,49 +1,71 @@
1
1
  class Hiera
2
- module Backend
3
- class Yaml_backend
4
- def initialize
5
- require 'yaml'
6
-
7
- Hiera.debug("Hiera YAML backend starting")
8
- end
9
-
10
- def lookup(key, scope, order_override, resolution_type)
11
- answer = Backend.empty_answer(resolution_type)
12
-
13
- Hiera.debug("Looking up #{key} in YAML backend")
14
-
15
- Backend.datasources(scope, order_override) do |source|
16
- Hiera.debug("Looking for data source #{source}")
17
-
18
- yamlfile = Backend.datafile(:yaml, scope, source, "yaml") || next
19
-
20
- data = YAML.load_file(yamlfile)
21
-
22
- next if ! data
23
- next if data.empty?
24
- next unless data.include?(key)
25
-
26
- # for array resolution we just append to the array whatever
27
- # we find, we then goes onto the next file and keep adding to
28
- # the array
29
- #
30
- # for priority searches we break after the first found data item
31
- new_answer = Backend.parse_answer(data[key], scope)
32
- case resolution_type
33
- when :array
34
- raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
35
- answer << new_answer
36
- when :hash
37
- raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
38
- answer = new_answer.merge answer
39
- else
40
- answer = new_answer
41
- break
42
- end
43
- end
44
-
45
- return answer
46
- end
2
+ module Backend
3
+ class Yaml_backend
4
+ def initialize
5
+ require 'yaml'
6
+ Hiera.debug("Hiera YAML backend starting")
7
+ @data = Hash.new
8
+ @cache = Hash.new
9
+ end
10
+
11
+ def lookup(key, scope, order_override, resolution_type)
12
+ answer = nil
13
+
14
+ Hiera.debug("Looking up #{key} in YAML backend")
15
+
16
+ Backend.datasources(scope, order_override) do |source|
17
+ Hiera.debug("Looking for data source #{source}")
18
+ yamlfile = Backend.datafile(:yaml, scope, source, "yaml") || next
19
+
20
+ # If you call stale? BEFORE you do encounter the YAML.load_file line
21
+ # it will populate the @cache variable and return true. The second
22
+ # time you call it, it will return false because @cache has been
23
+ # populated. Because of this there are two conditions to check:
24
+ # is @data[yamlfile] populated AND is the cache stale.
25
+ if @data[yamlfile]
26
+ @data[yamlfile] = YAML.load_file(yamlfile) if stale?(yamlfile)
27
+ else
28
+ @data[yamlfile] = YAML.load_file(yamlfile)
29
+ end
30
+
31
+ next if ! @data[yamlfile]
32
+ next if @data[yamlfile].empty?
33
+ next unless @data[yamlfile].include?(key)
34
+ # for array resolution we just append to the array whatever
35
+ # we find, we then goes onto the next file and keep adding to
36
+ # the array
37
+ #
38
+ # for priority searches we break after the first found data item
39
+ new_answer = Backend.parse_answer(@data[yamlfile][key], scope)
40
+ case resolution_type
41
+ when :array
42
+ raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
43
+ answer ||= []
44
+ answer << new_answer
45
+ when :hash
46
+ raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
47
+ answer ||= {}
48
+ answer = new_answer.merge answer
49
+ else
50
+ answer = new_answer
51
+ break
52
+ end
47
53
  end
54
+
55
+ return answer
56
+ end
57
+
58
+ def stale?(yamlfile)
59
+ # NOTE: The mtime change in a file MUST be > 1 second before being
60
+ # recognized as stale. File mtime changes within 1 second will
61
+ # not be recognized.
62
+ stat = File.stat(yamlfile)
63
+ current = { 'inode' => stat.ino, 'mtime' => stat.mtime, 'size' => stat.size }
64
+ return false if @cache[yamlfile] == current
65
+
66
+ @cache[yamlfile] = current
67
+ return true
68
+ end
48
69
  end
70
+ end
49
71
  end