hiera 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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