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.
@@ -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,8 +8,9 @@ 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
 
@@ -20,6 +21,7 @@ class Hiera
20
21
 
21
22
  next if data.empty?
22
23
  next unless data.include?(key)
24
+ found = true
23
25
 
24
26
  # Extra logging that we found the key. This can be outputted
25
27
  # multiple times if the resolution type is array or hash but that
@@ -32,22 +34,22 @@ class Hiera
32
34
  # the array
33
35
  #
34
36
  # for priority searches we break after the first found data item
35
- new_answer = Backend.parse_answer(data[key], scope)
36
- case resolution_type
37
+ new_answer = Backend.parse_answer(data[key], scope, {}, context)
38
+ case resolution_type.is_a?(Hash) ? :hash : resolution_type
37
39
  when :array
38
- 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
39
41
  answer ||= []
40
42
  answer << new_answer
41
43
  when :hash
42
- 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
43
45
  answer ||= {}
44
- answer = Backend.merge_answer(new_answer,answer)
46
+ answer = Backend.merge_answer(new_answer, answer, resolution_type)
45
47
  else
46
48
  answer = new_answer
47
49
  break
48
50
  end
49
51
  end
50
-
52
+ throw :no_such_key unless found
51
53
  return answer
52
54
  end
53
55
 
@@ -54,9 +54,7 @@ class Hiera::Config
54
54
  begin
55
55
  require "deep_merge"
56
56
  rescue LoadError
57
- Hiera.warn "Ignoring configured merge_behavior"
58
- Hiera.warn "Must have 'deep_merge' gem installed."
59
- @config[:merge_behavior] = :native
57
+ raise Hiera::Error, "Must have 'deep_merge' gem installed for the configured merge_behavior."
60
58
  end
61
59
  end
62
60
  end
@@ -1,30 +1,57 @@
1
1
  require 'hiera/backend'
2
2
  require 'hiera/recursive_guard'
3
3
 
4
+
5
+ class Hiera::InterpolationInvalidValue < StandardError; end
6
+
4
7
  class Hiera::Interpolate
5
8
  class << self
6
9
  INTERPOLATION = /%\{([^\}]*)\}/
