hiera 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dd4a69f9d2131165eba3e8767b8731dcc11f3a6d
4
+ data.tar.gz: fd038d50edc2f8c3dd6d21d21d30cc9463b11c4c
5
+ SHA512:
6
+ metadata.gz: 6d669f8a2ea3cea56f2c0d66959e2aa2df7a84f74940ae51e8c192929f45e102d99b85c7530084c87294594557229060de56b093e87c5a96076c7849343efb15
7
+ data.tar.gz: 10d478df00b859bddf583631ae9fe9f2f1028b3394fda6dfd38d3e0a2a57ce32a1b41a681b814552594421d9b9b373b94c6300eccdd2291827abacf8f9c3a566
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  Puppet - Automating Configuration Management.
2
2
 
3
- Copyright (C) 2012 Puppet Labs Inc
3
+ Copyright (C) 2012-2014 Puppet Labs Inc
4
4
 
5
5
  Puppet Labs can be contacted at: info@puppetlabs.com
6
6
 
data/README.md CHANGED
@@ -84,6 +84,23 @@ Hiera can search through all the tiers in a hierarchy and merge the result into
84
84
  array. This is used in the hiera-puppet project to replace External Node Classifiers by
85
85
  creating a Hiera compatible include function.
86
86
 
87
+ ### Qualified Key Lookup
88
+ You can use a qualified key to lookup a value that is contained inside a hash or array:
89
+
90
+ <pre>
91
+ $ hiera user
92
+ {"name"=>"kim", "home"=>"/home/kim"}
93
+ $ hiera user.name
94
+ kim
95
+ </pre>
96
+
97
+ <pre>
98
+ $ hiera ssh_users
99
+ ["root", "jeff", "gary", "hunter"]
100
+ $ hiera ssh_users.0
101
+ root
102
+ </pre>
103
+
87
104
  ## Future Enhancements
88
105
 
89
106
  * More backends should be created
@@ -121,29 +138,29 @@ A sample configuration file can be seen here:
121
138
  - common
122
139
 
123
140
  :yaml:
124
- :datadir: /etc/puppet/hieradata
141
+ :datadir: /etc/puppetlabs/code/hieradata
125
142
 
126
143
  :puppet:
127
144
  :datasource: data
128
145
  </pre>
129
146
 
130
- This configuration will require YAML files in _/etc/puppet/hieradata_ these need to contain
147
+ This configuration will require YAML files in _/etc/puppetlabs/code/hieradata_ these need to contain
131
148
  Hash data, sample files matching the hierarchy described in the _Why?_ section are below:
132
149
 
133
- _/etc/puppet/hieradata/sites/dc1.yaml_:
150
+ _/etc/puppetlabs/code/hieradata/sites/dc1.yaml_:
134
151
  <pre>
135
152
  ---
136
153
  ntpserver: ntp1.dc1.example.com
137
154
  sysadmin: dc1noc@example.com
138
155
  </pre>
139
156
 
140
- _/etc/puppet/hieradata/sites/dc2.yaml_:
157
+ _/etc/puppetlabs/code/hieradata/sites/dc2.yaml_:
141
158
  <pre>
142
159
  ---
143
160
  ntpserver: ntp1.dc2.example.com
144
161
  </pre>
145
162
 
146
- _/etc/puppet/hieradata/common.yaml_:
163
+ _/etc/puppetlabs/code/hieradata/common.yaml_:
147
164
  <pre>
148
165
  ---
149
166
  sysadmin: "sysadmin@%{domain}"
@@ -161,13 +178,13 @@ store as found on your Puppet Masters.
161
178
  If no data is found and the facts had a location=dc1 fact the default would be _sites/dc1_
162
179
 
163
180
  <pre>
164
- $ hiera acme_version 'sites/%{location}' --yaml /var/lib/puppet/yaml/facts/example.com.yaml
181
+ $ hiera acme_version 'sites/%{location}' --yaml /opt/puppetlabs/puppet/cache/yaml/facts/example.com.yaml
165
182
  </pre>
166
183
 
167
184
  You can also supply extra facts on the CLI, assuming Puppet facts did not have a location fact:
