hiera 1.3.0 → 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 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