7
- METHOD_INTERPOLATION = /%\{(scope|hiera)\(['"]([^"']*)["']\)\}/
10
+ METHOD_INTERPOLATION = /%\{(scope|hiera|literal|alias)\(['"]([^"']*)["']\)\}/
8
11
 
9
- def interpolate(data, scope, extra_data)
12
+ def interpolate(data, scope, extra_data, context)
10
13
  if data.is_a?(String)
11
14
  # Wrapping do_interpolation in a gsub block ensures we process
12
15
  # each interpolation site in isolation using separate recursion guards.
16
+ context ||= {}
17
+ new_context = context.clone
18
+ new_context[:recurse_guard] ||= Hiera::RecursiveGuard.new
13
19
  data.gsub(INTERPOLATION) do |match|
14
- do_interpolation(match, Hiera::RecursiveGuard.new, scope, extra_data)
20
+ interp_val = do_interpolation(match, scope, extra_data, new_context)
21
+
22
+ # Get interp method in case we are aliasing
23
+ if data.is_a?(String) && (match = data.match(INTERPOLATION))
24
+ interpolate_method, key = get_interpolation_method_and_key(data)
25
+ else
26
+ interpolate_method = nil
27
+ end
28
+
29
+ if ( (interpolate_method == :alias_interpolate) and (!interp_val.is_a?(String)) )
30
+ if data.match("^#{INTERPOLATION}$")
31
+ return interp_val
32
+ else
33
+ raise Hiera::InterpolationInvalidValue, "Cannot call alias in the string context"
34
+ end
35
+ else
36
+ interp_val
37
+ end
15
38
  end
16
39
  else
17
40
  data
18
41
  end
19
42
  end
20
43
 
21
- def do_interpolation(data, recurse_guard, scope, extra_data)
44
+ def do_interpolation(data, scope, extra_data, context)
22
45
  if data.is_a?(String) && (match = data.match(INTERPOLATION))
23
46
  interpolation_variable = match[1]
24
- recurse_guard.check(interpolation_variable) do
47
+ context[:recurse_guard].check(interpolation_variable) do
25
48
  interpolate_method, key = get_interpolation_method_and_key(data)
26
- interpolated_data = send(interpolate_method, data, key, scope, extra_data)
27
- do_interpolation(interpolated_data, recurse_guard, scope, extra_data)
49
+ interpolated_data = send(interpolate_method, data, key, scope, extra_data, context)
50
+
51
+ # Halt recursion if we encounter a literal.
52
+ return interpolated_data if interpolate_method == :literal_interpolate
53
+
54
+ do_interpolation(interpolated_data, scope, extra_data, context)
28
55
  end
29
56
  else
30
57
  data
@@ -37,6 +64,8 @@ class Hiera::Interpolate
37
64
  case match[1]
38
65
  when 'hiera' then [:hiera_interpolate, match[2]]
39
66
  when 'scope' then [:scope_interpolate, match[2]]
67
+ when 'literal' then [:literal_interpolate, match[2]]
68
+ when 'alias' then [:alias_interpolate, match[2]]
40
69
  end
41
70
  elsif (match = data.match(INTERPOLATION))
42
71
  [:scope_interpolate, match[1]]
@@ -44,19 +73,26 @@ class Hiera::Interpolate
44
73
  end
45
74
  private :get_interpolation_method_and_key
46
75
 
47
- def scope_interpolate(data, key, scope, extra_data)
48
- value = scope[key]
49
- if value.nil? || value == :undefined
50
- value = extra_data[key]
51
- end
52
-
53
- value
76
+ def scope_interpolate(data, key, scope, extra_data, context)
77
+ segments = key.split('.')
78
+ catch(:no_such_key) { return Hiera::Backend.qualified_lookup(segments, scope) }
79
+ catch(:no_such_key) { Hiera::Backend.qualified_lookup(segments, extra_data) }
54
80
  end
55
81
  private :scope_interpolate
56
82
 
57
- def hiera_interpolate(data, key, scope, extra_data)
58
- Hiera::Backend.lookup(key, nil, scope, nil, :priority)
83
+ def hiera_interpolate(data, key, scope, extra_data, context)
84
+ Hiera::Backend.lookup(key, nil, scope, context[:order_override], :priority, context)
59
85
  end
60
86
  private :hiera_interpolate
87
+
88
+ def literal_interpolate(data, key, scope, extra_data, context)
89
+ key
90
+ end
91
+ private :literal_interpolate
92
+
93
+ def alias_interpolate(data, key, scope, extra_data, context)
94
+ Hiera::Backend.lookup(key, nil, scope, context[:order_override], :priority, context)
95
+ end
96
+ private :alias_interpolate
61
97
  end
62
98
  end
@@ -9,7 +9,7 @@ class Hiera
9
9
 
10
10
  def microsoft_windows?
11
11
  return false unless file_alt_separator
12
-
12
+
13
13
  begin
14
14
  require 'win32/dir'
15
15
  true
@@ -21,17 +21,17 @@ class Hiera
21
21
 
22
22
  def config_dir
23
23
  if microsoft_windows?
24
- File.join(common_appdata, 'PuppetLabs', 'hiera', 'etc')
24
+ File.join(common_appdata, 'PuppetLabs', 'code')
25
25
  else
26
- '/etc'
26
+ '/etc/puppetlabs/code'
27
27
  end
28
28
  end
29
29
 
30
30
  def var_dir
31
31
  if microsoft_windows?
32
- File.join(common_appdata, 'PuppetLabs', 'hiera', 'var')
32
+ File.join(common_appdata, 'PuppetLabs', 'code', 'hieradata')
33
33
  else
34
- '/var/lib/hiera'
34
+ '/etc/puppetlabs/code/hieradata'
35
35
  end
36
36
  end
37
37
 
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
  class Hiera
10
- VERSION = "1.3.4"
10
+ VERSION = "2.0.0"
11
11
 
12
12
  ##
13
13
  # version is a public API method intended to always provide a fast and
@@ -9,6 +9,13 @@ require 'tmpdir'
9
9
  RSpec.configure do |config|
10
10
  config.mock_with :mocha
11
11
 
12
+ if Hiera::Util.microsoft_windows? && RUBY_VERSION =~ /^1\./
13
+ require 'win32console'
14
+ config.output_stream = $stdout
15
+ config.error_stream = $stderr
16
+ config.formatters.each { |f| f.instance_variable_set(:@output, $stdout) }
17
+ end
18
+
12
19
  config.after :suite do
13
20
  # Log the spec order to a file, but only if the LOG_SPEC_ORDER environment variable is
14
21
  # set. This should be enabled on Jenkins runs, as it can be used with Nick L.'s bisect
@@ -21,6 +28,11 @@ RSpec.configure do |config|
21
28
  end
22
29
  end
23
30
 
31
+ # So everyone else doesn't have to include this base constant.
32
+ module HieraSpec
33
+ FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), 'unit', 'fixtures') unless defined?(FIXTURE_DIR)
34
+ end
35
+
24
36
  # In ruby 1.8.5 Dir does not have mktmpdir defined, so this monkey patches
25
37
  # Dir to include the 1.8.7 definition of that method if it isn't already defined.
26
38
  # Method definition borrowed from ruby-1.8.7-p357/lib/ruby/1.8/tmpdir.rb
@@ -25,7 +25,7 @@ class Hiera
25
25
  Backend.expects(:datafile).with(:json, {}, "one", "json").returns(nil)
26
26
  Backend.expects(:datafile).with(:json, {}, "two", "json").returns(nil)
27
27
 
28
- @backend.lookup("key", {}, nil, :priority)
28
+ expect { @backend.lookup("key", {}, nil, :priority, nil) }.to throw_symbol(:no_such_key)
29
29
  end
30
30
 
31
31
  it "should retain the data types found in data files" do
@@ -35,9 +35,9 @@ class Hiera
35
35
 
36
36
  @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"stringval" => "string", "boolval" => true, "numericval" => 1}).times(3)