168
185
 
169
186
  <pre>
170
- $ hiera acme_version 'sites/%{location}' location=dc1 --yaml /var/lib/puppet/yaml/facts/example.com.yaml
187
+ $ hiera acme_version 'sites/%{location}' location=dc1 --yaml /opt/puppetlabs/puppet/cache/yaml/facts/example.com.yaml
171
188
  </pre>
172
189
 
173
190
  Or if you use MCollective you can fetch the scope from a remote node's facts:
@@ -193,10 +210,10 @@ require 'hiera'
193
210
  require 'puppet'
194
211
 
195
212
  # load the facts for example.com
196
- scope = YAML.load_file("/var/lib/puppet/yaml/facts/example.com.yaml").values
213
+ scope = YAML.load_file("/opt/puppetlabs/puppet/cache/yaml/facts/example.com.yaml").values
197
214
 
198
215
  # create a new instance based on config file
199
- hiera = Hiera.new(:config => "/etc/puppet/hiera.yaml")
216
+ hiera = Hiera.new(:config => "/etc/puppetlabs/code/hiera.yaml")
200
217
 
201
218
  # resolve the 'acme_version' variable based on scope
202
219
  #
@@ -235,5 +252,25 @@ See LICENSE file.
235
252
 
236
253
  ## Support
237
254
 
