hiera 1.3.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +47 -10
- data/bin/hiera +48 -31
- data/lib/hiera.rb +55 -3
- data/lib/hiera/backend.rb +124 -37
- data/lib/hiera/backend/json_backend.rb +9 -7
- data/lib/hiera/backend/yaml_backend.rb +9 -7
- data/lib/hiera/config.rb +1 -3
- data/lib/hiera/interpolate.rb +52 -16
- data/lib/hiera/util.rb +5 -5
- data/lib/hiera/version.rb +1 -1
- data/spec/spec_helper.rb +12 -0
- data/spec/unit/backend/json_backend_spec.rb +9 -9
- data/spec/unit/backend/yaml_backend_spec.rb +20 -15
- data/spec/unit/backend_spec.rb +186 -32
- data/spec/unit/config_spec.rb +15 -0
- data/spec/unit/fixtures/interpolate/config/hiera.yaml +6 -0
- data/spec/unit/fixtures/interpolate/data/niltest.yaml +2 -0
- data/spec/unit/fixtures/interpolate/data/recursive.yaml +3 -0
- data/spec/unit/fixtures/override/config/hiera.yaml +5 -0
- data/spec/unit/fixtures/override/data/alternate.yaml +1 -0
- data/spec/unit/fixtures/override/data/common.yaml +2 -0
- data/spec/unit/interpolate_spec.rb +36 -0
- data/spec/unit/util_spec.rb +4 -4
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd4a69f9d2131165eba3e8767b8731dcc11f3a6d
|
4
|
+
data.tar.gz: fd038d50edc2f8c3dd6d21d21d30cc9463b11c4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d669f8a2ea3cea56f2c0d66959e2aa2df7a84f74940ae51e8c192929f45e102d99b85c7530084c87294594557229060de56b093e87c5a96076c7849343efb15
|
7
|
+
data.tar.gz: 10d478df00b859bddf583631ae9fe9f2f1028b3394fda6dfd38d3e0a2a57ce32a1b41a681b814552594421d9b9b373b94c6300eccdd2291827abacf8f9c3a566
|
data/LICENSE
CHANGED
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/
|
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/
|
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/
|
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/
|
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/
|
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 /
|
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 /
|
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("/
|
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/
|
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 [
|
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
|
-
|
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
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
195
|
+
unless initial_scopes.empty?
|
196
|
+
initial_scopes.each { |this_scope|
|
197
|
+
# Load initial scope
|
177
198
|
begin
|
178
|
-
options[:scope] = load_scope(
|
199
|
+
options[:scope] = load_scope(this_scope[:value], this_scope[:type])
|
179
200
|
rescue Exception => e
|
180
|
-
STDERR.puts "Could not load
|
201
|
+
STDERR.puts "Could not load #{this_scope[:name]} scope: #{e.class}: #{e}"
|
181
202
|
exit 1
|
182
203
|
end
|
183
|
-
|
184
|
-
end
|
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
|
-
|
228
|
-
puts ans
|
229
|
-
else
|
230
|
-
pp ans
|
231
|
-
end
|
248
|
+
output_answer(ans, options[:format])
|
data/lib/hiera.rb
CHANGED
@@ -49,15 +49,67 @@ class Hiera
|
|
49
49
|
|
50
50
|
# Calls the backends to do the actual lookup.
|
51
51
|
#
|
52
|
-
# The
|
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
|
-
|
data/lib/hiera/backend.rb
CHANGED
@@ -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
|
-
#
|
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/
|
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
|
-
|
176
|
-
|
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
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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,
|
227
|
-
answer = parse_string(default, scope) if
|
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
|