hiera 1.3.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 54743f04196eaecdaffcb0162606f26843f35a03
4
- data.tar.gz: c83bf33b0c25e508380073c67b94796493d01124
3
+ metadata.gz: dd4a69f9d2131165eba3e8767b8731dcc11f3a6d
4
+ data.tar.gz: fd038d50edc2f8c3dd6d21d21d30cc9463b11c4c
5
5
  SHA512:
6
- metadata.gz: 50281c08a2494cce3c6e2d59ec94a47eb14dbad34f0b0b20915fe2b96f0bc5ce1c687984fb74426a61760018eaef64f911eb1cf677820b8ca2261f32549f4bba
7
- data.tar.gz: 570c1a8cff80579c90afd19451075bc52a4c035d77852d665980b1b3c2c0fd6075a5da11c947ad9eac2f9ce24399cd797d2ae23beed63bef372db4cd5fb9aa83
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
@@ -1,9 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # For security reasons, ensure that '.' is not on the load path
4
- # This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path
5
- $LOAD_PATH.delete '.'
6
-
7
3
  # CLI client for Hiera.
8
4
  #
9
5
  # To lookup the 'release' key for a node given Puppet YAML facts:
@@ -36,9 +32,12 @@ options = {
36
32
  :scope => {},
37
33
  :key => nil,
38
34
  :verbose => false,
39
- :resolution_type => :priority
35
+ :resolution_type => :priority,
36
+ :format => :ruby
40
37
  }
41
38
 
39
+ initial_scopes = Array.new
40
+
42
41
  # Loads the scope from YAML or JSON files
43
42
  def load_scope(source, type=:yaml)
44
43
  case type
@@ -117,6 +116,26 @@ def load_scope(source, type=:yaml)
117
116
  scope
118
117
  end
119
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
+
120
139
  OptionParser.new do |opts|
121
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"
122
141
 
@@ -147,41 +166,43 @@ OptionParser.new do |opts|
147
166
  end
148
167
 
149
168
  opts.on("--json SCOPE", "-j", "JSON format file to load scope from") do |v|
150
- begin
151
- options[:scope] = load_scope(v, :json)
152
- rescue Exception => e
153
- STDERR.puts "Could not load JSON scope: #{e.class}: #{e}"
154
- exit 1
155
- end
169
+ initial_scopes << { :type => :json, :value => v, :name => "JSON" }
156
170
  end
157
171
 
158
172
  opts.on("--yaml SCOPE", "-y", "YAML format file to load scope from") do |v|
159
- begin
160
- options[:scope] = load_scope(v)
161
- rescue Exception => e
162
- STDERR.puts "Could not load YAML scope: #{e.class}: #{e}"
163
- exit 1
164
- end
173
+ initial_scopes << { :type => :yaml, :value => v, :name => "YAML" }
165
174
  end
166
175
 
167
176
  opts.on("--mcollective IDENTITY", "-m", "Use facts from a node (via mcollective) as scope") do |v|
168
- begin
169
- options[:scope] = load_scope(v, :mcollective)
170
- rescue Exception => e
171
- 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}"
172
190
  exit 1
173
191
  end
174
192
  end
193
+ end.parse!
175
194
 
176
- 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
177
198
  begin
178
- options[:scope] = load_scope(v, :inventory_service)
199
+ options[:scope] = load_scope(this_scope[:value], this_scope[:type])
179
200
  rescue Exception => e
180
- 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}"
181
202
  exit 1
182
203
  end
183
- end
184
- end.parse!
204
+ }
205
+ end
185
206
 
186
207
  # arguments can be:
187
208
  #
@@ -224,8 +245,4 @@ end
224
245
 
225
246
  ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, options[:resolution_type])
226
247
 
227
- if ans.is_a?(String)
228
- puts ans
229
- else
230
- pp ans
231
- end
248
+ output_answer(ans, options[:format])
@@ -49,15 +49,67 @@ class Hiera
49
49
 
50
50
  # Calls the backends to do the actual lookup.
51
51
  #
52
- # The scope can be anything that responds to [], if you have input
52
+ # The _scope_ can be anything that responds to `[]`, if you have input
53
53
  # data like a Puppet Scope that does not you can wrap that data in a
54
- # class that has a [] method that fetches the data from your source.
54
+ # class that has a `[]` method that fetches the data from your source.
55
55
  # See hiera-puppet for an example of this.