37
37
 
38
- @backend.lookup("stringval", {}, nil, :priority).should == "string"
39
- @backend.lookup("boolval", {}, nil, :priority).should == true
40
- @backend.lookup("numericval", {}, nil, :priority).should == 1
38
+ @backend.lookup("stringval", {}, nil, :priority, nil).should == "string"
39
+ @backend.lookup("boolval", {}, nil, :priority, nil).should == true
40
+ @backend.lookup("numericval", {}, nil, :priority, nil).should == 1
41
41
  end
42
42
 
43
43
  it "should pick data earliest source that has it for priority searches" do
@@ -49,12 +49,12 @@ class Hiera
49
49
  File.stubs(:exist?).with("/nonexisting/one.json").returns(true)
50
50
  @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
51
51
 
52
- @backend.lookup("key", scope, nil, :priority).should == "test_test"
52
+ @backend.lookup("key", scope, nil, :priority, nil).should == "test_test"
53
53
  end
54
54
 
55
55
  it "should build an array of all data sources for array searches" do
56
56
  Hiera::Backend.stubs(:empty_answer).returns([])
57
- Backend.stubs(:parse_answer).with('answer', {}).returns("answer")
57
+ Backend.stubs(:parse_answer).with('answer', {}, {}, anything).returns("answer")
58
58
  Backend.expects(:datafile).with(:json, {}, "one", "json").returns("/nonexisting/one.json")
59
59
  Backend.expects(:datafile).with(:json, {}, "two", "json").returns("/nonexisting/two.json")
60
60
 
@@ -66,18 +66,18 @@ class Hiera
66
66
  @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "answer"})
67
67
  @cache.expects(:read_file).with("/nonexisting/two.json", Hash).returns({"key" => "answer"})
68
68
 
69
- @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
69
+ @backend.lookup("key", {}, nil, :array, nil).should == ["answer", "answer"]
70
70
  end
71
71
 
72
72
  it "should parse the answer for scope variables" do
73
- Backend.stubs(:parse_answer).with('test_%{rspec}', {'rspec' => 'test'}).returns("test_test")
73
+ Backend.stubs(:parse_answer).with('test_%{rspec}', {'rspec' => 'test'}, {}, anything).returns("test_test")
74
74
  Backend.expects(:datasources).yields("one")
75
75
  Backend.expects(:datafile).with(:json, {"rspec" => "test"}, "one", "json").returns("/nonexisting/one.json")
76
76
 
77
77
  File.expects(:exist?).with("/nonexisting/one.json").returns(true)
78
78
  @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
79
79
 
80
- @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
80
+ @backend.lookup("key", {"rspec" => "test"}, nil, :priority, nil).should == "test_test"
81
81
  end
82
82
  end
83
83
  end
@@ -41,7 +41,7 @@ class Hiera
41
41
  Backend.expects(:datasourcefiles).with(:yaml, {}, "yaml", nil).yields(["one", "/nonexisting/one.yaml"])
42
42
  @cache.value = "---\nkey: answer"
