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 +7 -0
- data/LICENSE +1 -1
- data/README.md +47 -10
- data/bin/hiera +48 -27
- data/lib/hiera/backend/json_backend.rb +9 -7
- data/lib/hiera/backend/yaml_backend.rb +17 -14
- data/lib/hiera/backend.rb +163 -44
- data/lib/hiera/config.rb +1 -3
- data/lib/hiera/interpolate.rb +64 -14
- data/lib/hiera/util.rb +5 -5
- data/lib/hiera/version.rb +1 -1
- data/lib/hiera.rb +55 -3
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/backend/json_backend_spec.rb +9 -9
- data/spec/unit/backend/yaml_backend_spec.rb +62 -73
- data/spec/unit/backend_spec.rb +192 -32
- data/spec/unit/config_spec.rb +17 -2
- 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 +51 -41
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
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
@@ -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
|
-
|
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
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
195
|
+
unless initial_scopes.empty?
|
196
|
+
initial_scopes.each { |this_scope|
|
197
|
+
# Load initial scope
|
173
198
|
begin
|
174
|
-
options[:scope] = load_scope(
|
199
|
+
options[:scope] = load_scope(this_scope[:value], this_scope[:type])
|
175
200
|
rescue Exception => e
|
176
|
-
STDERR.puts "Could not load
|
201
|
+
STDERR.puts "Could not load #{this_scope[:name]} scope: #{e.class}: #{e}"
|
177
202
|
exit 1
|
178
203
|
end
|
179
|
-
|
180
|
-
end
|
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
|
-
|
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.
|
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
|
-
|
53
|
+
datafile_in(datadir(backend, scope), source, extension)
|
54
|
+
end
|
39
55
|
|
40
|
-
|
41
|
-
|
56
|
+
# @api private
|
57
|
+
def datafile_in(datadir, source, extension)
|
58
|
+
file = File.join(datadir, "#{source}.#{extension}")
|
42
59
|
|
43
|
-
|
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,
|
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
|
-
#
|
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/
|
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
|
-
|
144
|
-
|
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
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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,
|
195
|
-
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)
|
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
|
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
|