238
- Please log tickets and issues at our [Projects site](http://projects.puppetlabs.com)
255
+ Please log tickets and issues at our [JIRA tracker](http://tickets.puppetlabs.com). A [mailing
256
+ list](https://groups.google.com/forum/?fromgroups#!forum/puppet-users) is
257
+ available for asking questions and getting help from others. In addition there
258
+ is an active #puppet channel on Freenode.
259
+
260
+ We use semantic version numbers for our releases, and recommend that users stay
261
+ as up-to-date as possible by upgrading to patch releases and minor releases as
262
+ they become available.
263
+
264
+ Bugfixes and ongoing development will occur in minor releases for the current
265
+ major version. Security fixes will be backported to a previous major version on
266
+ a best-effort basis, until the previous major version is no longer maintained.
267
+
268
+
269
+ For example: If a security vulnerability is discovered in Hiera 1.3.0, we
270
+ would fix it in the 1 series, most likely as 1.3.1. Maintainers would then make
271
+ a best effort to backport that fix onto the latest Hiera release they carry.
272
+
273
+ Long-term support, including security patches and bug fixes, is available for
274
+ commercial customers. Please see the following page for more details:
239
275
 
276
+ [Puppet Enterprise Support Lifecycle](http://puppetlabs.com/misc/puppet-enterprise-lifecycle)
data/bin/hiera CHANGED
@@ -32,9 +32,12 @@ options = {
32
32
  :scope => {},
33
33
  :key => nil,
34
34
  :verbose => false,
35
- :resolution_type => :priority
35
+ :resolution_type => :priority,
36
+ :format => :ruby
36
37
  }
37
38
 
39
+ initial_scopes = Array.new
40
+
38
41
  # Loads the scope from YAML or JSON files
39
42
  def load_scope(source, type=:yaml)
40
43
  case type
@@ -113,6 +116,26 @@ def load_scope(source, type=:yaml)
113
116
  scope
114
117
  end
115
118
 
119
+ def output_answer(ans, format)
120
+ case format
121
+ when :json
122
+ require 'json'
123
+ puts JSON.dump(ans)
124
+ when :ruby
125
+ if ans.is_a?(String)
126
+ puts ans
127
+ else
128
+ pp ans
129
+ end
130
+ when :yaml
131
+ require 'yaml'
132
+ puts ans.to_yaml
133
+ else
134
+ STDERR.puts "Unknown output format: #{v}"
135
+ exit 1
136
+ end
137
+ end
138
+
116
139
  OptionParser.new do |opts|
117
140
  opts.banner = "Usage: hiera [options] key [default value] [variable='text'...]\n\nThe default value will be used if no value is found for the key. Scope variables\nwill be interpolated into %{variable} placeholders in the hierarchy and in\nreturned values.\n\n"
118
141
 
@@ -143,41 +166,43 @@ OptionParser.new do |opts|
143
166
  end
144
167
 
145
168
  opts.on("--json SCOPE", "-j", "JSON format file to load scope from") do |v|
146
- begin
147
- options[:scope] = load_scope(v, :json)
148
- rescue Exception => e
149
- STDERR.puts "Could not load JSON scope: #{e.class}: #{e}"
150
- exit 1
151
- end
169
+ initial_scopes << { :type => :json, :value => v, :name => "JSON" }
152
170
  end
153
171
 
154
172
  opts.on("--yaml SCOPE", "-y", "YAML format file to load scope from") do |v|
155
- begin
156
- options[:scope] = load_scope(v)
157
- rescue Exception => e
158
- STDERR.puts "Could not load YAML scope: #{e.class}: #{e}"
159
- exit 1
160
- end
173
+ initial_scopes << { :type => :yaml, :value => v, :name => "YAML" }
161
174
  end
162
175
 
163
176
  opts.on("--mcollective IDENTITY", "-m", "Use facts from a node (via mcollective) as scope") do |v|
164
- begin
165
- options[:scope] = load_scope(v, :mcollective)
166
- rescue Exception => e
167
- STDERR.puts "Could not load MCollective scope: #{e.class}: #{e}"
177
+ initial_scopes << { :type => :mcollective, :value => v, :name => "Mcollective" }
178
+ end
179
+
180
+ opts.on("--inventory_service IDENTITY", "-i", "Use facts from a node (via Puppet's inventory service) as scope") do |v|
181
+ initial_scopes << { :type => :inventory_service, :value => v, :name => "Puppet inventory service" }
182
+ end
183
+
184
+ opts.on("--format TYPE", "-f", "Output the result in a specific format (ruby, yaml or json); default is 'ruby'") do |v|
185
+ options[:format] = case v
186
+ when 'json', 'ruby', 'yaml'
187
+ v.to_sym
188
+ else
189
+ STDERR.puts "Unknown output format: #{v}"
168
190
  exit 1
169
191
  end
170
192
  end
193
+ end.parse!
171
194
 
172
- opts.on("--inventory_service IDENTITY", "-i", "Use facts from a node (via Puppet's inventory service) as scope") do |v|
195
+ unless initial_scopes.empty?
196
+ initial_scopes.each { |this_scope|
197
+ # Load initial scope
173
198
  begin
174
- options[:scope] = load_scope(v, :inventory_service)
199
+ options[:scope] = load_scope(this_scope[:value], this_scope[:type])
175
200
  rescue Exception => e
176
- STDERR.puts "Could not load Puppet inventory service scope: #{e.class}: #{e}"
201
+ STDERR.puts "Could not load #{this_scope[:name]} scope: #{e.class}: #{e}"
177
202
  exit 1
178
203
  end
179
- end
180
- end.parse!
204
+ }
205
+ end
181
206
 
182
207
  # arguments can be:
183
208
  #
@@ -220,8 +245,4 @@ end
220
245
 
221
246
  ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, options[:resolution_type])
222
247
 
223
- if ans.is_a?(String)
224
- puts ans
225
- else
226
- pp ans
227
- end
248
+ output_answer(ans, options[:format])
@@ -9,8 +9,9 @@ class Hiera
9
9
  @cache = cache || Filecache.new
10
10
  end
11
11
 
12
- def lookup(key, scope, order_override, resolution_type)
12
+ def lookup(key, scope, order_override, resolution_type, context)
13
13
  answer = nil
14
+ found = false
14
15
 
15
16
  Hiera.debug("Looking up #{key} in JSON backend")
16
17
 
@@ -27,28 +28,29 @@ class Hiera
27
28
 
28
29
  next if data.empty?
29
30
  next unless data.include?(key)
31
+ found = true
30
32
 
31
33
  # for array resolution we just append to the array whatever
32
34
  # we find, we then goes onto the next file and keep adding to
33
35
  # the array
34
36
  #
35
37
  # for priority searches we break after the first found data item
36
- new_answer = Backend.parse_answer(data[key], scope)
37
- case resolution_type
38
+ new_answer = Backend.parse_answer(data[key], scope, {}, context)
39
+ case resolution_type.is_a?(Hash) ? :hash : resolution_type
38
40
  when :array
39
- raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
41
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
40
42
  answer ||= []
41
43
  answer << new_answer
42
44
  when :hash
43
- raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
45
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
44
46
  answer ||= {}
45
- answer = Backend.merge_answer(new_answer,answer)
47
+ answer = Backend.merge_answer(new_answer, answer, resolution_type)
46
48
  else
47
49
  answer = new_answer
48
50
  break
49
51
  end
50
52
  end
51
-
53
+ throw :no_such_key unless found
52
54
  return answer
53
55
  end
54
56
  end
@@ -8,23 +8,20 @@ class Hiera
8
8
  @cache = cache || Filecache.new
9
9
  end
10
10
 
11
- def lookup(key, scope, order_override, resolution_type)
11
+ def lookup(key, scope, order_override, resolution_type, context)
12
12
  answer = nil
13
+ found = false
13
14
 
14
15
  Hiera.debug("Looking up #{key} in YAML backend")
15
16
 
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
- next unless File.exist?(yamlfile)
21
-
17
+ Backend.datasourcefiles(:yaml, scope, "yaml", order_override) do |source, yamlfile|
22
18
  data = @cache.read_file(yamlfile, Hash) do |data|
23
- YAML.load(data)
19
+ YAML.load(data) || {}
24
20
  end
25
21
 
26
22
  next if data.empty?
27
23
  next unless data.include?(key)
24
+ found = true
28
25
 
29
26
  # Extra logging that we found the key. This can be outputted
30
27
  # multiple times if the resolution type is array or hash but that
@@ -37,24 +34,30 @@ class Hiera
37
34
  # the array
38
35
  #
39
36
  # for priority searches we break after the first found data item
40
- new_answer = Backend.parse_answer(data[key], scope)
41
- case resolution_type
37
+ new_answer = Backend.parse_answer(data[key], scope, {}, context)
38
+ case resolution_type.is_a?(Hash) ? :hash : resolution_type
42
39
  when :array
43
- raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
40
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
44
41
  answer ||= []
45
42
  answer << new_answer
46
43
  when :hash
47
- raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
44
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
48
45
  answer ||= {}
49
- answer = Backend.merge_answer(new_answer,answer)
46
+ answer = Backend.merge_answer(new_answer, answer, resolution_type)
50
47
  else
51
48
  answer = new_answer
52
49
  break
53
50
  end
54
51
  end
55
-
52
+ throw :no_such_key unless found
56
53
  return answer
57
54
  end
55
+
56
+ private
57
+
58
+ def file_exists?(path)
59
+ File.exist? path
60
+ end
58
61
  end
59
62
  end
60
63
  end
data/lib/hiera/backend.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'hiera/util'
2
- require 'hiera/recursive_guard'
3
2
  require 'hiera/interpolate'
4
3
 
5
4
  begin
@@ -9,6 +8,22 @@ end
9
8
 
10
9
  class Hiera
11
10
  module Backend
11
+ class Backend1xWrapper
12
+ def initialize(wrapped)
13
+ @wrapped = wrapped
14
+ end
15
+
16
+ def lookup(key, scope, order_override, resolution_type, context)
17
+ Hiera.debug("Using Hiera 1.x backend API to access instance of class #{@wrapped.class.name}. Lookup recursion will not be detected")
18
+ value = @wrapped.lookup(key, scope, order_override, resolution_type.is_a?(Hash) ? :hash : resolution_type)
19
+
20
+ # The most likely cause when an old backend returns nil is that the key was not found. In any case, it is
21
+ # impossible to know the difference between that and a found nil. The throw here preserves the old behavior.
22
+ throw (:no_such_key) if value.nil?
23
+ value
24
+ end
25
+ end
26
+
12
27
  class << self
13
28
  # Data lives in /var/lib/hiera by default. If a backend
14
29
  # supplies a datadir in the config it will be used and
@@ -35,15 +50,19 @@ class Hiera
35
50
  #
36
51
  # If the file is not found nil is returned
37
52
  def datafile(backend, scope, source, extension)
38
- file = File.join([datadir(backend, scope), "#{source}.#{extension}"])
53
+ datafile_in(datadir(backend, scope), source, extension)
54
+ end
39
55
 
40
- unless File.exist?(file)
41
- Hiera.debug("Cannot find datafile #{file}, skipping")
56
+ # @api private
57
+ def datafile_in(datadir, source, extension)
58
+ file = File.join(datadir, "#{source}.#{extension}")
42
59
 
43
- return nil
60
+ if File.exist?(file)
61
+ file
62
+ else
63
+ Hiera.debug("Cannot find datafile #{file}, skipping")
64
+ nil
44
65
  end
45
-
46
- return file
47
66
  end
48
67
 
49
68
  # Constructs a list of data sources to search
@@ -69,11 +88,40 @@ class Hiera
69
88
  hierarchy.insert(0, override) if override
70
89
 
71
90
  hierarchy.flatten.map do |source|
72
- source = parse_string(source, scope)
91
+ source = parse_string(source, scope, {}, :order_override => override)
73
92
  yield(source) unless source == "" or source =~ /(^\/|\/\/|\/$)/
74
93
  end
75
94
  end
76
95
 
96
+ # Constructs a list of data files to search
97
+ #
98
+ # If you give it a specific hierarchy it will just use that
99
+ # else it will use the global configured one, failing that
100
+ # it will just look in the 'common' data source.
101
+ #
102
+ # An override can be supplied that will be pre-pended to the
103
+ # hierarchy.
104
+ #
105
+ # The source names will be subject to variable expansion based
106
+ # on scope
107
+ #
108
+ # Only files that exist will be returned. If the file is missing, then
109
+ # the block will not receive the file.
110
+ #
111
+ # @yield [String, String] the source string and the name of the resulting file
112
+ # @api public
113
+ def datasourcefiles(backend, scope, extension, override=nil, hierarchy=nil)
114
+ datadir = Backend.datadir(backend, scope)
115
+ Backend.datasources(scope, override, hierarchy) do |source|
116
+ Hiera.debug("Looking for data source #{source}")
117
+ file = datafile_in(datadir, source, extension)
118
+
119
+ if file
120
+ yield source, file
121
+ end
122
+ end
123
+ end
124
+
77
125
  # Parse a string like <code>'%{foo}'</code> against a supplied
78
126
  # scope and additional scope. If either scope or
79
127
  # extra_scope includes the variable 'foo', then it will
@@ -86,34 +134,35 @@ class Hiera
86
134
  # This will not be modified, instead a new string will be returned.
87
135
  # @param scope [#[]] The primary source of data for substitutions.
88
136
  # @param extra_data [#[]] The secondary source of data for substitutions.
137
+ # @param context [#[]] Context can include :recurse_guard and :order_override.
89
138
  # @return [String] A copy of the data with all instances of <code>%{...}</code> replaced.
90
139
  #
91
140
  # @api public
92
- def parse_string(data, scope, extra_data={})
93
- Hiera::Interpolate.interpolate(data, Hiera::RecursiveGuard.new, scope, extra_data)
141
+ def parse_string(data, scope, extra_data={}, context={:recurse_guard => nil, :order_override => nil})
142
+ Hiera::Interpolate.interpolate(data, scope, extra_data, context)
94
143
  end
95
144
 
96
145
  # Parses a answer received from data files
97
146
  #
98
147
  # Ultimately it just pass the data through parse_string but
99
148
  # it makes some effort to handle arrays of strings as well
100
- def parse_answer(data, scope, extra_data={})
149
+ def parse_answer(data, scope, extra_data={}, context={:recurse_guard => nil, :order_override => nil})
101
150
  if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
102
151
  return data
103
152
  elsif data.is_a?(String)
104
- return parse_string(data, scope, extra_data)
153
+ return parse_string(data, scope, extra_data, context)
105
154
  elsif data.is_a?(Hash)
106
155
  answer = {}
107
156
  data.each_pair do |key, val|
108
- interpolated_key = parse_string(key, scope, extra_data)
109
- answer[interpolated_key] = parse_answer(val, scope, extra_data)
157
+ interpolated_key = parse_string(key, scope, extra_data, context)
158
+ answer[interpolated_key] = parse_answer(val, scope, extra_data, context)
110
159
  end
111
160
 
112
161
  return answer
113
162
  elsif data.is_a?(Array)
114
163
  answer = []
115
164
  data.each do |item|
116
- answer << parse_answer(item, scope, extra_data)
165
+ answer << parse_answer(item, scope, extra_data, context)
117
166
  end
118
167
 
119
168
  return answer
@@ -131,21 +180,37 @@ class Hiera
131
180
  end
132
181
  end
133
182
 
134
- # Merges two hashes answers with the configured merge behavior.
135
- # :merge_behavior: {:native|:deep|:deeper}
183
+ # Merges two hashes answers with the given or configured merge behavior. Behavior can be given
184
+ # by passing _resolution_type_ as a Hash
185
+ #
186
+ # :merge_behavior: {:native|:deep|:deeper}
136
187
  #
137
- # Deep merge options use the Hash utility function provided by [deep_merge](https://github.com/peritor/deep_merge)
188
+ # Deep merge options use the Hash utility function provided by [deep_merge](https://github.com/danielsdeleo/deep_merge)
138
189
  #
139
190
  # :native => Native Hash.merge
140
191
  # :deep => Use Hash.deep_merge
141
192
  # :deeper => Use Hash.deep_merge!
142
193
  #
143
- def merge_answer(left,right)
144
- case Config[:merge_behavior]
194
+ # @param left [Hash] left side of the merge
195
+ # @param right [Hash] right side of the merge
196
+ # @param resolution_type [String,Hash] The merge type, or if hash, the merge behavior and options
197
+ # @return [Hash] The merged result
198
+ # @see Hiera#lookup
199
+ #
200
+ def merge_answer(left,right,resolution_type=nil)
201
+ behavior, options =
202
+ if resolution_type.is_a?(Hash)
203
+ merge = resolution_type.clone
204
+ [merge.delete(:behavior), merge]
205
+ else
206
+ [Config[:merge_behavior], Config[:deep_merge_options] || {}]
207
+ end
208
+
209
+ case behavior
145
210
  when :deeper,'deeper'
146
- left.deep_merge!(right)
211
+ left.deep_merge!(right, options)
147
212
  when :deep,'deep'
148
- left.deep_merge(right)
213
+ left.deep_merge(right, options)
149
214
  else # Native and undefined
150
215
  left.merge(right)
151
216
  end
@@ -164,43 +229,97 @@ class Hiera
164
229
  # Backend instances are cached so if you need to connect to any
165
230
  # databases then do so in your constructor, future calls to your
166
231
  # backend will not create new instances
167
- def lookup(key, default, scope, order_override, resolution_type)
232
+
233
+ # @param key [String] The key to lookup
234
+ # @param scope [#[]] The primary source of data for substitutions.
235
+ # @param order_override [#[],nil] An override that will be pre-pended to the hierarchy definition.
236
+ # @param resolution_type [Symbol,Hash,nil] One of :hash, :array,:priority or a Hash with deep merge behavior and options
237
+ # @param context [#[]] Context used for internal processing
238
+ # @return [Object] The value that corresponds to the given key or nil if no such value cannot be found
239
+ #
240
+ def lookup(key, default, scope, order_override, resolution_type, context = {:recurse_guard => nil})
168
241
  @backends ||= {}
169
242
  answer = nil
170
243
 
244
+ # order_override is kept as an explicit argument for backwards compatibility, but should be specified
245
+ # in the context for internal handling.
246
+ context ||= {}
247
+ order_override ||= context[:order_override]
248
+ context[:order_override] ||= order_override
249
+
250
+ strategy = resolution_type.is_a?(Hash) ? :hash : resolution_type
251
+
252
+ segments = key.split('.')
253
+ subsegments = nil
254
+ if segments.size > 1
255
+ raise ArgumentError, "Resolution type :#{strategy} is illegal when doing segmented key lookups" unless strategy.nil? || strategy == :priority
256
+ subsegments = segments.drop(1)
257
+ end
258
+
259
+ found = false
171
260
  Config[:backends].each do |backend|
172
- if constants.include?("#{backend.capitalize}_backend") || constants.include?("#{backend.capitalize}_backend".to_sym)
173
- @backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
174
- new_answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
175
-
176
- if not new_answer.nil?
177
- case resolution_type
178
- when :array
179
- raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
180
- answer ||= []
181
- answer << new_answer
182
- when :hash
183
- raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
184
- answer ||= {}
185
- answer = merge_answer(new_answer,answer)
186
- else
187
- answer = new_answer
188
- break
189
- end
261
+ backend_constant = "#{backend.capitalize}_backend"
262
+ if constants.include?(backend_constant) || constants.include?(backend_constant.to_sym)
263
+ backend = (@backends[backend] ||= find_backend(backend_constant))
264
+ found_in_backend = false
265
+ new_answer = catch(:no_such_key) do
266
+ value = backend.lookup(segments[0], scope, order_override, resolution_type, context)
267
+ value = qualified_lookup(subsegments, value) unless subsegments.nil?
268
+ found_in_backend = true
269
+ value
270
+ end
271
+ next unless found_in_backend
272
+ found = true
273
+
274
+ case strategy
275
+ when :array
276
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
277
+ answer ||= []
278
+ answer << new_answer
279
+ when :hash
280
+ raise Exception, "Hiera type mismatch for key '#{key}': expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
281
+ answer ||= {}
282
+ answer = merge_answer(new_answer, answer, resolution_type)
283
+ else
284
+ answer = new_answer
285
+ break
190
286
  end
191
287
  end
192
288
  end
193
289
 
194
- answer = resolve_answer(answer, resolution_type) unless answer.nil?
195
- answer = parse_string(default, scope) if answer.nil? and default.is_a?(String)
290
+ answer = resolve_answer(answer, strategy) unless answer.nil?
291
+ answer = parse_string(default, scope, {}, context) if !found && default.is_a?(String)
196
292
 
197
- return default if answer.nil?
293
+ return default if !found && answer.nil?
198
294
  return answer
199
295
  end
200
296
 
201
297
  def clear!
202
298
  @backends = {}
203
299
  end
300
+
301
+ def qualified_lookup(segments, hash)
302
+ value = hash
303
+ segments.each do |segment|
304
+ throw :no_such_key if value.nil?
305
+ if segment =~ /^[0-9]+$/
306
+ segment = segment.to_i
307
+ raise Exception, "Hiera type mismatch: Got #{value.class.name} when Array was expected enable lookup using key '#{segment}'" unless value.instance_of?(Array)
308
+ throw :no_such_key unless segment < value.size
309
+ else
310
+ raise Exception, "Hiera type mismatch: Got #{value.class.name} when a non Array object that responds to '[]' was expected to enable lookup using key '#{segment}'" unless value.respond_to?(:'[]') && !value.instance_of?(Array);
311
+ throw :no_such_key unless value.include?(segment)
312
+ end
313
+ value = value[segment]
314
+ end
315
+ value
316
+ end
317
+
318
+ def find_backend(backend_constant)
319
+ backend = Backend.const_get(backend_constant).new
320
+ return backend.method(:lookup).arity == 4 ? Backend1xWrapper.new(backend) : backend
321
+ end
322
+ private :find_backend
204
323
  end
205
324
  end
206
325
  end
data/lib/hiera/config.rb CHANGED
@@ -54,9 +54,7 @@ class Hiera::Config
54
54
  begin
55
55
  require "deep_merge"
56
56
  rescue LoadError
57
- Hiera.warn "Ignoring configured merge_behavior"
58
- Hiera.warn "Must have 'deep_merge' gem installed."
59
- @config[:merge_behavior] = :native
57
+ raise Hiera::Error, "Must have 'deep_merge' gem installed for the configured merge_behavior."
60
58
  end
61
59
  end
62
60
  end