43
43
 
44
- @backend.lookup("key", {}, nil, :priority).should == "answer"
44
+ @backend.lookup("key", {}, nil, :priority, nil).should == "answer"
45
45
  end
46
46
 
47
47
  describe "handling unexpected YAML values" do
@@ -49,19 +49,24 @@ class Hiera
49
49
  Backend.expects(:datasourcefiles).with(:yaml, {}, "yaml", nil).yields(["one", "/nonexisting/one.yaml"])
50
50
  end
51
51
 
52
- it "returns nil when the YAML value is nil" do
52
+ it "throws :no_such_key when key is missing in YAML" do
53
53
  @cache.value = "---\n"
54
- @backend.lookup("key", {}, nil, :priority).should be_nil
54
+ expect { @backend.lookup("key", {}, nil, :priority, nil) }.to throw_symbol(:no_such_key)
55
+ end
56
+
57
+ it "returns nil when the YAML value is nil" do
58
+ @cache.value = "key: ~\n"
59
+ @backend.lookup("key", {}, nil, :priority, nil).should be_nil
55
60
  end
56
61
 
57
- it "returns nil when the YAML file is false" do
62
+ it "throws :no_such_key when the YAML file is false" do
58
63
  @cache.value = ""
59
- @backend.lookup("key", {}, nil, :priority).should be_nil
64
+ expect { @backend.lookup("key", {}, nil, :priority, nil) }.to throw_symbol(:no_such_key)
60
65
  end
61
66
 
62
67
  it "raises a TypeError when the YAML value is not a hash" do
63
68
  @cache.value = "---\n[one, two, three]"
64
- expect { @backend.lookup("key", {}, nil, :priority) }.to raise_error(TypeError)
69
+ expect { @backend.lookup("key", {}, nil, :priority, nil) }.to raise_error(TypeError)
65
70
  end
66
71
  end
67
72
 
@@ -70,7 +75,7 @@ class Hiera
70
75
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"answer"})
71
76
  @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>"answer"})
72
77
 
73
- @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
78
+ @backend.lookup("key", {}, nil, :array, nil).should == ["answer", "answer"]
74
79
  end
75
80
 
76
81
  it "should ignore empty hash of data sources for hash searches" do
@@ -79,7 +84,7 @@ class Hiera
79
84
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({})
80
85
  @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
81
86
 
82
- @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer"}
87
+ @backend.lookup("key", {}, nil, :hash, nil).should == {"a" => "answer"}
83
88
  end
84
89
 
85
90
  it "should build a merged hash of data sources for hash searches" do
@@ -88,7 +93,7 @@ class Hiera
88
93
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
89
94
  @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"b"=>"answer", "a"=>"wrong"}})
90
95
 
91
- @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer", "b" => "answer"}
96
+ @backend.lookup("key", {}, nil, :hash, nil).should == {"a" => "answer", "b" => "answer"}
92
97
  end
93
98
 
94
99
  it "should fail when trying to << a Hash" do
@@ -97,7 +102,7 @@ class Hiera
97
102
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>["a", "answer"]})
98
103
  @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
99
104
 
100
- expect {@backend.lookup("key", {}, nil, :array)}.to raise_error(Exception, "Hiera type mismatch: expected Array and got Hash")
105
+ expect {@backend.lookup("key", {}, nil, :array, nil)}.to raise_error(Exception, "Hiera type mismatch for key 'key': expected Array and got Hash")
101
106
  end
102
107
 
103
108
  it "should fail when trying to merge an Array" do
@@ -106,7 +111,7 @@ class Hiera
106
111
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
107
112
  @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>["a", "wrong"]})
108
113
 
109
- expect { @backend.lookup("key", {}, nil, :hash) }.to raise_error(Exception, "Hiera type mismatch: expected Hash and got Array")
114
+ expect { @backend.lookup("key", {}, nil, :hash, nil) }.to raise_error(Exception, "Hiera type mismatch for key 'key': expected Hash and got Array")
110
115
  end
111
116
 
112
117
  it "should parse the answer for scope variables" do
@@ -114,7 +119,7 @@ class Hiera
114
119
 
115
120
  @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"test_%{rspec}"})
116
121
 
117
- @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
122
+ @backend.lookup("key", {"rspec" => "test"}, nil, :priority, nil).should == "test_test"
118
123
  end
119
124
 
120
125
  it "should retain datatypes found in yaml files" do
