puppet 4.9.3-x64-mingw32 → 4.9.4-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/lib/puppet/functions/eyaml_lookup_key.rb +18 -10
- data/lib/puppet/functions/hocon_data.rb +6 -4
- data/lib/puppet/functions/json_data.rb +7 -5
- data/lib/puppet/functions/yaml_data.rb +14 -11
- data/lib/puppet/pops/functions/dispatcher.rb +8 -9
- data/lib/puppet/pops/lookup.rb +3 -1
- data/lib/puppet/pops/lookup/configured_data_provider.rb +1 -1
- data/lib/puppet/pops/lookup/context.rb +75 -11
- data/lib/puppet/pops/lookup/data_hash_function_provider.rb +1 -1
- data/lib/puppet/pops/lookup/data_provider.rb +9 -18
- data/lib/puppet/pops/lookup/function_provider.rb +5 -2
- data/lib/puppet/pops/lookup/global_data_provider.rb +12 -7
- data/lib/puppet/pops/lookup/hiera_config.rb +51 -23
- data/lib/puppet/pops/lookup/invocation.rb +14 -0
- data/lib/puppet/pops/lookup/lookup_key_function_provider.rb +3 -2
- data/lib/puppet/pops/types/types.rb +68 -21
- data/lib/puppet/version.rb +1 -1
- data/spec/unit/functions/hiera_spec.rb +88 -19
- data/spec/unit/functions/lookup_spec.rb +126 -68
- data/spec/unit/pops/lookup/context_spec.rb +126 -18
- data/spec/unit/pops/lookup/lookup_spec.rb +34 -0
- metadata +2 -2
@@ -28,21 +28,29 @@ Puppet::Functions.create_function(:eyaml_lookup_key) do
|
|
28
28
|
context.explain { "Setting Eyaml option '#{k}' to '#{v}'" }
|
29
29
|
end
|
30
30
|
end
|
31
|
-
raw_data = load_data_hash(options)
|
31
|
+
raw_data = load_data_hash(options, context)
|
32
32
|
context.cache(nil, raw_data)
|
33
33
|
end
|
34
34
|
context.not_found unless raw_data.include?(key)
|
35
35
|
context.cache(key, decrypt_value(raw_data[key], context))
|
36
36
|
end
|
37
37
|
|
38
|
-
def load_data_hash(options)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
def load_data_hash(options, context)
|
39
|
+
path = options['path']
|
40
|
+
context.cached_file_data(path) do |content|
|
41
|
+
begin
|
42
|
+
data = YAML.load(content, path)
|
43
|
+
if data.is_a?(Hash)
|
44
|
+
Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data)
|
45
|
+
else
|
46
|
+
Puppet.warning("#{path}: file does not contain a valid yaml hash")
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
rescue YAML::SyntaxError => ex
|
50
|
+
# Psych errors includes the absolute path to the file, so no need to add that
|
51
|
+
# to the message
|
52
|
+
raise Puppet::DataBinding::LookupError, "Unable to parse #{ex.message}"
|
53
|
+
end
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
@@ -52,7 +60,7 @@ Puppet::Functions.create_function(:eyaml_lookup_key) do
|
|
52
60
|
decrypt(value, context)
|
53
61
|
when Hash
|
54
62
|
result = {}
|
55
|
-
value.each_pair { |k, v| result[k] = decrypt_value(v, context) }
|
63
|
+
value.each_pair { |k, v| result[context.interpolate(k)] = decrypt_value(v, context) }
|
56
64
|
result
|
57
65
|
when Array
|
58
66
|
value.map { |v| decrypt_value(v, context) }
|
@@ -15,10 +15,12 @@ Puppet::Functions.create_function(:hocon_data) do
|
|
15
15
|
|
16
16
|
def hocon_data(options, context)
|
17
17
|
path = options['path']
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
context.cached_file_data(path) do |content|
|
19
|
+
begin
|
20
|
+
Hocon.parse(content)
|
21
|
+
rescue Hocon::ConfigError => ex
|
22
|
+
raise Puppet::DataBinding::LookupError, "Unable to parse (#{path}): #{ex.message}"
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -8,11 +8,13 @@ Puppet::Functions.create_function(:json_data) do
|
|
8
8
|
|
9
9
|
def json_data(options, context)
|
10
10
|
path = options['path']
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
context.cached_file_data(path) do |content|
|
12
|
+
begin
|
13
|
+
JSON.parse(content)
|
14
|
+
rescue JSON::ParserError => ex
|
15
|
+
# Filename not included in message, so we add it here.
|
16
|
+
raise Puppet::DataBinding::LookupError, "Unable to parse (#{path}): #{ex.message}"
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
@@ -9,18 +9,21 @@ Puppet::Functions.create_function(:yaml_data) do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def yaml_data(options, context)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
path = options['path']
|
13
|
+
context.cached_file_data(path) do |content|
|
14
|
+
begin
|
15
|
+
data = YAML.load(content, path)
|
16
|
+
if data.is_a?(Hash)
|
17
|
+
Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data)
|
18
|
+
else
|
19
|
+
Puppet.warning("#{path}: file does not contain a valid yaml hash")
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
rescue YAML::SyntaxError => ex
|
23
|
+
# Psych errors includes the absolute path to the file, so no need to add that
|
24
|
+
# to the message
|
25
|
+
raise Puppet::DataBinding::LookupError, "Unable to parse #{ex.message}"
|
18
26
|
end
|
19
|
-
Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data.nil? ? {} : data)
|
20
|
-
rescue YAML::SyntaxError => ex
|
21
|
-
# Psych errors includes the absolute path to the file, so no need to add that
|
22
|
-
# to the message
|
23
|
-
raise Puppet::DataBinding::LookupError, "Unable to parse #{ex.message}"
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
@@ -28,15 +28,14 @@ class Puppet::Pops::Functions::Dispatcher
|
|
28
28
|
#
|
29
29
|
# @api private
|
30
30
|
def dispatch(instance, calling_scope, args, &block)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
raise ArgumentError, Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(instance.class.name, @dispatchers, actual)
|
31
|
+
found = @dispatchers.find { |d| d.type.callable_with?(args, block) }
|
32
|
+
unless found
|
33
|
+
args_type = Puppet::Pops::Types::TypeCalculator.singleton.infer_set(block_given? ? args + [block] : args)
|
34
|
+
raise ArgumentError, Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(instance.class.name, @dispatchers, args_type)
|
35
|
+
end
|
36
|
+
|
37
|
+
catch(:next) do
|
38
|
+
found.invoke(instance, calling_scope, args, &block)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
data/lib/puppet/pops/lookup.rb
CHANGED
@@ -73,7 +73,9 @@ module Lookup
|
|
73
73
|
|
74
74
|
# @api private
|
75
75
|
def self.search_and_merge(name, lookup_invocation, merge)
|
76
|
-
lookup_invocation.lookup_adapter.lookup(name, lookup_invocation, merge)
|
76
|
+
answer = lookup_invocation.lookup_adapter.lookup(name, lookup_invocation, merge)
|
77
|
+
lookup_invocation.emit_debug_info("Automatic Parameter Lookup of '#{name}") if Puppet[:debug]
|
78
|
+
answer
|
77
79
|
end
|
78
80
|
|
79
81
|
def self.assert_type(subject, type, value)
|
@@ -13,7 +13,7 @@ class ConfiguredDataProvider
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def config(lookup_invocation)
|
16
|
-
@config ||= assert_config_version(HieraConfig.create(configuration_path(lookup_invocation)))
|
16
|
+
@config ||= assert_config_version(HieraConfig.create(lookup_invocation, configuration_path(lookup_invocation)))
|
17
17
|
end
|
18
18
|
|
19
19
|
# @return [Pathname] the path to the configuration
|
@@ -2,19 +2,73 @@ require_relative 'interpolation'
|
|
2
2
|
|
3
3
|
module Puppet::Pops
|
4
4
|
module Lookup
|
5
|
+
# The EnvironmentContext is adapted to the current environment
|
6
|
+
#
|
7
|
+
class EnvironmentContext < Adaptable::Adapter
|
8
|
+
class FileData
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def initialize(path, inode, mtime, size, data)
|
12
|
+
@path = path
|
13
|
+
@inode = inode
|
14
|
+
@mtime = mtime
|
15
|
+
@size = size
|
16
|
+
@data = data
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?(stat)
|
20
|
+
stat.ino == @inode && stat.mtime == @mtime && stat.size == @size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :environment_name
|
25
|
+
|
26
|
+
def self.create_adapter(environment)
|
27
|
+
new(environment)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(environment)
|
31
|
+
@environment_name = environment.name
|
32
|
+
@file_data_cache = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads the contents of the file given by _path_. The content is then yielded to the provided block in
|
36
|
+
# case a block is given, and the returned value from that block is cached and returned by this method.
|
37
|
+
# If no block is given, the content is stored instead.
|
38
|
+
#
|
39
|
+
# The cache is retained as long as the inode, mtime, and size of the file remains unchanged.
|
40
|
+
#
|
41
|
+
# @param path [String] path to the file to be read
|
42
|
+
# @yieldparam content [String] the content that was read from the file
|
43
|
+
# @yieldreturn [Object] some result based on the content
|
44
|
+
# @return [Object] the content, or if a block was given, the return value of the block
|
45
|
+
#
|
46
|
+
def cached_file_data(path)
|
47
|
+
file_data = @file_data_cache[path]
|
48
|
+
stat = Puppet::FileSystem.stat(path)
|
49
|
+
unless file_data && file_data.valid?(stat)
|
50
|
+
Puppet.debug("File at '#{path}' was changed, reloading") if file_data
|
51
|
+
content = Puppet::FileSystem.read(path, :encoding => 'utf-8')
|
52
|
+
file_data = FileData.new(path, stat.ino, stat.mtime, stat.size, block_given? ? yield(content) : content)
|
53
|
+
@file_data_cache[path] = file_data
|
54
|
+
end
|
55
|
+
file_data.data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
5
59
|
# A FunctionContext is created for each unique hierarchy entry and adapted to the Compiler (and hence shares
|
6
60
|
# the compiler's life-cycle).
|
7
61
|
# @api private
|
8
62
|
class FunctionContext
|
9
63
|
include Interpolation
|
10
64
|
|
11
|
-
attr_reader :
|
65
|
+
attr_reader :module_name, :function
|
12
66
|
attr_accessor :data_hash
|
13
67
|
|
14
|
-
def initialize(
|
68
|
+
def initialize(environment_context, module_name, function)
|
15
69
|
@data_hash = nil
|
16
70
|
@cache = {}
|
17
|
-
@
|
71
|
+
@environment_context = environment_context
|
18
72
|
@module_name = module_name
|
19
73
|
@function = function
|
20
74
|
end
|
@@ -51,6 +105,14 @@ class FunctionContext
|
|
51
105
|
Types::Iterable.on(@cache)
|
52
106
|
end
|
53
107
|
end
|
108
|
+
|
109
|
+
def cached_file_data(path, &block)
|
110
|
+
@environment_context.cached_file_data(path, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def environment_name
|
114
|
+
@environment_context.environment_name
|
115
|
+
end
|
54
116
|
end
|
55
117
|
|
56
118
|
# The Context is created once for each call to a function. It provides a combination of the {Invocation} object needed
|
@@ -73,10 +135,12 @@ class Context
|
|
73
135
|
key_type = tf.optional(tf.scalar)
|
74
136
|
@type = Pcore::create_object_type(loader, ir, self, 'Puppet::LookupContext', 'Any',
|
75
137
|
{
|
76
|
-
'environment_name' =>
|
138
|
+
'environment_name' => {
|
139
|
+
Types::KEY_TYPE => Types::PStringType::NON_EMPTY,
|
140
|
+
Types::KEY_KIND => Types::PObjectType::ATTRIBUTE_KIND_DERIVED
|
141
|
+
},
|
77
142
|
'module_name' => {
|
78
|
-
Types::KEY_TYPE => tf.
|
79
|
-
Types::KEY_VALUE => nil
|
143
|
+
Types::KEY_TYPE => tf.variant(Types::PStringType::NON_EMPTY, Types::PUndefType::DEFAULT)
|
80
144
|
}
|
81
145
|
},
|
82
146
|
{
|
@@ -90,19 +154,19 @@ class Context
|
|
90
154
|
'cached_entries' => tf.variant(
|
91
155
|
tf.callable([0, 0, tf.callable(1,1)], tf.undef),
|
92
156
|
tf.callable([0, 0, tf.callable(2,2)], tf.undef),
|
93
|
-
tf.callable([0, 0], tf.iterable(tf.tuple([key_type, tf.any])))
|
94
|
-
)
|
157
|
+
tf.callable([0, 0], tf.iterable(tf.tuple([key_type, tf.any])))),
|
158
|
+
'cached_file_data' => tf.callable(tf.string, tf.optional(tf.callable([1, 1])))
|
95
159
|
}
|
96
160
|
).resolve(Types::TypeParser.singleton, loader)
|
97
161
|
end
|
98
162
|
|
99
163
|
# Mainly for test purposes. Makes it possible to create a {Context} in Puppet code provided that a current {Invocation} exists.
|
100
|
-
def self.from_asserted_args(
|
101
|
-
new(FunctionContext.new(
|
164
|
+
def self.from_asserted_args(module_name)
|
165
|
+
new(FunctionContext.new(EnvironmentContext.adapt(Puppet.lookup(:environments).get(Puppet[:environment])), module_name, nil), Invocation.current)
|
102
166
|
end
|
103
167
|
|
104
168
|
# Public methods delegated to the {FunctionContext}
|
105
|
-
def_delegators :@function_context, :cache, :cache_all, :cache_has_key, :cached_value, :cached_entries, :environment_name, :module_name
|
169
|
+
def_delegators :@function_context, :cache, :cache_all, :cache_has_key, :cached_value, :cached_entries, :environment_name, :module_name, :cached_file_data
|
106
170
|
|
107
171
|
def initialize(function_context, lookup_invocation)
|
108
172
|
@lookup_invocation = lookup_invocation
|
@@ -54,7 +54,7 @@ class DataHashFunctionProvider < FunctionProvider
|
|
54
54
|
lookup_invocation.report_not_found(root_key)
|
55
55
|
throw :no_such_key
|
56
56
|
end
|
57
|
-
interpolate(value, lookup_invocation, true)
|
57
|
+
interpolate(parent_data_provider.validate_data_value(self, value), lookup_invocation, true)
|
58
58
|
end
|
59
59
|
|
60
60
|
def data_hash(lookup_invocation, location)
|
@@ -70,32 +70,23 @@ module DataProvider
|
|
70
70
|
raise NotImplementedError, "Subclass of #{DataProvider.name} must implement 'name' method"
|
71
71
|
end
|
72
72
|
|
73
|
-
# Asserts that _data_hash_ is a
|
73
|
+
# Asserts that _data_hash_ is a hash.
|
74
74
|
#
|
75
75
|
# @param data_provider [DataProvider] The data provider that produced the hash
|
76
76
|
# @param data_hash [Hash{String=>Object}] The data hash
|
77
77
|
# @return [Hash{String=>Object}] The data hash
|
78
78
|
def validate_data_hash(data_provider, data_hash)
|
79
79
|
Types::TypeAsserter.assert_instance_of(nil, Types::PHashType::DEFAULT, data_hash) { "Value returned from #{data_provider.name}" }
|
80
|
-
data_hash.each_pair { |k, v| validate_data_entry(data_provider, k, v) }
|
81
|
-
data_hash
|
82
80
|
end
|
83
81
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
value
|
93
|
-
end
|
94
|
-
|
95
|
-
def validate_data_entry(data_provider, key, value)
|
96
|
-
Types::TypeAsserter.assert_instance_of(nil, DataProvider.key_type, key) { "Key in hash returned from #{data_provider.name}" }
|
97
|
-
validate_data_value(data_provider, value, 'in hash ')
|
98
|
-
nil
|
82
|
+
# Asserts that _data_value_ is of valid type.
|
83
|
+
#
|
84
|
+
# @param data_provider [DataProvider] The data provider that produced the hash
|
85
|
+
# @param data_value [Object] The data value
|
86
|
+
# @return [Object] The data value
|
87
|
+
def validate_data_value(data_provider, value)
|
88
|
+
# The DataProvider.value_type is self recursive so further recursive check of collections is needed here
|
89
|
+
Types::TypeAsserter.assert_instance_of(nil, DataProvider.value_type, value) { "Value returned from #{data_provider.name}" }
|
99
90
|
end
|
100
91
|
end
|
101
92
|
end
|
@@ -21,9 +21,12 @@ class FunctionProvider
|
|
21
21
|
|
22
22
|
# @return [FunctionContext] the function context associated with this provider
|
23
23
|
def function_context(lookup_invocation, location)
|
24
|
+
@contexts[location] ||= create_function_context(lookup_invocation)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_function_context(lookup_invocation)
|
24
28
|
scope = lookup_invocation.scope
|
25
|
-
compiler
|
26
|
-
@contexts[location] ||= FunctionContext.new(compiler.environment.name, module_name, function(scope))
|
29
|
+
FunctionContext.new(EnvironmentContext.adapt(scope.compiler.environment), module_name, function(scope))
|
27
30
|
end
|
28
31
|
|
29
32
|
def module_name
|
@@ -22,19 +22,24 @@ class GlobalDataProvider < ConfiguredDataProvider
|
|
22
22
|
lookup_invocation.explainer)
|
23
23
|
end
|
24
24
|
|
25
|
+
merge = MergeStrategy.strategy(merge)
|
25
26
|
unless config.merge_strategy.is_a?(DefaultMergeStrategy)
|
26
|
-
if lookup_invocation.hiera_xxx_call?
|
27
|
-
# Merge strategy
|
28
|
-
merge = config.merge_strategy
|
29
|
-
lookup_invocation.set_hiera_v3_merge_behavior
|
30
|
-
elsif merge.is_a?(DefaultMergeStrategy)
|
31
|
-
# For all other calls, the strategy of the call overrides the strategy defined in the hiera config
|
27
|
+
if lookup_invocation.hiera_xxx_call? && merge.is_a?(HashMergeStrategy)
|
28
|
+
# Merge strategy defined in the hiera config only applies when the call stems from a hiera_hash call.
|
32
29
|
merge = config.merge_strategy
|
33
30
|
lookup_invocation.set_hiera_v3_merge_behavior
|
34
31
|
end
|
35
32
|
end
|
33
|
+
|
34
|
+
value = super(key, lookup_invocation, merge)
|
35
|
+
if lookup_invocation.hiera_xxx_call? && (merge.is_a?(HashMergeStrategy) || merge.is_a?(DeepMergeStrategy))
|
36
|
+
# hiera_hash calls should error when found values are not hashes
|
37
|
+
Types::TypeAsserter.assert_instance_of('value', Types::PHashType::DEFAULT, value)
|
38
|
+
end
|
39
|
+
value
|
40
|
+
else
|
41
|
+
super
|
36
42
|
end
|
37
|
-
super(key, lookup_invocation, merge)
|
38
43
|
end
|
39
44
|
|
40
45
|
protected
|
@@ -8,15 +8,19 @@ module Lookup
|
|
8
8
|
|
9
9
|
# @api private
|
10
10
|
class ScopeLookupCollectingInvocation < Invocation
|
11
|
-
attr_reader :scope_interpolations
|
12
|
-
|
13
11
|
def initialize(scope)
|
14
12
|
super(scope)
|
15
13
|
@scope_interpolations = []
|
16
14
|
end
|
17
15
|
|
18
|
-
def remember_scope_lookup(
|
19
|
-
@scope_interpolations <<
|
16
|
+
def remember_scope_lookup(key, root_key, segments, value)
|
17
|
+
@scope_interpolations << [key, root_key, segments, value] unless !value.nil? && key.start_with?('::')
|
18
|
+
end
|
19
|
+
|
20
|
+
def scope_interpolations
|
21
|
+
# Save extra checks by keeping the array unique with respect to the key (first entry)
|
22
|
+
@scope_interpolations.uniq! { |si| si[0] }
|
23
|
+
@scope_interpolations
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
@@ -48,6 +52,7 @@ class HieraConfig
|
|
48
52
|
KEY_V3_BACKEND = V3BackendFunctionProvider::TAG
|
49
53
|
KEY_V4_DATA_HASH = V4DataHashFunctionProvider::TAG
|
50
54
|
KEY_BACKEND = 'backend'.freeze
|
55
|
+
KEY_EXTENSION = 'extension'.freeze
|
51
56
|
|
52
57
|
FUNCTION_KEYS = [KEY_DATA_HASH, KEY_LOOKUP_KEY, KEY_DATA_DIG, KEY_V3_BACKEND]
|
53
58
|
ALL_FUNCTION_KEYS = FUNCTION_KEYS + [KEY_V4_DATA_HASH]
|
@@ -99,22 +104,28 @@ class HieraConfig
|
|
99
104
|
# Creates a new HieraConfig from the given _config_root_. This is where the 'hiera.yaml' is expected to be found
|
100
105
|
# and is also the base location used when resolving relative paths.
|
101
106
|
#
|
107
|
+
# @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults
|
102
108
|
# @param config_path [Pathname] Absolute path to the configuration file
|
103
109
|
# @return [LookupConfiguration] the configuration
|
104
|
-
def self.create(config_path)
|
110
|
+
def self.create(lookup_invocation, config_path)
|
105
111
|
if config_path.is_a?(Hash)
|
106
112
|
config_path = nil
|
107
113
|
loaded_config = config_path
|
108
114
|
else
|
109
115
|
config_root = config_path.parent
|
110
116
|
if config_path.exist?
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
117
|
+
env_context = EnvironmentContext.adapt(lookup_invocation.scope.compiler.environment)
|
118
|
+
loaded_config = env_context.cached_file_data(config_path) do |content|
|
119
|
+
parsed = YAML.load(content, config_path)
|
120
|
+
|
121
|
+
# For backward compatibility, we must treat an empty file, or a yaml that doesn't
|
122
|
+
# produce a Hash as Hiera version 3 default.
|
123
|
+
if parsed.is_a?(Hash)
|
124
|
+
parsed
|
125
|
+
else
|
126
|
+
Puppet.warning("#{config_path}: File exists but does not contain a valid YAML hash. Falling back to Hiera version 3 default config")
|
127
|
+
HieraConfigV3::DEFAULT_CONFIG_HASH
|
128
|
+
end
|
118
129
|
end
|
119
130
|
else
|
120
131
|
config_path = nil
|
@@ -169,15 +180,21 @@ class HieraConfig
|
|
169
180
|
end
|
170
181
|
|
171
182
|
def scope_interpolations_stable?(lookup_invocation)
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
183
|
+
if @scope_interpolations.empty?
|
184
|
+
true
|
185
|
+
else
|
186
|
+
scope = lookup_invocation.scope
|
187
|
+
lookup_invocation.without_explain do
|
188
|
+
@scope_interpolations.all? do |key, root_key, segments, old_value|
|
189
|
+
value = scope[root_key]
|
190
|
+
unless value.nil? || segments.empty?
|
191
|
+
found = '';
|
192
|
+
catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) }
|
193
|
+
value = found;
|
194
|
+
end
|
195
|
+
old_value.eql?(value)
|
196
|
+
end
|
179
197
|
end
|
180
|
-
old_value.eql?(value)
|
181
198
|
end
|
182
199
|
end
|
183
200
|
|
@@ -297,9 +314,19 @@ class HieraConfigV3 < HieraConfig
|
|
297
314
|
[@config[KEY_BACKENDS]].flatten.each do |backend|
|
298
315
|
raise Puppet::DataBinding::LookupError, "#{@config_path}: Backend '#{backend}' defined more than once" if data_providers.include?(backend)
|
299
316
|
original_paths = [@config[KEY_HIERARCHY]].flatten
|
300
|
-
backend_config = @config[backend]
|
301
|
-
|
302
|
-
|
317
|
+
backend_config = @config[backend]
|
318
|
+
if backend_config.nil?
|
319
|
+
backend_config = EMPTY_HASH
|
320
|
+
else
|
321
|
+
backend_config = interpolate(backend_config, lookup_invocation, false)
|
322
|
+
end
|
323
|
+
datadir = Pathname(backend_config[KEY_DATADIR] || interpolate(default_datadir, lookup_invocation, false))
|
324
|
+
ext = backend_config[KEY_EXTENSION]
|
325
|
+
if ext.nil?
|
326
|
+
ext = backend == 'hocon' ? '.conf' : ".#{backend}"
|
327
|
+
else
|
328
|
+
ext = ".#{ext}"
|
329
|
+
end
|
303
330
|
paths = resolve_paths(datadir, original_paths, lookup_invocation, @config_path.nil?, ext)
|
304
331
|
data_providers[backend] = case
|
305
332
|
when backend == 'json', backend == 'yaml'
|
@@ -510,6 +537,7 @@ class HieraConfigV5 < HieraConfig
|
|
510
537
|
end
|
511
538
|
|
512
539
|
entry_datadir = @config_root + (he[KEY_DATADIR] || datadir)
|
540
|
+
entry_datadir = Pathname(interpolate(entry_datadir.to_s, lookup_invocation, false))
|
513
541
|
location_key = LOCATION_KEYS.find { |key| he.include?(key) }
|
514
542
|
locations = case location_key
|
515
543
|
when KEY_PATHS
|