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 +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
|