@@ -123,9 +128,9 @@ class Hiera
123
128
 
124
129
  @cache.value = "---\nstringval: 'string'\nboolval: true\nnumericval: 1"
125
130
 
126
- @backend.lookup("stringval", {}, nil, :priority).should == "string"
127
- @backend.lookup("boolval", {}, nil, :priority).should == true
128
- @backend.lookup("numericval", {}, nil, :priority).should == 1
131
+ @backend.lookup("stringval", {}, nil, :priority, nil).should == "string"
132
+ @backend.lookup("boolval", {}, nil, :priority, nil).should == true
133
+ @backend.lookup("numericval", {}, nil, :priority, nil).should == 1
129
134
  end
130
135
  end
131
136
  end
@@ -2,6 +2,14 @@ require 'spec_helper'
2
2
  require 'hiera/util'
3
3
 
4
4
  class Hiera
5
+ module Backend
6
+ class Backend1x_backend
7
+ def lookup(key, scope, order_override, resolution_type)
8
+ ["a", "b"]
9
+ end
10
+ end
11
+ end
12
+
5
13
  describe Backend do
6
14
  describe "#datadir" do
7
15
  it "interpolates any values in the configured value" do
@@ -84,7 +92,7 @@ class Hiera
84
92
  end
85
93
 
86
94
  it "parses the names of the hierarchy levels using the given scope" do
87
- Backend.expects(:parse_string).with("common", {:rspec => :tests})
95
+ Backend.expects(:parse_string).with("common", {:rspec => :tests}, {}, {:order_override => nil})
88
96
  Backend.datasources({:rspec => :tests}) { }
89
97
  end
90
98
 
@@ -181,17 +189,6 @@ class Hiera
181
189
  "test_%{scope('rspec')}_test" => "test__test"
182
190
  }
183
191
 