56
56
  #
57
57
  # The order-override will insert as first in the hierarchy a data source
58
58
  # of your choice.
59
+ #
60
+ # Possible values for the _resolution_type_ parameter:
61
+ #
62
+ # - _:priority_ - This is the default. First found value is returned and no merge is performed
63
+ # - _:array_ - An array merge lookup assembles a value from every matching level of the hierarchy. It retrieves all
64
+ # of the (string or array) values for a given key, then flattens them into a single array of unique values.
65
+ # If _priority_ lookup can be thought of as a “default with overrides” pattern, _array_ merge lookup can be though
66
+ # of as “default with additions.”
67
+ # - _:hash_ - A hash merge lookup assembles a value from every matching level of the hierarchy. It retrieves all of
68
+ # the (hash) values for a given key, then merges the hashes into a single hash. Hash merge lookups will fail with
69
+ # an error if any of the values found in the data sources are strings or arrays. It only works when every value
70
+ # found is a hash. The actual merge behavior is determined by looking up the keys `:merge_behavior` and
71
+ # `:deep_merge_options` in the Hiera config. `:merge_behavior` can be set to `:deep`, :deeper` or `:native`
72
+ # (explained in detail below).
73
+ # - _{ deep merge options }_ - Configured values for `:merge_behavior` and `:deep_merge_options`will be completely
74
+ # ignored. Instead the _resolution_type_ will be a `:hash` merge where the `:merge_behavior` will be the value
75
+ # keyed by `:behavior` in the given hash and the `:deep_merge_options` will be the remaining top level entries of
76
+ # that same hash.
77
+ #
78
+ # Valid behaviors for the _:hash_ resolution type:
79
+ #
80
+ # - _native_ - Performs a simple hash-merge by overwriting keys of lower lookup priority.
81
+ # - _deeper_ - In a deeper hash merge, Hiera recursively merges keys and values in each source hash. For each key,
82
+ # if the value is:
83
+ # - only present in one source hash, it goes into the final hash.
84
+ # - a string/number/boolean and exists in two or more source hashes, the highest priority value goes into
85
+ # the final hash.
86
+ # - an array and exists in two or more source hashes, the values from each source are merged into a single
87
+ # array and de-duplicated (but not automatically flattened, as in an array merge lookup).
88
+ # - a hash and exists in two or more source hashes, the values from each source are recursively merged, as
89
+ # though they were source hashes.
90
+ # - mismatched between two or more source hashes, we haven’t validated the behavior. It should act as
91
+ # described in the deep_merge gem documentation.
92
+ # - _deep_ - In a deep hash merge, Hiera behaves the same as for _deeper_, except that when a string/number/boolean
93
+ # exists in two or more source hashes, the lowest priority value goes into the final hash. This is considered
94
+ # largely useless and should be avoided. Use _deeper_ instead.
95
+ #
96
+ # The _merge_ can be given as a hash with the mandatory key `:strategy` to denote the actual strategy. This
97
+ # is useful for the `:deeper` and `:deep` strategy since they can use additional options to control the behavior.
98
+ # The options can be passed as top level keys in the `merge` parameter when it is a given as a hash. Recognized
99
+ # options are:
100
+ #
101
+ # - 'knockout_prefix' Set to string value to signify prefix which deletes elements from existing element. Defaults is _undef_
102
+ # - 'sort_merged_arrays' Set to _true_ to sort all arrays that are merged together. Default is _false_
103
+ # - 'unpack_arrays' Set to string value used as a deliminator to join all array values and then split them again. Default is _undef_
104
+ # - 'merge_hash_arrays' Set to _true_ to merge hashes within arrays. Default is _false_
105
+ #
106
+ # @param key [String] The key to lookup
107
+ # @param default [Object,nil] The value to return when there is no match for _key_
108
+ # @param scope [#[],nil] The scope to use for the lookup
109
+ # @param order_override [#[]] An override that will considered the first source of lookup
110
+ # @param resolution_type [String,Hash<Symbol,String>] Symbolic resolution type or deep merge configuration
111
+ # @return [Object] The found value or the given _default_ value
59
112
  def lookup(key, default, scope, order_override=nil, resolution_type=:priority)
60
113
  Backend.lookup(key, default, scope, order_override, resolution_type)
61
114
  end
62
115
  end
63
-
@@ -8,6 +8,22 @@ end
8
8
 
9
9
  class Hiera