184
- @interprets_undefined_in_scope_tests.each do |input, expected|
185
- it "interprets :undefined in scope as a non-value" do
186
- Backend.parse_string(input, {"rspec" => :undefined}).should == expected
187
- end
188
- end
189
-
190
- it "uses the value from extra_data when scope is :undefined" do
191
- input = "test_%{rspec}_test"
192
- Backend.parse_string(input, {"rspec" => :undefined}, { "rspec" => "extra" }).should == "test_extra_test"
193
- end
194
-
195
192
  @exact_lookup_tests = {
196
193
  "test_%{::rspec::data}_test" => "test_value_test",
197
194
  "test_%{scope('::rspec::data')}_test" => "test_value_test"
@@ -264,10 +261,21 @@ class Hiera
264
261
  scope = {}
265
262
  Config.load({:yaml => {:datadir => "/tmp"}})
266
263
  Config.load_backends
267
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("key1", scope, nil, :priority).returns("answer")
264
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("key1", scope, nil, :priority, instance_of(Hash)).returns("answer")
268
265
 
269
266
  Backend.parse_string(input, scope).should == "answer"
270
267
  end
268
+
269
+ it "interpolation passes the order_override back into the backend" do
270
+ Backend.expects(:lookup).with("lookup::key", nil, {}, "order_override_datasource", :priority, instance_of(Hash))
271
+ Backend.parse_string("%{hiera('lookup::key')}", {}, {}, {:order_override => "order_override_datasource"})
272
+ end
273
+
274
+ it "replaces literal interpolations with their argument" do
275
+ scope = {}
276
+ input = "%{literal('%')}{rspec::data}"
277
+ Backend.parse_string(input, scope).should == "%{rspec::data}"
278
+ end
271
279
  end
272
280
 
273
281
  describe "#parse_answer" do
@@ -306,16 +314,47 @@ class Hiera
306
314
  scope = {}
307
315
  Config.load({:yaml => {:datadir => "/tmp"}})
308
316
  Config.load_backends
309
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
317
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test")
310
318
  Backend.parse_answer(input, scope).should == "test_test_test"
311
319
  end
312
320
 
321
+ it "interpolates alias lookups with non-string types" do
322
+ input = "%{alias('rspec')}"
323
+ scope = {}
324
+ Config.load({:yaml => {:datadir => "/tmp"}})
325
+ Config.load_backends
326
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test'])
327
+ Backend.parse_answer(input, scope).should == ['test', 'test']
328
+ end
329
+
330
+ it 'fails if alias interpolation is attempted in a string context with a prefix' do
331
+ input = "stuff_before%{alias('rspec')}"
332
+ scope = {}
333
+ Config.load({:yaml => {:datadir => "/tmp"}})
334
+ Config.load_backends
335
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test'])
336
+ expect do
337
+ Backend.parse_answer(input, scope).should == ['test', 'test']
338
+ end.to raise_error(Hiera::InterpolationInvalidValue, 'Cannot call alias in the string context')
339
+ end
340
+
341
+ it 'fails if alias interpolation is attempted in a string context with a postfix' do
342
+ input = "%{alias('rspec')}_stiff after"
343
+ scope = {}
344
+ Config.load({:yaml => {:datadir => "/tmp"}})
345
+ Config.load_backends
346
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test'])
347
+ expect do
348
+ Backend.parse_answer(input, scope).should == ['test', 'test']
349
+ end.to raise_error(Hiera::InterpolationInvalidValue, 'Cannot call alias in the string context')
350
+ end
351
+
313
352
  it "interpolates hiera lookups in each string in an array" do
314
353
  input = ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test", ["test_%{hiera('rspec')}_test"]]
315
354
  scope = {}
316
355
  Config.load({:yaml => {:datadir => "/tmp"}})
317
356
  Config.load_backends
318
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
357
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test")
319
358
  Backend.parse_answer(input, scope).should == ["test_test_test", "test_test_test", ["test_test_test"]]
320
359
  end
321
360
 
@@ -324,7 +363,7 @@ class Hiera
324
363
  scope = {}
325
364
  Config.load({:yaml => {:datadir => "/tmp"}})
326
365
  Config.load_backends
327
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
366
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test")
328
367
  Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
329
368
  end
330
369
 
@@ -333,7 +372,7 @@ class Hiera
333
372
  scope = {}
334
373
  Config.load({:yaml => {:datadir => "/tmp"}})
335
374
  Config.load_backends
336
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
375
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("foo")
337
376
  Backend.parse_answer(input, scope).should == {"foo"=>"test"}
338
377
  end
339
378
 
@@ -342,7 +381,7 @@ class Hiera
342
381
  scope = {}
343
382
  Config.load({:yaml => {:datadir => "/tmp"}})
344
383
  Config.load_backends
345
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
384
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("foo")
346
385
  Backend.parse_answer(input, scope).should == {"topkey"=>{"foo" => "test"}}
347
386
  end
348
387
 
@@ -351,7 +390,7 @@ class Hiera
351
390
  scope = {}
352
391
  Config.load({:yaml => {:datadir => "/tmp"}})
353
392
  Config.load_backends
354
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
393
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test")
355
394
  Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
356
395
  end
357
396
 
@@ -360,7 +399,7 @@ class Hiera
360
399
  scope = {"rspec2" => "scope_rspec"}
361
400
  Config.load({:yaml => {:datadir => "/tmp"}})
362
401
  Config.load_backends
363
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
402
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("hiera_rspec")
364
403
  Backend.parse_answer(input, scope).should == {"foo"=>"test_hiera_rspec_test", "bar"=>"test_scope_rspec_test"}
365
404
  end
366
405
 
@@ -369,7 +408,7 @@ class Hiera
369
408
  scope = {"rspec" => "scope_rspec"}
370
409
  Config.load({:yaml => {:datadir => "/tmp"}})
371
410
  Config.load_backends
372
- Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
411
+ Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("hiera_rspec")
373
412
  Backend.parse_answer(input, scope).should == "test_hiera_rspec_test_scope_rspec"
374
413
  end
375
414
 
@@ -431,7 +470,7 @@ class Hiera
431
470
  Config.load({:yaml => {:datadir => "/tmp"}})
432
471
  Config.load_backends
433
472
 
434
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil).returns("answer")
473
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil, instance_of(Hash)).returns("answer")
435
474
 
436
475
  Backend.lookup("key", "default", {}, nil, nil).should == "answer"
437
476
  end
@@ -440,9 +479,9 @@ class Hiera
440
479
  Config.load({:yaml => {:datadir => "/tmp"}})
441
480
  Config.load_backends
442
481
 
443
- Backend::Yaml_backend.any_instance.expects(:lookup).with("stringval", {}, nil, nil).returns("string")
444
- Backend::Yaml_backend.any_instance.expects(:lookup).with("boolval", {}, nil, nil).returns(false)
445
- Backend::Yaml_backend.any_instance.expects(:lookup).with("numericval", {}, nil, nil).returns(1)
482
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("stringval", {}, nil, nil, instance_of(Hash)).returns("string")
483
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("boolval", {}, nil, nil, instance_of(Hash)).returns(false)
484
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("numericval", {}, nil, nil, instance_of(Hash)).returns(1)
446
485
 
447
486
  Backend.lookup("stringval", "default", {}, nil, nil).should == "string"
448
487
  Backend.lookup("boolval", "default", {}, nil, nil).should == false
@@ -524,7 +563,7 @@ class Hiera
524
563
  Config.load_backends
525
564
 
526
565
  Backend.expects(:resolve_answer).with("test_test", :priority).returns("parsed")
527
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, :priority).returns("test_test")
566
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, :priority, instance_of(Hash)).returns("test_test")
528
567
 
529
568
  Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, :priority).should == "parsed"
530
569
  end
@@ -533,31 +572,129 @@ class Hiera
533
572
  Config.load({:yaml => {:datadir => "/tmp"}})
534
573
  Config.load_backends
535
574
 
536
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil)
575
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil, instance_of(Hash)).throws(:no_such_key)
537
576
 
538
577
  Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil).should == "test_test"
539
578
  end
540
579
 
580
+ it "returns nil instead of the default when key is found with a nil value" do
581
+ Config.load({:yaml => {:datadir => "/tmp"}})
582
+ Config.load_backends
583
+
584
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil, instance_of(Hash)).returns(nil)
585
+
586
+ Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil).should == nil
587
+ end
588
+
541
589
  it "keeps string default data as a string" do
542
590
  Config.load({:yaml => {:datadir => "/tmp"}})
543
591
  Config.load_backends
544
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil)
592
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil, instance_of(Hash)).throws(:no_such_key)
545
593
  Backend.lookup("key", "test", {}, nil, nil).should == "test"
546
594
  end
547
595
 
548
596
  it "keeps array default data as an array" do
549
597
  Config.load({:yaml => {:datadir => "/tmp"}})
550
598
  Config.load_backends
551
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :array)
599
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :array, instance_of(Hash)).throws(:no_such_key)
552
600
  Backend.lookup("key", ["test"], {}, nil, :array).should == ["test"]
553
601
  end
554
602
 
555
603
  it "keeps hash default data as a hash" do
556
604
  Config.load({:yaml => {:datadir => "/tmp"}})
557
605
  Config.load_backends
558
- Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :hash)
606
+ Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :hash, instance_of(Hash)).throws(:no_such_key)
559
607
  Backend.lookup("key", {"test" => "value"}, {}, nil, :hash).should == {"test" => "value"}
560
608
  end