10
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
+
11
27
  class << self
12
28
  # Data lives in /var/lib/hiera by default. If a backend
13
29
  # supplies a datadir in the config it will be used and
@@ -72,7 +88,7 @@ class Hiera
72
88
  hierarchy.insert(0, override) if override
73
89
 
74
90
  hierarchy.flatten.map do |source|
75
- source = parse_string(source, scope)
91
+ source = parse_string(source, scope, {}, :order_override => override)
76
92
  yield(source) unless source == "" or source =~ /(^\/|\/\/|\/$)/
77
93
  end
78
94
  end
@@ -118,34 +134,35 @@ class Hiera
118
134
  # This will not be modified, instead a new string will be returned.
119
135
  # @param scope [#[]] The primary source of data for substitutions.
120
136
  # @param extra_data [#[]] The secondary source of data for substitutions.
137
+ # @param context [#[]] Context can include :recurse_guard and :order_override.
121
138
  # @return [String] A copy of the data with all instances of <code>%{...}</code> replaced.
122
139
  #
123
140
  # @api public
124
- def parse_string(data, scope, extra_data={})
125
- Hiera::Interpolate.interpolate(data, 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)
126
143
  end
127
144
 
128
145
  # Parses a answer received from data files
129
146
  #
130
147
  # Ultimately it just pass the data through parse_string but
131
148
  # it makes some effort to handle arrays of strings as well
132
- def parse_answer(data, scope, extra_data={})
149
+ def parse_answer(data, scope, extra_data={}, context={:recurse_guard => nil, :order_override => nil})
133
150
  if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
134
151
  return data
135
152
  elsif data.is_a?(String)
136
- return parse_string(data, scope, extra_data)
153
+ return parse_string(data, scope, extra_data, context)
137
154
  elsif data.is_a?(Hash)
138
155
  answer = {}
139
156
  data.each_pair do |key, val|
140
- interpolated_key = parse_string(key, scope, extra_data)
141
- 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)
142
159
  end
143
160
 
144
161
  return answer
145
162
  elsif data.is_a?(Array)
146
163
  answer = []
147
164
  data.each do |item|
148
- answer << parse_answer(item, scope, extra_data)
165
+ answer << parse_answer(item, scope, extra_data, context)
149
166
  end
150
167
 
151
168
  return answer
@@ -163,21 +180,37 @@ class Hiera
163
180
  end
164
181
  end
165
182
 
166
- # Merges two hashes answers with the configured merge behavior.
167
- # :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}
168
187
  #
169
- # 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)
170
189
  #
171
190
  # :native => Native Hash.merge
172
191
  # :deep => Use Hash.deep_merge
173
192
  # :deeper => Use Hash.deep_merge!
174
193
  #
175
- def merge_answer(left,right)
176
- 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
177
210
  when :deeper,'deeper'
178
- left.deep_merge!(right)
211
+ left.deep_merge!(right, options)
179
212
  when :deep,'deep'
180
- left.deep_merge(right)
213
+ left.deep_merge(right, options)
181
214
  else # Native and undefined
182
215
  left.merge(right)
183
216
  end
@@ -196,43 +229,97 @@ class Hiera
196
229
  # Backend instances are cached so if you need to connect to any
197
230
  # databases then do so in your constructor, future calls to your
198
231
  # backend will not create new instances
199
- 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})
200
241
  @backends ||= {}
201
242
  answer = nil
202
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
203
260
  Config[:backends].each do |backend|
204
- if constants.include?("#{backend.capitalize}_backend") || constants.include?("#{backend.capitalize}_backend".to_sym)
205
- @backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
206
- new_answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
207
-
208
- if not new_answer.nil?
209
- case resolution_type
210
- when :array
211
- raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
212
- answer ||= []
213
- answer << new_answer
214
- when :hash
215
- raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
216
- answer ||= {}
217
- answer = merge_answer(new_answer,answer)
218
- else
219
- answer = new_answer
220
- break
221
- 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
222
286
  end
223
287
  end
224
288
  end
225
289
 
226
- answer = resolve_answer(answer, resolution_type) unless answer.nil?
227
- 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)
228
292
 
229
- return default if answer.nil?
293
+ return default if !found && answer.nil?
230
294
  return answer
231
295
  end
232
296
 
233
297
  def clear!
234
298
  @backends = {}
235
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
236
323
  end
237
324
  end
238
325
  end