609
+
610
+ it 'can use qualified key to lookup value in hash' do
611
+ Config.load({:yaml => {:datadir => '/tmp'}})
612
+ Config.load_backends
613
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns({ 'test' => 'value'})
614
+ Backend.lookup('key.test', 'dflt', {}, nil, nil).should == 'value'
615
+ end
616
+
617
+ it 'can use qualified key to lookup value in array' do
618
+ Config.load({:yaml => {:datadir => '/tmp'}})
619
+ Config.load_backends
620
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns([ 'first', 'second'])
621
+ Backend.lookup('key.1', 'dflt', {}, nil, nil).should == 'second'
622
+ end
623
+
624
+ it 'will fail when qualified key is partially found but not expected hash' do
625
+ Config.load({:yaml => {:datadir => '/tmp'}})
626
+ Config.load_backends
627
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(['value 1', 'value 2'])
628
+ expect do
629
+ Backend.lookup('key.test', 'dflt', {}, nil, nil)
630
+ end.to raise_error(Exception, /^Hiera type mismatch:/)
631
+ end
632
+
633
+ it 'will fail when qualified key used with resolution_type :hash' do
634
+ expect do
635
+ Backend.lookup('key.test', 'dflt', {}, nil, :hash)
636
+ end.to raise_error(ArgumentError, /^Resolution type :hash is illegal/)
637
+ end
638
+
639
+ it 'will fail when qualified key used with resolution_type :array' do
640
+ expect do
641
+ Backend.lookup('key.test', 'dflt', {}, nil, :array)
642
+ end.to raise_error(ArgumentError, /^Resolution type :array is illegal/)
643
+ end
644
+
645
+ it 'will succeed when qualified key used with resolution_type :priority' do
646
+ Config.load({:yaml => {:datadir => '/tmp'}})
647
+ Config.load_backends
648
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, :priority, instance_of(Hash)).returns({ 'test' => 'value'})
649
+ Backend.lookup('key.test', 'dflt', {}, nil, :priority).should == 'value'
650
+ end
651
+
652
+ it 'will fail when qualified key is partially found but not expected array' do
653
+ Config.load({:yaml => {:datadir => '/tmp'}})
654
+ Config.load_backends
655
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns({ 'test' => 'value'})
656
+ expect do
657
+ Backend.lookup('key.2', 'dflt', {}, nil, nil)
658
+ end.to raise_error(Exception, /^Hiera type mismatch:/)
659
+ end
660
+
661
+ it 'will not fail when qualified key is partially not found' do
662
+ Config.load({:yaml => {:datadir => '/tmp'}})
663
+ Config.load_backends
664
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(nil)
665
+ Backend.lookup('key.test', 'dflt', {}, nil, nil).should == 'dflt'
666
+ end
667
+
668
+ it 'will not fail when qualified key is array index out of bounds' do
669
+ Config.load({:yaml => {:datadir => '/tmp'}})
670
+ Config.load_backends
671
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(['value 1', 'value 2'])
672
+ Backend.lookup('key.33', 'dflt', {}, nil, nil).should == 'dflt'
673
+ end
674
+
675
+ it 'can use qualified key in interpolation to lookup value in hash' do
676
+ Config.load({:yaml => {:datadir => '/tmp'}})
677
+ Config.load_backends
678
+ Hiera::Backend.stubs(:datasourcefiles).yields('foo', 'bar')
679
+ Hiera::Filecache.any_instance.expects(:read_file).at_most(2).returns({'key' => '%{hiera(\'some.subkey\')}', 'some' => { 'subkey' => 'value' }})
680
+ Backend.lookup('key', 'dflt', {}, nil, nil).should == 'value'
681
+ end
682
+
683
+ it 'can use qualified key in interpolated default and scope' do
684
+ Config.load({:yaml => {:datadir => '/tmp'}})
685
+ Config.load_backends
686
+ scope = { 'some' => { 'test' => 'value'}}
687
+ Backend::Yaml_backend.any_instance.expects(:lookup).with('key', scope, nil, nil, instance_of(Hash))
688
+ Backend.lookup('key.notfound', '%{some.test}', scope, nil, nil).should == 'value'
689
+ end
690
+
691
+ it "handles older backend with 4 argument lookup" do
692
+ Config.load({})
693
+ Config.instance_variable_set("@config", {:backends => ["Backend1x"]})
694
+
695
+ Hiera.expects(:debug).at_least_once.with(regexp_matches /Using Hiera 1.x backend/)
696
+ Backend.lookup("key", {}, {"rspec" => "test"}, nil, :priority).should == ["a", "b"]
697
+ end
561
698
  end
562
699
 
563
700
  describe '#merge_answer' do
@@ -575,13 +712,30 @@ class Hiera
575
712
 
576
713
  it "uses deep_merge! when configured with :merge_behavior => :deeper" do
577
714
  Config.load({:merge_behavior => :deeper})
578
- Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"})
715
+ Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"})
579
716
  Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
580
717
  end
581
718
 
582
719
  it "uses deep_merge when configured with :merge_behavior => :deep" do
583
720
  Config.load({:merge_behavior => :deep})
584
- Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"})
721
+ Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"})
722
+ Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
723
+ end
724
+
725
+ it "disregards configuration when 'merge' parameter is given as a Hash" do
726
+ Config.load({:merge_behavior => :deep})
727
+ Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"})
728
+ Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}, {:behavior => 'deeper' }).should == {"a" => "answer", "b" => "bnswer"}
729
+ end
730
+
731
+ it "propagates deep merge options when given Hash 'merge' parameter" do
732
+ Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, { :knockout_prefix => '-' }).returns({"a" => "answer", "b" => "bnswer"})
733
+ Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}, {:behavior => 'deeper', :knockout_prefix => '-'}).should == {"a" => "answer", "b" => "bnswer"}
734
+ end
735
+
736
+ it "passes Config[:deep_merge_options] into calls to deep_merge" do
737
+ Config.load({:merge_behavior => :deep, :deep_merge_options => { :knockout_prefix => '-' } })
738
+ Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}, {:knockout_prefix => '-'}).returns({"a" => "answer", "b" => "bnswer"})
585
739
  Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
586
740
  end
587
741
  end