puppet 4.9.4 → 4.10.0

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.

Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +6 -0
  3. data/ext/project_data.yaml +2 -2
  4. data/lib/hiera/puppet_function.rb +1 -1
  5. data/lib/puppet.rb +1 -0
  6. data/lib/puppet/application.rb +14 -0
  7. data/lib/puppet/application/inspect.rb +3 -0
  8. data/lib/puppet/defaults.rb +12 -2
  9. data/lib/puppet/etc.rb +125 -0
  10. data/lib/puppet/face/help.rb +1 -1
  11. data/lib/puppet/functions.rb +49 -4
  12. data/lib/puppet/functions/eyaml_lookup_key.rb +12 -3
  13. data/lib/puppet/functions/hocon_data.rb +9 -0
  14. data/lib/puppet/functions/json_data.rb +9 -0
  15. data/lib/puppet/functions/yaml_data.rb +9 -0
  16. data/lib/puppet/indirector/file_bucket_file/file.rb +69 -22
  17. data/lib/puppet/indirector/key/file.rb +2 -1
  18. data/lib/puppet/indirector/ssl_file.rb +24 -3
  19. data/lib/puppet/module.rb +28 -22
  20. data/lib/puppet/network/http/compression.rb +2 -1
  21. data/lib/puppet/parser/compiler.rb +15 -38
  22. data/lib/puppet/parser/functions/hiera.rb +1 -1
  23. data/lib/puppet/parser/functions/hiera_array.rb +1 -1
  24. data/lib/puppet/parser/functions/hiera_hash.rb +1 -1
  25. data/lib/puppet/parser/functions/hiera_include.rb +1 -1
  26. data/lib/puppet/parser/scope.rb +59 -17
  27. data/lib/puppet/pops/evaluator/callable_signature.rb +7 -0
  28. data/lib/puppet/pops/functions/dispatch.rb +18 -5
  29. data/lib/puppet/pops/functions/dispatcher.rb +7 -13
  30. data/lib/puppet/pops/issue_reporter.rb +1 -1
  31. data/lib/puppet/pops/issues.rb +84 -0
  32. data/lib/puppet/pops/loader/base_loader.rb +13 -5
  33. data/lib/puppet/pops/lookup/configured_data_provider.rb +8 -2
  34. data/lib/puppet/pops/lookup/data_dig_function_provider.rb +109 -19
  35. data/lib/puppet/pops/lookup/data_hash_function_provider.rb +19 -4
  36. data/lib/puppet/pops/lookup/data_provider.rb +43 -29
  37. data/lib/puppet/pops/lookup/environment_data_provider.rb +1 -1
  38. data/lib/puppet/pops/lookup/explainer.rb +1 -0
  39. data/lib/puppet/pops/lookup/function_provider.rb +36 -11
  40. data/lib/puppet/pops/lookup/global_data_provider.rb +18 -5
  41. data/lib/puppet/pops/lookup/hiera_config.rb +203 -84
  42. data/lib/puppet/pops/lookup/interpolation.rb +21 -6
  43. data/lib/puppet/pops/lookup/invocation.rb +14 -9
  44. data/lib/puppet/pops/lookup/location_resolver.rb +27 -0
  45. data/lib/puppet/pops/lookup/lookup_adapter.rb +59 -6
  46. data/lib/puppet/pops/lookup/lookup_key_function_provider.rb +9 -77
  47. data/lib/puppet/pops/lookup/module_data_provider.rb +27 -4
  48. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  49. data/lib/puppet/pops/pcore.rb +3 -3
  50. data/lib/puppet/pops/types/p_object_type.rb +4 -6
  51. data/lib/puppet/pops/types/ruby_generator.rb +2 -2
  52. data/lib/puppet/pops/types/type_asserter.rb +3 -3
  53. data/lib/puppet/pops/types/type_mismatch_describer.rb +25 -7
  54. data/lib/puppet/pops/types/types.rb +20 -29
  55. data/lib/puppet/provider/exec.rb +4 -2
  56. data/lib/puppet/provider/nameservice.rb +8 -8
  57. data/lib/puppet/provider/selmodule/semodule.rb +20 -16
  58. data/lib/puppet/provider/service/src.rb +39 -39
  59. data/lib/puppet/provider/service/systemd.rb +1 -1
  60. data/lib/puppet/provider/user/aix.rb +7 -2
  61. data/lib/puppet/settings.rb +30 -17
  62. data/lib/puppet/ssl/base.rb +14 -1
  63. data/lib/puppet/ssl/certificate_authority.rb +4 -2
  64. data/lib/puppet/ssl/configuration.rb +4 -1
  65. data/lib/puppet/ssl/inventory.rb +10 -3
  66. data/lib/puppet/ssl/key.rb +7 -3
  67. data/lib/puppet/test/test_helper.rb +3 -0
  68. data/lib/puppet/type.rb +13 -1
  69. data/lib/puppet/type/exec.rb +16 -1
  70. data/lib/puppet/type/group.rb +17 -11
  71. data/lib/puppet/type/user.rb +3 -1
  72. data/lib/puppet/util.rb +1 -0
  73. data/lib/puppet/util/character_encoding.rb +95 -0
  74. data/lib/puppet/util/execution.rb +9 -6
  75. data/lib/puppet/util/reference.rb +4 -2
  76. data/lib/puppet/util/windows/file.rb +5 -1
  77. data/lib/puppet/version.rb +6 -2
  78. data/locales/config.yaml +1 -1
  79. data/locales/puppet.pot +18 -4
  80. data/spec/integration/ssl/autosign_spec.rb +18 -3
  81. data/spec/integration/ssl/key_spec.rb +104 -0
  82. data/spec/integration/type/user_spec.rb +13 -6
  83. data/spec/spec_helper.rb +7 -0
  84. data/spec/unit/application/inspect_spec.rb +9 -2
  85. data/spec/unit/data_providers/function_data_provider_spec.rb +2 -2
  86. data/spec/unit/etc_spec.rb +234 -0
  87. data/spec/unit/face/certificate_spec.rb +10 -2
  88. data/spec/unit/functions/dig_spec.rb +1 -1
  89. data/spec/unit/functions/hiera_spec.rb +40 -1
  90. data/spec/unit/functions/lookup_fixture_spec.rb +10 -10
  91. data/spec/unit/functions/lookup_spec.rb +1217 -357
  92. data/spec/unit/functions4_spec.rb +37 -1
  93. data/spec/unit/indirector/file_bucket_file/file_spec.rb +33 -2
  94. data/spec/unit/indirector/key/file_spec.rb +1 -1
  95. data/spec/unit/indirector/ssl_file_spec.rb +3 -3
  96. data/spec/unit/module_spec.rb +52 -59
  97. data/spec/unit/network/http/compression_spec.rb +39 -8
  98. data/spec/unit/parser/compiler_spec.rb +14 -0
  99. data/spec/unit/pops/loaders/loaders_spec.rb +21 -3
  100. data/spec/unit/pops/loaders/module_loaders_spec.rb +61 -0
  101. data/spec/unit/pops/lookup/context_spec.rb +56 -8
  102. data/spec/unit/pops/lookup/lookup_spec.rb +32 -1
  103. data/spec/unit/pops/parser/lexer2_spec.rb +8 -0
  104. data/spec/unit/pops/types/ruby_generator_spec.rb +48 -0
  105. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +12 -3
  106. data/spec/unit/pops/types/types_spec.rb +6 -7
  107. data/spec/unit/provider/nameservice_spec.rb +12 -12
  108. data/spec/unit/provider/package/pkg_spec.rb +2 -0
  109. data/spec/unit/provider/service/src_spec.rb +5 -0
  110. data/spec/unit/ssl/base_spec.rb +9 -0
  111. data/spec/unit/ssl/certificate_authority_spec.rb +2 -2
  112. data/spec/unit/ssl/certificate_request_attributes_spec.rb +6 -0
  113. data/spec/unit/ssl/certificate_request_spec.rb +1 -1
  114. data/spec/unit/ssl/certificate_spec.rb +1 -1
  115. data/spec/unit/ssl/configuration_spec.rb +11 -2
  116. data/spec/unit/ssl/inventory_spec.rb +27 -3
  117. data/spec/unit/ssl/key_spec.rb +7 -7
  118. data/spec/unit/type/exec_spec.rb +41 -4
  119. data/spec/unit/type/file_spec.rb +4 -1
  120. data/spec/unit/util/character_encoding_spec.rb +88 -0
  121. data/spec/unit/util/execution_spec.rb +12 -0
  122. data/spec/unit/version_spec.rb +4 -0
  123. metadata +3803 -3808
  124. data/tasks/i18n.rake +0 -20
@@ -9,6 +9,13 @@ describe Puppet::Face[:certificate, '0.0.1'] do
9
9
 
10
10
  let(:ca) { Puppet::SSL::CertificateAuthority.instance }
11
11
 
12
+ # different UTF-8 widths
13
+ # 1-byte A
14
+ # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191
15
+ # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160
16
+ # 4-byte ܎ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142
17
+ let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ܎
18
+
12
19
  before :each do
13
20
  Puppet[:confdir] = tmpdir('conf')
14
21
  Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true
@@ -84,9 +91,10 @@ describe Puppet::Face[:certificate, '0.0.1'] do
84
91
  it "should add the provided dns_alt_names if they are specified" do
85
92
  Puppet[:dns_alt_names] = 'from,the,config'
86
93
 
87
- subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names'))
94
+ subject.generate(hostname, options.merge(:dns_alt_names => "explicit,alt,#{mixed_utf8}"))
88
95
 
89
- expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}]
96
+ # CSRs will return subject_alt_names as BINARY strings
97
+ expected = %W[DNS:explicit DNS:alt DNS:#{mixed_utf8.force_encoding(Encoding::BINARY)} DNS:#{hostname}]
90
98
 
91
99
  expect(csr.subject_alt_names).to match_array(expected)
92
100
  end
@@ -50,7 +50,7 @@ describe 'the dig function' do
50
50
  end
51
51
 
52
52
  it 'errors if not given a non Collection as the starting point' do
53
- expect { compile_to_catalog(<<-SOURCE)}.to raise_error(/'dig' parameter 'data' expects a Collection value, got String/)
53
+ expect { compile_to_catalog(<<-SOURCE)}.to raise_error(/'dig' parameter 'data' expects a value of type Undef or Collection, got String/)
54
54
  "hello".dig(1, yes, 2)
55
55
  SOURCE
56
56
  end
@@ -66,6 +66,8 @@ describe 'when calling' do
66
66
  name: postgres
67
67
  uid: 500
68
68
  gid: 500
69
+ groups:
70
+ db: 520
69
71
  b:
70
72
  b1: first b1
71
73
  b2: first b2
@@ -74,6 +76,19 @@ describe 'when calling' do
74
76
  - mod::bar
75
77
  - mod::baz
76
78
  empty_array: []
79
+ nested_array:
80
+ first:
81
+ - 10
82
+ - 11
83
+ second:
84
+ - 21
85
+ - 22
86
+ dotted.key:
87
+ a: dotted.key a
88
+ b: dotted.key b
89
+ dotted.array:
90
+ - a
91
+ - b
77
92
  YAML
78
93
  'second.yaml' => <<-YAML.unindent,
79
94
  ---
@@ -193,6 +208,14 @@ describe 'when calling' do
193
208
  expect(func('a')).to eql('first a')
194
209
  end
195
210
 
211
+ it 'should allow lookup with quoted dotted key' do
212
+ expect(func("'dotted.key'")).to eql({'a' => 'dotted.key a', 'b' => 'dotted.key b'})
213
+ end
214
+
215
+ it 'should allow lookup with dotted key' do
216
+ expect(func('database_user.groups.db')).to eql(520)
217
+ end
218
+
196
219
  it 'should not find data in module' do
197
220
  expect(func('mod::c', 'default mod::c')).to eql('default mod::c')
198
221
  end
@@ -292,6 +315,14 @@ describe 'when calling' do
292
315
  expect(func('fbb', {'fbb' => 'foo_result'})).to eql(%w[mod::foo mod::bar mod::baz])
293
316
  end
294
317
 
318
+ it 'should allow lookup with quoted dotted key' do
319
+ expect(func("'dotted.array'")).to eql(['a', 'b'])
320
+ end
321
+
322
+ it 'should fail lookup with dotted key' do
323
+ expect{ func('nested_array.0.first') }.to raise_error(/Resolution type :array is illegal when accessing values using dotted keys. Offending key was 'nested_array.0.first'/)
324
+ end
325
+
295
326
  it 'should use default block' do
296
327
  expect(func('foo') { |k| ['key', k] }).to eql(%w[key foo])
297
328
  end
@@ -313,7 +344,15 @@ describe 'when calling' do
313
344
  end
314
345
 
315
346
  it 'should lookup and return a hash' do
316
- expect(func('database_user')).to eql({ 'name' => 'postgres', 'uid' => 500, 'gid' => 500})
347
+ expect(func('database_user')).to eql({ 'name' => 'postgres', 'uid' => 500, 'gid' => 500, 'groups' => { 'db' => 520 }})
348
+ end
349
+
350
+ it 'should allow lookup with quoted dotted key' do
351
+ expect(func("'dotted.key'")).to eql({'a' => 'dotted.key a', 'b' => 'dotted.key b'})
352
+ end
353
+
354
+ it 'should fail lookup with dotted key' do
355
+ expect{ func('database_user.groups') }.to raise_error(/Resolution type :hash is illegal when accessing values using dotted keys. Offending key was 'database_user.groups'/)
317
356
  end
318
357
 
319
358
  it 'should log deprecation errors' do
@@ -383,7 +383,7 @@ describe 'The lookup function' do
383
383
  expect { compiler.compile }.to raise_error(Puppet::ParseError, /did not find a value for the name 'bad_data::b'/)
384
384
  end
385
385
  warnings = logs.select { |log| log.level == :warning }.map { |log| log.message }
386
- expect(warnings).to include("Module 'bad_data': deprecated API function \"bad_data::data\" must use keys qualified with the name of the module")
386
+ expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module")
387
387
  end
388
388
 
389
389
  it 'will succeed finding prefixed keys even when a key in the function provided module data is not prefixed' do
@@ -397,7 +397,7 @@ describe 'The lookup function' do
397
397
  expect(resources).to include('module_c')
398
398
  end
399
399
  warnings = logs.select { |log| log.level == :warning }.map { |log| log.message }
400
- expect(warnings).to include("Module 'bad_data': deprecated API function \"bad_data::data\" must use keys qualified with the name of the module")
400
+ expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module")
401
401
  end
402
402
 
403
403
  it 'will resolve global, environment, and module correctly' do
@@ -429,7 +429,7 @@ describe 'The lookup function' do
429
429
  compiler.compile
430
430
  end
431
431
  warnings = logs.select { |log| log.level == :warning }.map { |log| log.message }
432
- expect(warnings).to include("Module 'bad_data': deprecated API function \"bad_data::data\" must use keys qualified with the name of the module")
432
+ expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module")
433
433
  end
434
434
 
435
435
  it 'a warning will be logged when key in the hiera provided module data is not prefixed' do
@@ -439,7 +439,7 @@ describe 'The lookup function' do
439
439
  compiler.compile
440
440
  end
441
441
  warnings = logs.select { |log| log.level == :warning }.map { |log| log.message }
442
- expect(warnings).to include("Module 'hieraprovider': Hierarchy entry \"two paths\" must use keys qualified with the name of the module")
442
+ expect(warnings).to include("Module 'hieraprovider': Value returned from data_hash function 'json_data', when using location '#{environmentpath}/production/modules/hieraprovider/data/first.json', must use keys qualified with the name of the module")
443
443
  end
444
444
  end
445
445
 
@@ -514,7 +514,7 @@ describe 'The lookup function' do
514
514
  end
515
515
  expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' '))
516
516
  Module "abc" Data Provider (hiera configuration version 5)
517
- deprecated API function "abc::data"
517
+ Deprecated API function "abc::data"
518
518
  No such key: "abc::x"
519
519
  EOS
520
520
  end
@@ -530,13 +530,13 @@ describe 'The lookup function' do
530
530
  Global Data Provider (hiera configuration version 5)
531
531
  No such key: "abc::e"
532
532
  Environment Data Provider (hiera configuration version 5)
533
- deprecated API function "environment::data"
533
+ Deprecated API function "environment::data"
534
534
  Found key: "abc::e" value: {
535
535
  "k1" => "env_e1",
536
536
  "k3" => "env_e3"
537
537
  }
538
538
  Module "abc" Data Provider (hiera configuration version 5)
539
- deprecated API function "abc::data"
539
+ Deprecated API function "abc::data"
540
540
  Found key: "abc::e" value: {
541
541
  "k1" => "module_e1",
542
542
  "k2" => "module_e2"
@@ -576,7 +576,7 @@ describe 'The lookup function' do
576
576
  Global Data Provider (hiera configuration version 5)
577
577
  No such key: "hieraprovider::test::not_found"
578
578
  Environment Data Provider (hiera configuration version 5)
579
- deprecated API function "environment::data"
579
+ Deprecated API function "environment::data"
580
580
  No such key: "hieraprovider::test::not_found"
581
581
  Module "hieraprovider" Data Provider (hiera configuration version 4)
582
582
  Using configuration "#{environmentpath}/production/modules/hieraprovider/hiera.yaml"
@@ -636,7 +636,7 @@ describe 'The lookup function' do
636
636
  :branches => [
637
637
  {
638
638
  :type => :data_provider,
639
- :name => 'deprecated API function "environment::data"',
639
+ :name => 'Deprecated API function "environment::data"',
640
640
  :key => 'abc::e',
641
641
  :value => { 'k1' => 'env_e1', 'k3' => 'env_e3' },
642
642
  :event => :found
@@ -650,7 +650,7 @@ describe 'The lookup function' do
650
650
  :branches => [
651
651
  {
652
652
  :type => :data_provider,
653
- :name => 'deprecated API function "abc::data"',
653
+ :name => 'Deprecated API function "abc::data"',
654
654
  :key => 'abc::e',
655
655
  :event => :found,
656
656
  :value => {
@@ -9,205 +9,429 @@ describe "The lookup function" do
9
9
  include PuppetSpec::Compiler
10
10
  include PuppetSpec::Files
11
11
 
12
- context 'with an environment' do
13
- let(:env_name) { 'spec' }
14
- let(:code_dir_files) { {} }
15
- let(:code_dir) { tmpdir('code') }
16
- let(:ruby_dir) { tmpdir('ruby') }
17
- let(:env_modules) { {} }
18
- let(:env_hiera_yaml) do
19
- <<-YAML.unindent
20
- ---
21
- version: 5
22
- hierarchy:
23
- - name: "Common"
24
- data_hash: yaml_data
25
- path: "common.yaml"
26
- YAML
27
- end
12
+ let(:env_name) { 'spec' }
13
+ let(:code_dir_files) { {} }
14
+ let(:code_dir) { tmpdir('code') }
15
+ let(:ruby_dir) { tmpdir('ruby') }
16
+ let(:env_modules) { {} }
17
+ let(:env_hiera_yaml) do
18
+ <<-YAML.unindent
19
+ ---
20
+ version: 5
21
+ hierarchy:
22
+ - name: "Common"
23
+ data_hash: yaml_data
24
+ path: "common.yaml"
25
+ YAML
26
+ end
28
27
 
29
- let(:env_data) do
30
- {
31
- 'common.yaml' => <<-YAML.unindent
32
- ---
33
- a: value a (from environment)
34
- c:
35
- c_b: value c_b (from environment)
36
- mod_a::a: value mod_a::a (from environment)
37
- mod_a::hash_a:
38
- a: value mod_a::hash_a.a (from environment)
39
- mod_a::hash_b:
40
- a: value mod_a::hash_b.a (from environment)
41
- hash_b:
42
- hash_ba:
43
- bab: value hash_b.hash_ba.bab (from environment)
44
- hash_c:
45
- hash_ca:
46
- caa: value hash_c.hash_ca.caa (from environment)
47
- lookup_options:
48
- mod_a::hash_b:
49
- merge: hash
50
- hash_c:
51
- merge: hash
52
- YAML
53
- }
54
- end
28
+ let(:env_data) { {} }
55
29
 
56
- let(:environment_files) do
57
- {
58
- env_name => {
59
- 'modules' => env_modules,
60
- 'hiera.yaml' => env_hiera_yaml,
61
- 'data' => env_data
62
- }
30
+ let(:environment_files) do
31
+ {
32
+ env_name => {
33
+ 'modules' => env_modules,
34
+ 'hiera.yaml' => env_hiera_yaml,
35
+ 'data' => env_data
63
36
  }
64
- end
37
+ }
38
+ end
65
39
 
66
- let(:ruby_dir_files) do
67
- {
68
- 'hiera' => {
69
- 'backend' => {
70
- 'custom_backend.rb' => <<-RUBY.unindent,
71
- class Hiera::Backend::Custom_backend
72
- def lookup(key, scope, order_override, resolution_type, context)
73
- case key
74
- when 'hash_c'
75
- { 'hash_ca' => { 'cad' => 'value hash_c.hash_ca.cad (from global custom)' }}
76
- when 'hash'
77
- { 'array' => [ 'x5,x6' ] }
78
- when 'array'
79
- [ 'x5,x6' ]
80
- when 'datasources'
81
- Hiera::Backend.datasources(scope, order_override) { |source| source }
82
- else
83
- throw :no_such_key
84
- end
85
- end
86
- end
87
- RUBY
88
- 'other_backend.rb' => <<-RUBY.unindent,
89
- class Hiera::Backend::Other_backend
90
- def lookup(key, scope, order_override, resolution_type, context)
91
- value = Hiera::Config[:other][key.to_sym]
92
- throw :no_such_key if value.nil?
93
- value
94
- end
95
- end
96
- RUBY
97
- }
98
- }
99
- }
100
- end
40
+ let(:ruby_dir_files) { {} }
41
+
42
+ let(:logs) { [] }
43
+ let(:scope_additions ) { {} }
44
+ let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } }
45
+ let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } }
46
+ let(:debugs) { logs.select { |log| log.level == :debug }.map { |log| log.message } }
47
+ let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, env_name, 'modules')]) }
48
+ let(:environments) { Puppet::Environments::Directories.new(populated_env_dir, []) }
49
+ let(:node) { Puppet::Node.new('test_lookup', :environment => env) }
50
+ let(:compiler) { Puppet::Parser::Compiler.new(node) }
51
+ let(:lookup_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'lookup') }
52
+ let(:invocation_with_explain) { Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) }
53
+ let(:explanation) { invocation_with_explain.explainer.explain }
54
+
55
+ let(:populated_code_dir) do
56
+ dir_contained_in(code_dir, code_dir_files)
57
+ code_dir
58
+ end
101
59
 
102
- let(:logs) { [] }
103
- let(:scope_additions ) { {} }
104
- let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } }
105
- let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } }
106
- let(:debugs) { logs.select { |log| log.level == :debug }.map { |log| log.message } }
107
- let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, env_name, 'modules')]) }
108
- let(:environments) { Puppet::Environments::Directories.new(populated_env_dir, []) }
109
- let(:node) { Puppet::Node.new('test_lookup', :environment => env) }
110
- let(:compiler) { Puppet::Parser::Compiler.new(node) }
111
- let(:lookup_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'lookup') }
112
- let(:defaults) {
113
- {
114
- 'mod_a::xd' => 'value mod_a::xd (from default)',
115
- 'mod_a::xd_found' => 'value mod_a::xd_found (from default)',
116
- 'scope_xd' => 'value scope_xd (from default)'
117
- }}
118
- let(:overrides) {
119
- {
120
- 'mod_a::xo' => 'value mod_a::xo (from override)',
121
- 'scope_xo' => 'value scope_xo (from override)'
122
- }}
123
- let(:invocation_with_explain) { Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) }
124
- let(:explanation) { invocation_with_explain.explainer.explain }
125
-
126
- let(:populated_code_dir) do
127
- dir_contained_in(code_dir, code_dir_files)
128
- code_dir
129
- end
60
+ let(:populated_ruby_dir) do
61
+ dir_contained_in(ruby_dir, ruby_dir_files)
62
+ ruby_dir
63
+ end
130
64
 
131
- let(:populated_ruby_dir) do
132
- dir_contained_in(ruby_dir, ruby_dir_files)
133
- ruby_dir
134
- end
65
+ let(:env_dir) do
66
+ d = File.join(populated_code_dir, 'environments')
67
+ Dir.mkdir(d)
68
+ d
69
+ end
70
+
71
+ let(:populated_env_dir) do
72
+ dir_contained_in(env_dir, environment_files)
73
+ env_dir
74
+ end
135
75
 
136
- let(:env_dir) do
137
- d = File.join(populated_code_dir, 'environments')
138
- Dir.mkdir(d)
139
- d
76
+ before(:each) do
77
+ Puppet.settings[:codedir] = code_dir
78
+ Puppet.push_context(:environments => environments, :current_environment => env)
79
+ end
80
+
81
+ after(:each) do
82
+ Puppet.pop_context
83
+ if Object.const_defined?(:Hiera)
84
+ Hiera.send(:remove_instance_variable, :@config) if Hiera.instance_variable_defined?(:@config)
85
+ Hiera.send(:remove_instance_variable, :@logger) if Hiera.instance_variable_defined?(:@logger)
86
+ if Hiera.const_defined?(:Config)
87
+ Hiera::Config.send(:remove_instance_variable, :@config) if Hiera::Config.instance_variable_defined?(:@config)
88
+ end
89
+ if Hiera.const_defined?(:Backend) && Hiera::Backend.respond_to?(:clear!)
90
+ Hiera::Backend.clear!
91
+ end
140
92
  end
93
+ end
141
94
 
142
- let(:populated_env_dir) do
143
- dir_contained_in(env_dir, environment_files)
144
- env_dir
95
+ def collect_notices(code, explain = false, &block)
96
+ Puppet[:code] = code
97
+ Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do
98
+ scope = compiler.topscope
99
+ scope['environment'] = env_name
100
+ scope['domain'] = 'example.com'
101
+ scope_additions.each_pair { |k, v| scope[k] = v }
102
+ if explain
103
+ begin
104
+ invocation_with_explain.lookup('dummy', nil) do
105
+ if block_given?
106
+ compiler.compile { |catalog| block.call(compiler.topscope); catalog }
107
+ else
108
+ compiler.compile
109
+ end
110
+ end
111
+ rescue RuntimeError => e
112
+ invocation_with_explain.report_text { e.message }
113
+ end
114
+ else
115
+ if block_given?
116
+ compiler.compile { |catalog| block.call(compiler.topscope); catalog }
117
+ else
118
+ compiler.compile
119
+ end
120
+ end
145
121
  end
122
+ nil
123
+ end
146
124
 
147
- before(:each) do
148
- Puppet.settings[:codedir] = code_dir
149
- Puppet.push_context(:environments => environments, :current_environment => env)
125
+ def lookup(key, options = {}, explain = false)
126
+ nc_opts = options.empty? ? '' : ", #{Puppet::Pops::Types::TypeFormatter.string(options)}"
127
+ keys = key.is_a?(Array) ? key : [key]
128
+ collect_notices(keys.map { |k| "notice(String(lookup('#{k}'#{nc_opts}), '%p'))" }.join("\n"), explain)
129
+ if explain
130
+ explanation
131
+ else
132
+ result = notices.map { |n| Puppet::Pops::Types::TypeParser.singleton.parse_literal(n) }
133
+ key.is_a?(Array) ? result : result[0]
150
134
  end
135
+ end
136
+
137
+ def explain(key, options = {})
138
+ lookup(key, options, true)[1]
139
+ explanation
140
+ end
141
+
142
+ context 'with faulty hiera.yaml configuration' do
143
+ context 'in global layer' do
144
+ let(:global_data) do
145
+ {
146
+ 'common.yaml' => <<-YAML.unindent
147
+ a: value a (from global)
148
+ YAML
149
+ }
150
+ end
151
+
152
+ let(:code_dir_files) do
153
+ {
154
+ 'hiera.yaml' => hiera_yaml,
155
+ 'data' => global_data
156
+ }
157
+ end
158
+
159
+ before(:each) do
160
+ # Need to set here since spec_helper defines these settings in its "before each"
161
+ Puppet.settings[:codedir] = populated_code_dir
162
+ Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml')
163
+ end
151
164
 
152
- after(:each) do
153
- Puppet.pop_context
154
- if Object.const_defined?(:Hiera)
155
- Hiera.send(:remove_instance_variable, :@config) if Hiera.instance_variable_defined?(:@config)
156
- Hiera.send(:remove_instance_variable, :@logger) if Hiera.instance_variable_defined?(:@logger)
157
- if Hiera.const_defined?(:Config)
158
- Hiera::Config.send(:remove_instance_variable, :@config) if Hiera::Config.instance_variable_defined?(:@config)
165
+ context 'using a not yet supported hiera version' do
166
+ let(:hiera_yaml) { <<-YAML.unindent }
167
+ version: 6
168
+ YAML
169
+
170
+ it 'fails and reports error' do
171
+ expect { lookup('a') }.to raise_error("This runtime does not support hiera.yaml version 6 in #{code_dir}/hiera.yaml")
159
172
  end
160
- if Hiera.const_defined?(:Backend) && Hiera::Backend.respond_to?(:clear!)
161
- Hiera::Backend.clear!
173
+ end
174
+
175
+ context 'with multiply defined backend using hiera version 3' do
176
+ let(:hiera_yaml) { <<-YAML.unindent }
177
+ :version: 3
178
+ :backends:
179
+ - yaml
180
+ - json
181
+ - yaml
182
+ YAML
183
+
184
+ it 'fails and reports error' do
185
+ expect { lookup('a') }.to raise_error(
186
+ "Backend 'yaml' is defined more than once. First defined at line 3 at #{code_dir}/hiera.yaml:5")
162
187
  end
163
188
  end
164
- end
165
189
 
166
- def collect_notices(code, explain = false, &block)
167
- Puppet[:code] = code
168
- Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do
169
- scope = compiler.topscope
170
- scope['environment'] = env_name
171
- scope['domain'] = 'example.com'
172
- scope_additions.each_pair { |k, v| scope[k] = v }
173
- if explain
174
- begin
175
- invocation_with_explain.lookup('dummy', nil) do
176
- if block_given?
177
- compiler.compile { |catalog| block.call(compiler.topscope); catalog }
178
- else
179
- compiler.compile
180
- end
181
- end
182
- rescue RuntimeError => e
183
- invocation_with_explain.report_text { e.message }
190
+ context 'using hiera version 4' do
191
+ let(:hiera_yaml) { <<-YAML.unindent }
192
+ version: 4
193
+ YAML
194
+
195
+ it 'fails and reports error' do
196
+ expect { lookup('a') }.to raise_error(
197
+ "hiera.yaml version 4 cannot be used in the global layer in #{code_dir}/hiera.yaml")
198
+ end
199
+ end
200
+
201
+ context 'using hiera version 5' do
202
+ context 'with multiply defined hierarchy' do
203
+ let(:hiera_yaml) { <<-YAML.unindent }
204
+ version: 5
205
+ hierarchy:
206
+ - name: Common
207
+ path: common.yaml
208
+ - name: Other
209
+ path: other.yaml
210
+ - name: Common
211
+ path: common.yaml
212
+ YAML
213
+
214
+ it 'fails and reports error' do
215
+ expect { lookup('a') }.to raise_error(
216
+ "Hierarchy name 'Common' defined more than once. First defined at line 3 at #{code_dir}/hiera.yaml:7")
184
217
  end
185
- else
186
- if block_given?
187
- compiler.compile { |catalog| block.call(compiler.topscope); catalog }
188
- else
189
- compiler.compile
218
+ end
219
+
220
+ context 'with hiera3_backend that is provided as data_hash function' do
221
+ let(:hiera_yaml) { <<-YAML.unindent }
222
+ version: 5
223
+ hierarchy:
224
+ - name: Common
225
+ hiera3_backend: hocon
226
+ path: common.conf
227
+ YAML
228
+
229
+ it 'fails and reports error' do
230
+ expect { lookup('a') }.to raise_error(
231
+ "Use \"data_hash: hocon_data\" instead of \"hiera3_backend: hocon\" at #{code_dir}/hiera.yaml:4")
232
+ end
233
+ end
234
+
235
+ context 'with no data provider function defined' do
236
+ let(:hiera_yaml) { <<-YAML.unindent }
237
+ version: 5
238
+ defaults:
239
+ datadir: data
240
+ hierarchy:
241
+ - name: Common
242
+ path: common.txt
243
+ YAML
244
+
245
+ it 'fails and reports error' do
246
+ expect { lookup('a') }.to raise_error(
247
+ "One of data_hash, lookup_key, data_dig, or hiera3_backend must be defined in hierarchy 'Common' in #{code_dir}/hiera.yaml")
248
+ end
249
+ end
250
+
251
+ context 'with multiple data providers in defaults' do
252
+ let(:hiera_yaml) { <<-YAML.unindent }
253
+ version: 5
254
+ defaults:
255
+ data_hash: yaml_data
256
+ lookup_key: eyaml_lookup_key
257
+ datadir: data
258
+ hierarchy:
259
+ - name: Common
260
+ path: common.txt
261
+ YAML
262
+
263
+ it 'fails and reports error' do
264
+ expect { lookup('a') }.to raise_error(
265
+ "Only one of data_hash, lookup_key, data_dig, or hiera3_backend can be defined in defaults in #{code_dir}/hiera.yaml")
266
+ end
267
+ end
268
+
269
+ context 'with non existing data provider function' do
270
+ let(:hiera_yaml) { <<-YAML.unindent }
271
+ version: 5
272
+ hierarchy:
273
+ - name: Common
274
+ data_hash: nonesuch_txt_data
275
+ path: common.yaml
276
+ YAML
277
+
278
+ it 'fails and reports error' do
279
+ Puppet[:strict] = :error
280
+ expect { lookup('a') }.to raise_error(
281
+ "Unable to find 'data_hash' function named 'nonesuch_txt_data' in #{code_dir}/hiera.yaml")
282
+ end
283
+ end
284
+
285
+ context 'with a declared default_hierarchy' do
286
+ let(:hiera_yaml) { <<-YAML.unindent }
287
+ version: 5
288
+ hierarchy:
289
+ - name: Common
290
+ path: common.yaml
291
+ default_hierarchy:
292
+ - name: Defaults
293
+ path: defaults.yaml
294
+ YAML
295
+
296
+ it 'fails and reports error' do
297
+ Puppet[:strict] = :error
298
+ expect { lookup('a') }.to raise_error(
299
+ "'default_hierarchy' is only allowed in the module layer at #{code_dir}/hiera.yaml:5")
300
+ end
301
+ end
302
+
303
+ context 'with missing variables' do
304
+ let(:scope_additions) { { 'fqdn' => 'test.example.com' } }
305
+ let(:hiera_yaml) { <<-YAML.unindent }
306
+ version: 5
307
+ hierarchy:
308
+ - name: Common # don't report this line %{::nonesuch}
309
+ path: "%{::fqdn}/%{::nonesuch}/data.yaml"
310
+ YAML
311
+
312
+ it 'fails and reports errors when strict == error' do
313
+ Puppet[:strict] = :error
314
+ expect { lookup('a') }.to raise_error("Undefined variable '::nonesuch' at #{code_dir}/hiera.yaml:4")
315
+ end
316
+ end
317
+
318
+ context 'using interpolation functions' do
319
+ let(:hiera_yaml) { <<-YAML.unindent }
320
+ version: 5
321
+ hierarchy:
322
+ - name: Common # don't report this line %{::nonesuch}
323
+ path: "%{lookup('fqdn')}/data.yaml"
324
+ YAML
325
+
326
+ it 'fails and reports errors when strict == error' do
327
+ Puppet[:strict] = :error
328
+ expect { lookup('a') }.to raise_error("Interpolation using method syntax is not allowed in this context in #{code_dir}/hiera.yaml")
190
329
  end
191
330
  end
192
331
  end
193
- nil
194
332
  end
195
333
 
196
- def lookup(key, options = {}, explain = false)
197
- nc_opts = options.empty? ? '' : ", #{Puppet::Pops::Types::TypeFormatter.string(options)}"
198
- keys = key.is_a?(Array) ? key : [key]
199
- collect_notices(keys.map { |k| "notice(String(lookup('#{k}'#{nc_opts}), '%p'))" }.join("\n"), explain)
200
- if explain
201
- explanation
202
- else
203
- result = notices.map { |n| Puppet::Pops::Types::TypeParser.singleton.parse_literal(n) }
204
- key.is_a?(Array) ? result : result[0]
334
+ context 'in environment layer' do
335
+ context 'using hiera version 4' do
336
+ context 'with an unknown backend' do
337
+ let(:env_hiera_yaml) { <<-YAML.unindent }
338
+ version: 4
339
+ hierarchy:
340
+ - name: Common
341
+ backend: nonesuch
342
+ path: common.yaml
343
+ YAML
344
+
345
+ it 'fails and reports error' do
346
+ expect { lookup('a') }.to raise_error(
347
+ "No data provider is registered for backend 'nonesuch' at #{env_dir}/spec/hiera.yaml:4")
348
+ end
349
+ end
350
+
351
+ context 'with multiply defined hierarchy' do
352
+ let(:env_hiera_yaml) { <<-YAML.unindent }
353
+ version: 4
354
+ hierarchy:
355
+ - name: Common
356
+ backend: yaml
357
+ path: common.yaml
358
+ - name: Other
359
+ backend: yaml
360
+ path: other.yaml
361
+ - name: Common
362
+ backend: yaml
363
+ path: common.yaml
364
+ YAML
365
+
366
+ it 'fails and reports error' do
367
+ expect { lookup('a') }.to raise_error(
368
+ "Hierarchy name 'Common' defined more than once. First defined at line 3 at #{env_dir}/spec/hiera.yaml:9")
369
+ end
370
+ end
371
+ end
372
+
373
+ context 'using hiera version 5' do
374
+ context 'with a hiera3_backend declaration' do
375
+ let(:env_hiera_yaml) { <<-YAML.unindent }
376
+ version: 5
377
+ hierarchy:
378
+ - name: Common
379
+ hiera3_backend: something
380
+ YAML
381
+
382
+ it 'fails and reports error' do
383
+ expect { lookup('a') }.to raise_error(
384
+ "'hiera3_backend' is only allowed in the global layer at #{env_dir}/spec/hiera.yaml:4")
385
+ end
386
+ end
387
+
388
+ context 'with a declared default_hierarchy' do
389
+ let(:env_hiera_yaml) { <<-YAML.unindent }
390
+ version: 5
391
+ hierarchy:
392
+ - name: Common
393
+ path: common.yaml
394
+ default_hierarchy:
395
+ - name: Defaults
396
+ path: defaults.yaml
397
+ YAML
398
+
399
+ it 'fails and reports error' do
400
+ Puppet[:strict] = :error
401
+ expect { lookup('a') }.to raise_error(
402
+ "'default_hierarchy' is only allowed in the module layer at #{env_dir}/spec/hiera.yaml:5")
403
+ end
404
+ end
205
405
  end
206
406
  end
407
+ end
207
408
 
208
- def explain(key, options = {})
209
- lookup(key, options, true)[1]
210
- explanation
409
+ context 'with an environment' do
410
+ let(:env_data) do
411
+ {
412
+ 'common.yaml' => <<-YAML.unindent
413
+ ---
414
+ a: value a (from environment)
415
+ c:
416
+ c_b: value c_b (from environment)
417
+ mod_a::a: value mod_a::a (from environment)
418
+ mod_a::hash_a:
419
+ a: value mod_a::hash_a.a (from environment)
420
+ mod_a::hash_b:
421
+ a: value mod_a::hash_b.a (from environment)
422
+ hash_b:
423
+ hash_ba:
424
+ bab: value hash_b.hash_ba.bab (from environment)
425
+ hash_c:
426
+ hash_ca:
427
+ caa: value hash_c.hash_ca.caa (from environment)
428
+ lookup_options:
429
+ mod_a::hash_b:
430
+ merge: hash
431
+ hash_c:
432
+ merge: hash
433
+ YAML
434
+ }
211
435
  end
212
436
 
213
437
  it 'finds data in the environment' do
@@ -303,7 +527,7 @@ describe "The lookup function" do
303
527
  hierarchy:
304
528
  - name: "Varying"
305
529
  data_hash: yaml_data
306
- path: "x%{var.sub}.yaml"
530
+ path: "#{data_path}"
307
531
  YAML
308
532
  end
309
533
 
@@ -315,83 +539,144 @@ describe "The lookup function" do
315
539
  'data' => {
316
540
  'x.yaml' => <<-YAML.unindent,
317
541
  y: value y from x
318
- YAML
542
+ YAML
319
543
  'x_d.yaml' => <<-YAML.unindent,
320
544
  y: value y from x_d
321
- YAML
322
- 'x_e.yaml' => <<-YAML.unindent
545
+ YAML
546
+ 'x_e.yaml' => <<-YAML.unindent,
323
547
  y: value y from x_e
324
- YAML
548
+ YAML
325
549
  }
326
550
  }
327
551
  }
328
552
  end
329
553
 
330
- it 'reloads the configuration if interpolated values change' do
331
- Puppet[:log_level] = 'debug'
332
- collect_notices("notice('success')") do |scope|
333
- expect(lookup_func.call(scope, 'y')).to eql('value y from x')
334
- scope['var'] = { 'sub' => '_d' }
335
- expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
336
- nested_scope = scope.compiler.newscope(scope)
337
- nested_scope['var'] = { 'sub' => '_e' }
338
- expect(lookup_func.call(nested_scope, 'y')).to eql('value y from x_e')
554
+ context 'using local variable reference' do
555
+ let(:data_path) { 'x%{var.sub}.yaml' }
556
+
557
+ it 'reloads the configuration if interpolated values change' do
558
+ Puppet[:log_level] = 'debug'
559
+ collect_notices("notice('success')") do |scope|
560
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x')
561
+ scope['var'] = { 'sub' => '_d' }
562
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
563
+ nested_scope = scope.compiler.newscope(scope)
564
+ nested_scope['var'] = { 'sub' => '_e' }
565
+ expect(lookup_func.call(nested_scope, 'y')).to eql('value y from x_e')
566
+ end
567
+ expect(notices).to eql(['success'])
568
+ expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy
569
+ end
570
+
571
+ it 'does not include the lookups performed during stability check in explain output' do
572
+ Puppet[:log_level] = 'debug'
573
+ collect_notices("notice('success')") do |scope|
574
+ var = { 'sub' => '_d' }
575
+ scope['var'] = var
576
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
577
+
578
+ # Second call triggers the check
579
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
580
+ end
581
+ expect(notices).to eql(['success'])
582
+ expect(debugs.any? { |m| m =~ /Sub key: "sub"/ }).to be_falsey
339
583
  end
340
- expect(notices).to eql(['success'])
341
- expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy
342
584
  end
343
585
 
344
- it 'does not include the lookups performed during stability check in explain output' do
345
- Puppet[:log_level] = 'debug'
346
- collect_notices("notice('success')") do |scope|
347
- var = { 'sub' => '_d' }
348
- scope['var'] = var
349
- expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
586
+ context 'using global variable reference' do
587
+ let(:data_path) { 'x%{::var.sub}.yaml' }
350
588
 
351
- # Second call triggers the check
352
- expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
589
+ it 'reloads the configuration if interpolated that was previously undefined, gets defined' do
590
+ Puppet[:log_level] = 'debug'
591
+ collect_notices("notice('success')") do |scope|
592
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x')
593
+ scope['var'] = { 'sub' => '_d' }
594
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
595
+ end
596
+ expect(notices).to eql(['success'])
597
+ expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy
598
+ end
599
+
600
+ it 'does not reload the configuration if value changes locally' do
601
+ Puppet[:log_level] = 'debug'
602
+ collect_notices("notice('success')") do |scope|
603
+ scope['var'] = { 'sub' => '_d' }
604
+ expect(lookup_func.call(scope, 'y')).to eql('value y from x_d')
605
+ nested_scope = scope.compiler.newscope(scope)
606
+ nested_scope['var'] = { 'sub' => '_e' }
607
+ expect(lookup_func.call(nested_scope, 'y')).to eql('value y from x_d')
608
+ end
609
+ expect(notices).to eql(['success'])
610
+ expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_falsey
353
611
  end
354
- expect(notices).to eql(['success'])
355
- expect(debugs.any? { |m| m =~ /Sub key: "sub"/ }).to be_falsey
356
612
  end
357
613
  end
358
614
 
359
- context 'that uses reserved option' do
360
- let(:environment_files) do
361
- {
362
- env_name => {
363
- 'hiera.yaml' => <<-YAML.unindent,
364
- ---
365
- version: 5
366
- hierarchy:
367
- - name: "Illegal"
368
- options:
369
- #{opt_spec}
370
- data_hash: yaml_data
371
- YAML
372
- 'data' => {
373
- 'foo.yaml' => "a: The value a\n"
374
- }
375
- }
376
- }
615
+ context 'that uses reserved' do
616
+ let(:environment_files) do
617
+ { env_name => { 'hiera.yaml' => hiera_yaml } }
377
618
  end
378
619
 
379
- context 'path' do
380
- let(:opt_spec) { 'path: data/foo.yaml' }
620
+ context 'option' do
621
+ let(:hiera_yaml) { <<-YAML.unindent }
622
+ version: 5
623
+ hierarchy:
624
+ - name: "Illegal"
625
+ options:
626
+ #{opt_spec}
627
+ data_hash: yaml_data
628
+ YAML
381
629
 
382
- it 'fails and reports the reserved option key' do
383
- expect { lookup('a') }.to raise_error do |e|
384
- expect(e.message).to match(/Option key 'path' used in hierarchy 'Illegal' is reserved by Puppet/)
630
+ context 'path' do
631
+ let(:opt_spec) { 'path: data/foo.yaml' }
632
+
633
+ it 'fails and reports the reserved option key' do
634
+ expect { lookup('a') }.to raise_error do |e|
635
+ expect(e.message).to match(/Option key 'path' used in hierarchy 'Illegal' is reserved by Puppet/)
636
+ end
637
+ end
638
+ end
639
+
640
+ context 'uri' do
641
+ let(:opt_spec) { 'uri: file:///data/foo.yaml' }
642
+
643
+ it 'fails and reports the reserved option key' do
644
+ expect { lookup('a') }.to raise_error do |e|
645
+ expect(e.message).to match(/Option key 'uri' used in hierarchy 'Illegal' is reserved by Puppet/)
646
+ end
385
647
  end
386
648
  end
387
649
  end
388
650
 
389
- context 'uri' do
390
- let(:opt_spec) { 'uri: file:///data/foo.yaml' }
651
+ context 'default option' do
652
+ let(:hiera_yaml) { <<-YAML.unindent }
653
+ ---
654
+ version: 5
655
+ defaults:
656
+ options:
657
+ #{opt_spec}
658
+ hierarchy:
659
+ - name: "Illegal"
660
+ data_hash: yaml_data
661
+ YAML
391
662
 
392
- it 'fails and reports the reserved option key' do
393
- expect { lookup('a') }.to raise_error do |e|
394
- expect(e.message).to match(/Option key 'uri' used in hierarchy 'Illegal' is reserved by Puppet/)
663
+ context 'path' do
664
+ let(:opt_spec) { 'path: data/foo.yaml' }
665
+
666
+ it 'fails and reports the reserved option key' do
667
+ expect { lookup('a') }.to raise_error do |e|
668
+ expect(e.message).to match(/Option key 'path' used in defaults is reserved by Puppet/)
669
+ end
670
+ end
671
+ end
672
+
673
+ context 'uri' do
674
+ let(:opt_spec) { 'uri: file:///data/foo.yaml' }
675
+
676
+ it 'fails and reports the reserved option key' do
677
+ expect { lookup('a') }.to raise_error do |e|
678
+ expect(e.message).to match(/Option key 'uri' used in defaults is reserved by Puppet/)
679
+ end
395
680
  end
396
681
  end
397
682
  end
@@ -505,7 +790,31 @@ describe "The lookup function" do
505
790
 
506
791
  it 'fails lookup and reports a type mismatch' do
507
792
  expect { lookup('a') }.to raise_error do |e|
508
- expect(e.message).to match(/wrong type, expects a value of type Scalar, Sensitive, Type, Hash, or Array, got Runtime/)
793
+ expect(e.message).to match(/key 'a'.*data_hash function 'yaml_data'.*using location.*wrong type, expects Puppet::LookupValue, got Runtime/)
794
+ end
795
+ end
796
+ end
797
+
798
+ context 'that contains illegal interpolations' do
799
+ context 'in the form of an alias that is not the entire string' do
800
+ let(:common_yaml) { <<-YAML.unindent }
801
+ a: "%{alias('x')} and then some"
802
+ x: value x
803
+ YAML
804
+
805
+ it 'fails lookup and reports a type mismatch' do
806
+ expect { lookup('a') }.to raise_error("'alias' interpolation is only permitted if the expression is equal to the entire string")
807
+ end
808
+ end
809
+
810
+ context 'in the form of an unknown function name' do
811
+ let(:common_yaml) { <<-YAML.unindent }
812
+ a: "%{what('x')}"
813
+ x: value x
814
+ YAML
815
+
816
+ it 'fails lookup and reports a type mismatch' do
817
+ expect { lookup('a') }.to raise_error("Unknown interpolation method 'what'")
509
818
  end
510
819
  end
511
820
  end
@@ -561,56 +870,59 @@ describe "The lookup function" do
561
870
  YAML
562
871
  end
563
872
 
873
+ let(:env_lookup_options) { <<-YAML.unindent }
874
+ lookup_options:
875
+ b:
876
+ merge: hash
877
+ '^[^b]$':
878
+ merge: deep
879
+ '^c':
880
+ merge: first
881
+ '^b':
882
+ merge: first
883
+ '^mod::ha.*_b':
884
+ merge: hash
885
+ YAML
886
+
564
887
  let(:env_data) do
565
888
  {
566
- 'first.yaml' => <<-YAML.unindent,
567
- a:
568
- aa:
569
- aaa: a.aa.aaa
570
- b:
571
- ba:
572
- baa: b.ba.baa
573
- bb:
574
- bba: b.bb.bba
575
- c:
576
- ca:
577
- caa: c.ca.caa
578
- mod::hash_a:
579
- aa:
580
- aab: aab (from environment)
581
- ab:
582
- aba: aba (from environment)
583
- abb: abb (from environment)
584
- mod::hash_b:
585
- ba:
586
- bab: bab (from environment)
587
- bc:
588
- bca: bca (from environment)
589
- lookup_options:
590
- b:
591
- merge: hash
592
- '^[^b]$':
593
- merge: deep
594
- '^c':
595
- merge: first
596
- '^b':
597
- merge: first
598
- '^mod::ha.*_b':
599
- merge: hash
600
- YAML
601
- 'second.yaml' => <<-YAML.unindent,
602
- a:
603
- aa:
604
- aab: a.aa.aab
605
- b:
606
- ba:
607
- bab: b.ba.bab
608
- bb:
609
- bbb: b.bb.bbb
610
- c:
611
- ca:
612
- cab: c.ca.cab
613
- YAML
889
+ 'first.yaml' => <<-YAML.unindent + env_lookup_options,
890
+ a:
891
+ aa:
892
+ aaa: a.aa.aaa
893
+ b:
894
+ ba:
895
+ baa: b.ba.baa
896
+ bb:
897
+ bba: b.bb.bba
898
+ c:
899
+ ca:
900
+ caa: c.ca.caa
901
+ mod::hash_a:
902
+ aa:
903
+ aab: aab (from environment)
904
+ ab:
905
+ aba: aba (from environment)
906
+ abb: abb (from environment)
907
+ mod::hash_b:
908
+ ba:
909
+ bab: bab (from environment)
910
+ bc:
911
+ bca: bca (from environment)
912
+ YAML
913
+ 'second.yaml' => <<-YAML.unindent,
914
+ a:
915
+ aa:
916
+ aab: a.aa.aab
917
+ b:
918
+ ba:
919
+ bab: b.ba.bab
920
+ bb:
921
+ bbb: b.bb.bbb
922
+ c:
923
+ ca:
924
+ cab: c.ca.cab
925
+ YAML
614
926
  }
615
927
  end
616
928
 
@@ -671,26 +983,24 @@ describe "The lookup function" do
671
983
  expect { lookup('mod::a') }.to raise_error(Puppet::DataBinding::LookupError, /all lookup_options patterns must match a key starting with module name/)
672
984
  end
673
985
  end
674
- end
675
-
676
- context 'and a global Hiera v4 configuration' do
677
- let(:code_dir_files) do
678
- {
679
- 'hiera.yaml' => <<-YAML.unindent,
680
- ---
681
- version: 4
682
- YAML
683
- }
684
- end
685
986
 
686
- before(:each) do
687
- # Need to set here since spec_helper defines these settings in its "before each"
688
- Puppet.settings[:codedir] = populated_code_dir
689
- Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml')
690
- end
987
+ context 'and there are no lookup options that do not use patterns' do
988
+
989
+ let(:env_lookup_options) { <<-YAML.unindent }
990
+ lookup_options:
991
+ '^[^b]$':
992
+ merge: deep
993
+ '^c':
994
+ merge: first
995
+ '^b':
996
+ merge: first
997
+ '^mod::ha.*_b':
998
+ merge: hash
999
+ YAML
691
1000
 
692
- it 'raises an error' do
693
- expect { lookup('a') }.to raise_error(Puppet::Error, /hiera configuration version 4 cannot be used in the global layer/)
1001
+ it 'finds lookup_options that matches a pattern' do
1002
+ expect(lookup('a')).to eql({'aa' => { 'aaa' => 'a.aa.aaa', 'aab' => 'a.aa.aab' }})
1003
+ end
694
1004
  end
695
1005
  end
696
1006
 
@@ -732,6 +1042,30 @@ describe "The lookup function" do
732
1042
  }
733
1043
  }
734
1044
  end
1045
+ end
1046
+
1047
+ context 'and an environment Hiera v5 configuration using mapped_paths' do
1048
+ let(:scope_additions) do
1049
+ {
1050
+ 'mapped' => {
1051
+ 'array_var' => ['a', 'b', 'c'],
1052
+ 'hash_var' => { 'x' => 'a', 'y' => 'b', 'z' => 'c' },
1053
+ 'string_var' => 's' },
1054
+ 'var' => 'global_var' # overridden by mapped path variable
1055
+ }
1056
+ end
1057
+
1058
+ let(:env_hiera_yaml) do
1059
+ <<-YAML.unindent
1060
+ ---
1061
+ version: 5
1062
+ hierarchy:
1063
+ - name: Mapped Paths
1064
+ mapped_paths: #{mapped_paths}
1065
+ - name: Global Path
1066
+ path: "%{var}.yaml"
1067
+ YAML
1068
+ end
735
1069
 
736
1070
  let(:environment_files) do
737
1071
  {
@@ -742,20 +1076,199 @@ describe "The lookup function" do
742
1076
  }
743
1077
  end
744
1078
 
745
- it 'finds environment data using globs' do
746
- expect(lookup('glob_a')).to eql('value glob_a')
747
- expect(warnings).to be_empty
1079
+ context 'that originates from an array' do
1080
+ let (:mapped_paths) { '[mapped.array_var, var, "paths/%{var}.yaml"]' }
1081
+
1082
+ let(:env_data) do
1083
+ {
1084
+ 'paths' => {
1085
+ 'a.yaml' => <<-YAML.unindent,
1086
+ path_a: value path_a
1087
+ path_h:
1088
+ a: value path_h.a
1089
+ c: value path_h.c
1090
+ YAML
1091
+ 'b.yaml' => <<-YAML.unindent,
1092
+ path_h:
1093
+ b: value path_h.b
1094
+ d: value path_h.d
1095
+ YAML
1096
+ 'd.yaml' => <<-YAML.unindent
1097
+ path_h:
1098
+ b: value path_h.b (from d.yaml)
1099
+ d: value path_h.d (from d.yaml)
1100
+ YAML
1101
+ },
1102
+ 'global_var.yaml' => <<-YAML.unindent,
1103
+ path_h:
1104
+ e: value path_h.e
1105
+ YAML
1106
+ 'other_var.yaml' => <<-YAML.unindent
1107
+ path_h:
1108
+ e: value path_h.e (from other_var.yaml)
1109
+ YAML
1110
+ }
1111
+ end
1112
+
1113
+ it 'finds environment data using mapped_paths' do
1114
+ expect(lookup('path_a')).to eql('value path_a')
1115
+ expect(warnings).to be_empty
1116
+ end
1117
+
1118
+ it 'includes mapped path in explain output' do
1119
+ explanation = explain('path_h', 'merge' => 'deep')
1120
+ ['a', 'b', 'c'].each do |var|
1121
+ expect(explanation).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/#{var}\.yaml"\n\s+Original path: "paths\/%\{var\}\.yaml"/)
1122
+ end
1123
+ expect(warnings).to be_empty
1124
+ end
1125
+
1126
+ it 'performs merges between mapped paths and global path interpolated using same key' do
1127
+ expect(lookup('path_h', 'merge' => 'hash')).to eql(
1128
+ {
1129
+ 'a' => 'value path_h.a',
1130
+ 'b' => 'value path_h.b',
1131
+ 'c' => 'value path_h.c',
1132
+ 'd' => 'value path_h.d',
1133
+ 'e' => 'value path_h.e'
1134
+ })
1135
+ expect(warnings).to be_empty
1136
+ end
1137
+
1138
+ it 'keeps track of changes in key overridden by interpolated key' do
1139
+ Puppet[:log_level] = 'debug'
1140
+ collect_notices("notice('success')") do |scope|
1141
+ expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql(
1142
+ {
1143
+ 'a' => 'value path_h.a',
1144
+ 'b' => 'value path_h.b',
1145
+ 'c' => 'value path_h.c',
1146
+ 'd' => 'value path_h.d',
1147
+ 'e' => 'value path_h.e'
1148
+ })
1149
+ scope.with_local_scope('var' => 'other_var') do
1150
+ expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql(
1151
+ {
1152
+ 'a' => 'value path_h.a',
1153
+ 'b' => 'value path_h.b',
1154
+ 'c' => 'value path_h.c',
1155
+ 'd' => 'value path_h.d',
1156
+ 'e' => 'value path_h.e (from other_var.yaml)'
1157
+ })
1158
+ end
1159
+ end
1160
+ expect(notices).to eql(['success'])
1161
+ expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy
1162
+ end
1163
+
1164
+ it 'keeps track of changes in elements of mapped key' do
1165
+ Puppet[:log_level] = 'debug'
1166
+ collect_notices("notice('success')") do |scope|
1167
+ expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql(
1168
+ {
1169
+ 'a' => 'value path_h.a',
1170
+ 'b' => 'value path_h.b',
1171
+ 'c' => 'value path_h.c',
1172
+ 'd' => 'value path_h.d',
1173
+ 'e' => 'value path_h.e'
1174
+ })
1175
+ scope['mapped']['array_var'] = ['a', 'c', 'd']
1176
+ expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql(
1177
+ {
1178
+ 'a' => 'value path_h.a',
1179
+ 'b' => 'value path_h.b (from d.yaml)',
1180
+ 'c' => 'value path_h.c',
1181
+ 'd' => 'value path_h.d (from d.yaml)',
1182
+ 'e' => 'value path_h.e'
1183
+ })
1184
+ end
1185
+ expect(notices).to eql(['success'])
1186
+ expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy
1187
+ end
1188
+ end
1189
+
1190
+ context 'that originates from a hash' do
1191
+ let (:mapped_paths) { '[mapped.hash_var, var, "paths/%{var.0}.%{var.1}.yaml"]' }
1192
+
1193
+ let(:env_data) do
1194
+ {
1195
+ 'paths' => {
1196
+ 'x.a.yaml' => <<-YAML.unindent,
1197
+ path_xa: value path_xa
1198
+ path_m:
1199
+ a: value path_m.a
1200
+ c: value path_m.c
1201
+ YAML
1202
+ 'y.b.yaml' => <<-YAML.unindent
1203
+ path_m:
1204
+ b: value path_m.b
1205
+ d: value path_m.d
1206
+ YAML
1207
+ },
1208
+ 'global_var.yaml' => <<-YAML.unindent
1209
+ path_m:
1210
+ e: value path_m.e
1211
+ YAML
1212
+ }
1213
+ end
1214
+
1215
+ it 'finds environment data using mapped_paths' do
1216
+ expect(lookup('path_xa')).to eql('value path_xa')
1217
+ expect(warnings).to be_empty
1218
+ end
1219
+
1220
+ it 'includes mapped path in explain output' do
1221
+ explanation = explain('path_h', 'merge' => 'deep')
1222
+ ['x\.a', 'y\.b', 'z\.c'].each do |var|
1223
+ expect(explanation).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/#{var}\.yaml"\n\s+Original path: "paths\/%\{var\.0\}\.%\{var\.1\}\.yaml"/)
1224
+ end
1225
+ expect(warnings).to be_empty
1226
+ end
1227
+
1228
+ it 'performs merges between mapped paths' do
1229
+ expect(lookup('path_m', 'merge' => 'hash')).to eql(
1230
+ {
1231
+ 'a' => 'value path_m.a',
1232
+ 'b' => 'value path_m.b',
1233
+ 'c' => 'value path_m.c',
1234
+ 'd' => 'value path_m.d',
1235
+ 'e' => 'value path_m.e'
1236
+ })
1237
+ expect(warnings).to be_empty
1238
+ end
748
1239
  end
749
1240
 
750
- it 'performs merges between interpolated and globbed paths' do
751
- expect(lookup('glob_b', 'merge' => 'hash')).to eql(
1241
+ context 'that originates from a string' do
1242
+ let (:mapped_paths) { '[mapped.string_var, var, "paths/%{var}.yaml"]' }
1243
+
1244
+ let(:env_data) do
752
1245
  {
753
- 'a' => 'value glob_b.a',
754
- 'b' => 'value glob_b.b',
755
- 'c' => 'value glob_b.c',
756
- 'd' => 'value glob_b.d'
757
- })
758
- expect(warnings).to be_empty
1246
+ 'paths' => {
1247
+ 's.yaml' => <<-YAML.unindent,
1248
+ path_s: value path_s
1249
+ YAML
1250
+ }
1251
+ }
1252
+ end
1253
+
1254
+ it 'includes mapped path in explain output' do
1255
+ expect(explain('path_s')).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/s\.yaml"\n\s+Original path: "paths\/%\{var\}\.yaml"/)
1256
+ expect(warnings).to be_empty
1257
+ end
1258
+
1259
+ it 'finds environment data using mapped_paths' do
1260
+ expect(lookup('path_s')).to eql('value path_s')
1261
+ expect(warnings).to be_empty
1262
+ end
1263
+ end
1264
+
1265
+ context 'where the enty does not exist' do
1266
+ let (:mapped_paths) { '[mapped.nosuch_var, var, "paths/%{var}.yaml"]' }
1267
+
1268
+ it 'finds environment data using mapped_paths' do
1269
+ expect(explain('hello')).to match(/No such key: "hello"/)
1270
+ expect(warnings).to be_empty
1271
+ end
759
1272
  end
760
1273
  end
761
1274
 
@@ -784,7 +1297,7 @@ describe "The lookup function" do
784
1297
 
785
1298
  it 'will raise an error if --strict is set to error' do
786
1299
  Puppet[:strict] = :error
787
- expect { lookup('g') }.to raise_error(Puppet::Error, /hiera configuration version 3 cannot be used in an environment/)
1300
+ expect { lookup('g') }.to raise_error(Puppet::Error, /hiera.yaml version 3 cannot be used in an environment/)
788
1301
  end
789
1302
 
790
1303
  it 'will log a warning and ignore the file if --strict is set to warning' do
@@ -879,6 +1392,9 @@ describe "The lookup function" do
879
1392
  hash_c:
880
1393
  hash_ca:
881
1394
  cab: value hash_c.hash_ca.cab (from global)
1395
+ ipl_hiera_env: "environment value '%{hiera('mod_a::hash_a.a')}'"
1396
+ ipl_hiera_mod: "module value '%{hiera('mod_a::abc')}'"
1397
+ ipl_hiera_modc: "module value '%{hiera('mod_a::caller')}'"
882
1398
  YAML
883
1399
  'example.com.yaml' => <<-YAML.unindent,
884
1400
  x: value x (from global example.com.yaml)
@@ -905,6 +1421,44 @@ describe "The lookup function" do
905
1421
  }
906
1422
  end
907
1423
 
1424
+ let(:ruby_dir_files) do
1425
+ {
1426
+ 'hiera' => {
1427
+ 'backend' => {
1428
+ 'custom_backend.rb' => <<-RUBY.unindent,
1429
+ class Hiera::Backend::Custom_backend
1430
+ def lookup(key, scope, order_override, resolution_type, context)
1431
+ case key
1432
+ when 'hash_c'
1433
+ { 'hash_ca' => { 'cad' => 'value hash_c.hash_ca.cad (from global custom)' }}
1434
+ when 'hash'
1435
+ { 'array' => [ 'x5,x6' ] }
1436
+ when 'array'
1437
+ [ 'x5,x6' ]
1438
+ when 'datasources'
1439
+ Hiera::Backend.datasources(scope, order_override) { |source| source }
1440
+ when 'dotted.key'
1441
+ 'custom backend received request for dotted.key value'
1442
+ else
1443
+ throw :no_such_key
1444
+ end
1445
+ end
1446
+ end
1447
+ RUBY
1448
+ 'other_backend.rb' => <<-RUBY.unindent,
1449
+ class Hiera::Backend::Other_backend
1450
+ def lookup(key, scope, order_override, resolution_type, context)
1451
+ value = Hiera::Config[:other][key.to_sym]
1452
+ throw :no_such_key if value.nil?
1453
+ value
1454
+ end
1455
+ end
1456
+ RUBY
1457
+ }
1458
+ }
1459
+ }
1460
+ end
1461
+
908
1462
  before(:each) do
909
1463
  # Need to set here since spec_helper defines these settings in its "before each"
910
1464
  Puppet.settings[:codedir] = populated_code_dir
@@ -929,7 +1483,7 @@ describe "The lookup function" do
929
1483
  end
930
1484
 
931
1485
  context 'version 3' do
932
- it 'finds data in the environment and reports deprecation warnings for both environment.conf and hiera.yaml' do
1486
+ it 'finds data in in global layer and reports deprecation warnings for hiera.yaml' do
933
1487
  expect(lookup('a')).to eql('value a (from global)')
934
1488
  expect(warnings).to include(/Use of 'hiera.yaml' version 3 is deprecated. It should be converted to version 5/)
935
1489
  end
@@ -981,6 +1535,64 @@ describe "The lookup function" do
981
1535
  expect(lookup('xs.subkey')).to eql('value xs.subkey (from global hocon)')
982
1536
  end
983
1537
 
1538
+ context 'with a module data provider' do
1539
+ let(:module_files) do
1540
+ {
1541
+ 'mod_a' => {
1542
+ 'hiera.yaml' => <<-YAML.unindent,
1543
+ version: 5
1544
+ hierarchy:
1545
+ - name: Common
1546
+ path: common.yaml
1547
+ YAML
1548
+ 'data' => {
1549
+ 'common.yaml' => <<-YAML.unindent
1550
+ mod_a::abc: value mod_a::abc (from module)
1551
+ mod_a::caller: "calling module is %{calling_module}"
1552
+ YAML
1553
+ }
1554
+ }
1555
+ }
1556
+ end
1557
+
1558
+ let(:environment_files) do
1559
+ {
1560
+ env_name => {
1561
+ 'hiera.yaml' => env_hiera_yaml,
1562
+ 'data' => env_data,
1563
+ 'modules' => module_files
1564
+ }
1565
+ }
1566
+ end
1567
+
1568
+ it "interpolation function 'hiera' finds values in environment" do
1569
+ expect(lookup('ipl_hiera_env')).to eql("environment value 'value mod_a::hash_a.a (from environment)'")
1570
+ end
1571
+
1572
+ it "interpolation function 'hiera' finds values in module" do
1573
+ expect(lookup('ipl_hiera_mod')).to eql("module value 'value mod_a::abc (from module)'")
1574
+ end
1575
+
1576
+ it "interpolation function 'hiera' finds values in module and that module does not find %{calling_module}" do
1577
+ expect(lookup('ipl_hiera_modc')).to eql("module value 'calling module is '")
1578
+ end
1579
+
1580
+ context 'but no environment data provider' do
1581
+ let(:environment_files) do
1582
+ {
1583
+ env_name => {
1584
+ 'modules' => module_files
1585
+ }
1586
+ }
1587
+ end
1588
+
1589
+ it "interpolation function 'hiera' does not find values in a module" do
1590
+ expect(lookup('ipl_hiera_mod')).to eql("module value ''")
1591
+ end
1592
+ end
1593
+ end
1594
+
1595
+
984
1596
  context 'using an eyaml backend' do
985
1597
  let(:private_key_name) { 'private_key.pkcs7.pem' }
986
1598
  let(:public_key_name) { 'public_key.pkcs7.pem' }
@@ -1309,6 +1921,10 @@ describe "The lookup function" do
1309
1921
  expect(lookup('other_option')).to eql('value of other_option')
1310
1922
  end
1311
1923
 
1924
+ it 'dotted keys are passed down to custom backend' do
1925
+ expect(lookup('dotted.key')).to eql('custom backend received request for dotted.key value')
1926
+ end
1927
+
1312
1928
  it 'multiple hiera3_backend declarations can be used and are merged into the generated config' do
1313
1929
  expect(lookup(['datasources', 'other_option'])).to eql([['common', 'example.com'], 'value of other_option'])
1314
1930
  expect(Hiera::Config.instance_variable_get(:@config)).to eql(
@@ -1328,6 +1944,60 @@ describe "The lookup function" do
1328
1944
  expect(e.message).to match(/Lookup using Hocon data_hash function is not supported without hocon library/)
1329
1945
  end
1330
1946
  end
1947
+
1948
+ context 'with missing path declaraion' do
1949
+ context 'and yaml_data function' do
1950
+ let(:hiera_yaml) { <<-YAML.unindent }
1951
+ version: 5
1952
+ hierarchy:
1953
+ - name: Yaml
1954
+ data_hash: yaml_data
1955
+ YAML
1956
+
1957
+ it 'fails and reports the missing path' do
1958
+ expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/)
1959
+ end
1960
+ end
1961
+
1962
+ context 'and json_data function' do
1963
+ let(:hiera_yaml) { <<-YAML.unindent }
1964
+ version: 5
1965
+ hierarchy:
1966
+ - name: Json
1967
+ data_hash: json_data
1968
+ YAML
1969
+
1970
+ it 'fails and reports the missing path' do
1971
+ expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/)
1972
+ end
1973
+ end
1974
+
1975
+ context 'and hocon_data function' do
1976
+ let(:hiera_yaml) { <<-YAML.unindent }
1977
+ version: 5
1978
+ hierarchy:
1979
+ - name: Hocon
1980
+ data_hash: hocon_data
1981
+ YAML
1982
+
1983
+ it 'fails and reports the missing path' do
1984
+ expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/)
1985
+ end
1986
+ end
1987
+
1988
+ context 'and eyaml_lookup_key function' do
1989
+ let(:hiera_yaml) { <<-YAML.unindent }
1990
+ version: 5
1991
+ hierarchy:
1992
+ - name: Yaml
1993
+ lookup_key: eyaml_lookup_key
1994
+ YAML
1995
+
1996
+ it 'fails and reports the missing path' do
1997
+ expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this lookup_key function/)
1998
+ end
1999
+ end
2000
+ end
1331
2001
  end
1332
2002
 
1333
2003
  context 'with a hiera3_backend that has no paths' do
@@ -1387,7 +2057,7 @@ describe "The lookup function" do
1387
2057
  end
1388
2058
 
1389
2059
  it 'raises an error' do
1390
- expect { lookup('mod_a::a') }.to raise_error(Puppet::Error, /hiera configuration version 3 cannot be used in a module/)
2060
+ expect { lookup('mod_a::a') }.to raise_error(Puppet::Error, /hiera.yaml version 3 cannot be used in a module/)
1391
2061
  end
1392
2062
  end
1393
2063
 
@@ -1438,13 +2108,24 @@ describe "The lookup function" do
1438
2108
  end
1439
2109
 
1440
2110
  context 'using a data_hash that reads a yaml file' do
2111
+ let(:defaults) {
2112
+ {
2113
+ 'mod_a::xd' => 'value mod_a::xd (from default)',
2114
+ 'mod_a::xd_found' => 'value mod_a::xd_found (from default)',
2115
+ 'scope_xd' => 'value scope_xd (from default)'
2116
+ }}
2117
+ let(:overrides) {
2118
+ {
2119
+ 'mod_a::xo' => 'value mod_a::xo (from override)',
2120
+ 'scope_xo' => 'value scope_xo (from override)'
2121
+ }}
2122
+
1441
2123
  let(:scope_additions) do
1442
2124
  {
1443
2125
  'scope_scalar' => 'scope scalar value',
1444
2126
  'scope_hash' => { 'a' => 'scope hash a', 'b' => 'scope hash b' }
1445
2127
  }
1446
2128
  end
1447
-
1448
2129
  let(:mod_a_files) do
1449
2130
  {
1450
2131
  'mod_a' => {
@@ -1628,55 +2309,57 @@ describe "The lookup function" do
1628
2309
  end
1629
2310
 
1630
2311
  context 'using a lookup_key that is a puppet function' do
2312
+ let(:puppet_function) { <<-PUPPET.unindent }
2313
+ function mod_a::pp_lookup_key(Puppet::LookupKey $key, Hash[String,String] $options, Puppet::LookupContext $context) >> Puppet::LookupValue {
2314
+ case $key {
2315
+ 'mod_a::really_interpolated': { $context.interpolate("-- %{lookup('mod_a::a')} --") }
2316
+ 'mod_a::recursive': { lookup($key) }
2317
+ default: {
2318
+ if $context.cache_has_key(mod_a::a) {
2319
+ $context.explain || { 'reusing cache' }
2320
+ } else {
2321
+ $context.explain || { 'initializing cache' }
2322
+ $context.cache_all({
2323
+ mod_a::a => 'value mod_a::a (from mod_a)',
2324
+ mod_a::b => 'value mod_a::b (from mod_a)',
2325
+ mod_a::c => 'value mod_a::c (from mod_a)',
2326
+ mod_a::hash_a => {
2327
+ a => 'value mod_a::hash_a.a (from mod_a)',
2328
+ b => 'value mod_a::hash_a.b (from mod_a)'
2329
+ },
2330
+ mod_a::hash_b => {
2331
+ a => 'value mod_a::hash_b.a (from mod_a)',
2332
+ b => 'value mod_a::hash_b.b (from mod_a)'
2333
+ },
2334
+ mod_a::interpolated => "-- %{lookup('mod_a::a')} --",
2335
+ mod_a::a_a => "-- %{lookup('mod_a::hash_a.a')} --",
2336
+ mod_a::a_b => "-- %{lookup('mod_a::hash_a.b')} --",
2337
+ mod_a::b_a => "-- %{lookup('mod_a::hash_b.a')} --",
2338
+ mod_a::b_b => "-- %{lookup('mod_a::hash_b.b')} --",
2339
+ 'mod_a::a.quoted.key' => 'value mod_a::a.quoted.key (from mod_a)',
2340
+ mod_a::sensitive => Sensitive('reduct me please'),
2341
+ mod_a::type => Object[{name => 'FindMe', 'attributes' => {'x' => String}}],
2342
+ mod_a::version => SemVer('3.4.1'),
2343
+ mod_a::version_range => SemVerRange('>=3.4.1'),
2344
+ mod_a::timestamp => Timestamp("1994-03-25T19:30:00"),
2345
+ mod_a::timespan => Timespan("3-10:00:00")
2346
+ })
2347
+ }
2348
+ if !$context.cache_has_key($key) {
2349
+ $context.not_found
2350
+ }
2351
+ $context.explain || { "returning value for $key" }
2352
+ $context.cached_value($key)
2353
+ }
2354
+ }
2355
+ }
2356
+ PUPPET
2357
+
1631
2358
  let(:mod_a_files) do
1632
2359
  {
1633
2360
  'mod_a' => {
1634
2361
  'functions' => {
1635
- 'pp_lookup_key.pp' => <<-PUPPET.unindent
1636
- function mod_a::pp_lookup_key($key, $options, $context) {
1637
- case $key {
1638
- 'mod_a::really_interpolated': { $context.interpolate("-- %{lookup('mod_a::a')} --") }
1639
- 'mod_a::recursive': { lookup($key) }
1640
- default: {
1641
- if $context.cache_has_key(mod_a::a) {
1642
- $context.explain || { 'reusing cache' }
1643
- } else {
1644
- $context.explain || { 'initializing cache' }
1645
- $context.cache_all({
1646
- mod_a::a => 'value mod_a::a (from mod_a)',
1647
- mod_a::b => 'value mod_a::b (from mod_a)',
1648
- mod_a::c => 'value mod_a::c (from mod_a)',
1649
- mod_a::hash_a => {
1650
- a => 'value mod_a::hash_a.a (from mod_a)',
1651
- b => 'value mod_a::hash_a.b (from mod_a)'
1652
- },
1653
- mod_a::hash_b => {
1654
- a => 'value mod_a::hash_b.a (from mod_a)',
1655
- b => 'value mod_a::hash_b.b (from mod_a)'
1656
- },
1657
- mod_a::interpolated => "-- %{lookup('mod_a::a')} --",
1658
- mod_a::a_a => "-- %{lookup('mod_a::hash_a.a')} --",
1659
- mod_a::a_b => "-- %{lookup('mod_a::hash_a.b')} --",
1660
- mod_a::b_a => "-- %{lookup('mod_a::hash_b.a')} --",
1661
- mod_a::b_b => "-- %{lookup('mod_a::hash_b.b')} --",
1662
- 'mod_a::a.quoted.key' => 'value mod_a::a.quoted.key (from mod_a)',
1663
- mod_a::sensitive => Sensitive('reduct me please'),
1664
- mod_a::type => Object[{name => 'FindMe', 'attributes' => {'x' => String}}],
1665
- mod_a::version => SemVer('3.4.1'),
1666
- mod_a::version_range => SemVerRange('>=3.4.1'),
1667
- mod_a::timestamp => Timestamp("1994-03-25T19:30:00"),
1668
- mod_a::timespan => Timespan("3-10:00:00")
1669
- })
1670
- }
1671
- if !$context.cache_has_key($key) {
1672
- $context.not_found
1673
- }
1674
- $context.explain || { "returning value for $key" }
1675
- $context.cached_value($key)
1676
- }
1677
- }
1678
- }
1679
- PUPPET
2362
+ 'pp_lookup_key.pp' => puppet_function
1680
2363
  },
1681
2364
  'hiera.yaml' => <<-YAML.unindent,
1682
2365
  ---
@@ -1749,6 +2432,19 @@ describe "The lookup function" do
1749
2432
  expect(notices).to eql(['success'])
1750
2433
  end
1751
2434
  end
2435
+
2436
+ context 'with declared but incompatible return_type' do
2437
+ let(:puppet_function) { <<-PUPPET.unindent }
2438
+ function mod_a::pp_lookup_key(Puppet::LookupKey $key, Hash[String,String] $options, Puppet::LookupContext $context) >> Runtime['ruby','Symbol'] {
2439
+ undef
2440
+ }
2441
+ PUPPET
2442
+
2443
+ it 'fails and reports error' do
2444
+ expect{lookup('mod_a::a')}.to raise_error(
2445
+ "Return type of 'lookup_key' function named 'mod_a::pp_lookup_key' is incorrect, expects a value of type Undef, Scalar, Sensitive, Type, Hash, or Array, got Runtime")
2446
+ end
2447
+ end
1752
2448
  end
1753
2449
 
1754
2450
  context 'using a data_dig that is a ruby function' do
@@ -1765,6 +2461,7 @@ describe "The lookup function" do
1765
2461
  param 'Array[String[1]]', :segments
1766
2462
  param 'Hash[String,Any]', :options
1767
2463
  param 'Puppet::LookupContext', :context
2464
+ return_type 'Puppet::LookupValue'
1768
2465
  end
1769
2466
 
1770
2467
  def ruby_dig(segments, options, context)
@@ -1813,6 +2510,11 @@ describe "The lookup function" do
1813
2510
  'hiera.yaml' => <<-YAML.unindent,
1814
2511
  ---
1815
2512
  version: 5
2513
+ defaults:
2514
+ options:
2515
+ option_b:
2516
+ z: Default option value b.z
2517
+
1816
2518
  hierarchy:
1817
2519
  - name: "Common"
1818
2520
  data_dig: mod_a::ruby_dig
@@ -1822,7 +2524,9 @@ describe "The lookup function" do
1822
2524
  option_b:
1823
2525
  x: Option value b.x
1824
2526
  y: Option value b.y
1825
- YAML
2527
+ - name: "Extra"
2528
+ data_dig: mod_a::ruby_dig
2529
+ YAML
1826
2530
  }
1827
2531
  }
1828
2532
  end
@@ -1844,11 +2548,13 @@ describe "The lookup function" do
1844
2548
  end
1845
2549
 
1846
2550
  it 'does not accept return of runtime type from function' do
1847
- expect(explain('mod_a::bad_type')).to include('Value returned from Hierarchy entry "Common" has wrong type')
2551
+ # Message is produced by the called function, not by the lookup framework
2552
+ expect(explain('mod_a::bad_type')).to include("value returned from function 'ruby_dig' has wrong type")
1848
2553
  end
1849
2554
 
1850
2555
  it 'does not accept return of runtime type embedded in hash from function' do
1851
- expect(explain('mod_a::bad_type_in_hash')).to include('Value returned from Hierarchy entry "Common" has wrong type')
2556
+ # Message is produced by the called function, not by the lookup framework
2557
+ expect(explain('mod_a::bad_type_in_hash')).to include("value returned from function 'ruby_dig' has wrong type")
1852
2558
  end
1853
2559
 
1854
2560
  it 'will not merge hashes from environment and module unless strategy hash is used' do
@@ -1859,6 +2565,22 @@ describe "The lookup function" do
1859
2565
  expect(lookup('mod_a::options.option_b.x')).to eql('Option value b.x')
1860
2566
  end
1861
2567
 
2568
+ it 'default options are passed to the function' do
2569
+ expect(lookup('mod_a::options.option_b.z')).to eql('Default option value b.z')
2570
+ end
2571
+
2572
+ it 'default options are not merged with hierarchy options' do
2573
+ expect(lookup('mod_a::options')).to eql(
2574
+ {
2575
+ 'option_a' => 'Option value a',
2576
+ 'option_b' => {
2577
+ 'y' => 'Option value b.y',
2578
+ 'x' => 'Option value b.x'
2579
+ },
2580
+ 'uri' => 'http://www.example.com/passed/as/option'
2581
+ })
2582
+ end
2583
+
1862
2584
  it 'hierarchy entry "uri" is passed as location option to the function' do
1863
2585
  expect(lookup('mod_a::options.uri')).to eql('http://www.example.com/passed/as/option')
1864
2586
  end
@@ -1878,6 +2600,125 @@ describe "The lookup function" do
1878
2600
  end
1879
2601
  end
1880
2602
  end
2603
+
2604
+ context 'that has a default_hierarchy' do
2605
+ let(:mod_a_hiera_yaml) { <<-YAML.unindent }
2606
+ version: 5
2607
+ hierarchy:
2608
+ - name: "Common"
2609
+ path: common.yaml
2610
+ - name: "Common 2"
2611
+ path: common2.yaml
2612
+
2613
+ default_hierarchy:
2614
+ - name: "Default"
2615
+ path: defaults.yaml
2616
+ - name: "Default 2"
2617
+ path: defaults2.yaml
2618
+ YAML
2619
+
2620
+ let(:mod_a_common) { <<-YAML.unindent }
2621
+ mod_a::a: value mod_a::a (from module)
2622
+ mod_a::d:
2623
+ a: value mod_a::d.a (from module)
2624
+ mod_a::f:
2625
+ a:
2626
+ a: value mod_a::f.a.a (from module)
2627
+ lookup_options:
2628
+ mod_a::e:
2629
+ merge: deep
2630
+ YAML
2631
+
2632
+
2633
+ let(:mod_a_common2) { <<-YAML.unindent }
2634
+ mod_a::b: value mod_a::b (from module)
2635
+ mod_a::d:
2636
+ c: value mod_a::d.c (from module)
2637
+ mod_a::f:
2638
+ a:
2639
+ b: value mod_a::f.a.b (from module)
2640
+ YAML
2641
+
2642
+ let(:mod_a_defaults) { <<-YAML.unindent }
2643
+ mod_a::a: value mod_a::a (from module defaults)
2644
+ mod_a::b: value mod_a::b (from module defaults)
2645
+ mod_a::c: value mod_a::c (from module defaults)
2646
+ mod_a::d:
2647
+ b: value mod_a::d.b (from module defaults)
2648
+ mod_a::e:
2649
+ a:
2650
+ a: value mod_a::e.a.a (from module defaults)
2651
+ mod_a::g:
2652
+ a:
2653
+ a: value mod_a::g.a.a (from module defaults)
2654
+ lookup_options:
2655
+ mod_a::d:
2656
+ merge: hash
2657
+ mod_a::g:
2658
+ merge: deep
2659
+ YAML
2660
+
2661
+ let(:mod_a_defaults2) { <<-YAML.unindent }
2662
+ mod_a::e:
2663
+ a:
2664
+ b: value mod_a::e.a.b (from module defaults)
2665
+ mod_a::g:
2666
+ a:
2667
+ b: value mod_a::g.a.b (from module defaults)
2668
+ YAML
2669
+
2670
+ let(:mod_a_files) do
2671
+ {
2672
+ 'mod_a' => {
2673
+ 'data' => {
2674
+ 'common.yaml' => mod_a_common,
2675
+ 'common2.yaml' => mod_a_common2,
2676
+ 'defaults.yaml' => mod_a_defaults,
2677
+ 'defaults2.yaml' => mod_a_defaults2
2678
+ },
2679
+ 'hiera.yaml' => mod_a_hiera_yaml
2680
+ }
2681
+ }
2682
+ end
2683
+
2684
+ it 'the default hierarchy does not interfere with environment hierarchy' do
2685
+ expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)')
2686
+ end
2687
+
2688
+ it 'the default hierarchy does not interfere with regular hierarchy in module' do
2689
+ expect(lookup('mod_a::b')).to eql('value mod_a::b (from module)')
2690
+ end
2691
+
2692
+ it 'the default hierarchy is consulted when no value is found elsewhere' do
2693
+ expect(lookup('mod_a::c')).to eql('value mod_a::c (from module defaults)')
2694
+ end
2695
+
2696
+ it 'the default hierarchy does not participate in a merge' do
2697
+ expect(lookup('mod_a::d', 'merge' => 'hash')).to eql('a' => 'value mod_a::d.a (from module)', 'c' => 'value mod_a::d.c (from module)')
2698
+ end
2699
+
2700
+ it 'lookup_options from regular hierarchy does not effect values found in the default hierarchy' do
2701
+ expect(lookup('mod_a::e')).to eql('a' => { 'a' => 'value mod_a::e.a.a (from module defaults)' })
2702
+ end
2703
+
2704
+ it 'lookup_options from default hierarchy affects values found in the default hierarchy' do
2705
+ expect(lookup('mod_a::g')).to eql('a' => { 'a' => 'value mod_a::g.a.a (from module defaults)', 'b' => 'value mod_a::g.a.b (from module defaults)'})
2706
+ end
2707
+
2708
+ it 'merge parameter does not override lookup_options defined in the default hierarchy' do
2709
+ expect(lookup('mod_a::g', 'merge' => 'hash')).to eql(
2710
+ 'a' => { 'a' => 'value mod_a::g.a.a (from module defaults)', 'b' => 'value mod_a::g.a.b (from module defaults)'})
2711
+ end
2712
+
2713
+ it 'lookup_options from default hierarchy does not effect values found in the regular hierarchy' do
2714
+ expect(lookup('mod_a::d')).to eql('a' => 'value mod_a::d.a (from module)')
2715
+ end
2716
+
2717
+ it 'the default hierarchy lookup is included in the explain output' do
2718
+ explanation = explain('mod_a::c')
2719
+ expect(explanation).to match(/Searching default_hierarchy of module "mod_a".+Original path: "defaults.yaml"/m)
2720
+ end
2721
+ end
1881
2722
  end
1882
2723
 
1883
2724
  context 'and an eyaml lookup_key function' do
@@ -1968,6 +2809,7 @@ describe "The lookup function" do
1968
2809
  let(:data_files) do
1969
2810
  {
1970
2811
  'common.eyaml' => <<-YAML.unindent
2812
+ # a: Encrypted value 'a' (from environment)
1971
2813
  a: >
1972
2814
  ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEw
1973
2815
  DQYJKoZIhvcNAQEBBQAEggEAUwwNRA5ZKM87SLnjnJfzDFRQbeheSYMTOhcr
@@ -1981,6 +2823,7 @@ describe "The lookup function" do
1981
2823
  dCILO7I8QqU=]
1982
2824
  hash_a:
1983
2825
  "hash_%{ipl_suffix}":
2826
+ # aaa: Encrypted value hash_a.hash_aa.aaa (from environment)
1984
2827
  aaa: >
1985
2828
  ENC[PKCS7,MIIBqQYJKoZIhvcNAQcDoIIBmjCCAZYCAQAxggEhMIIBHQIBADAFMAACAQEw
1986
2829
  DQYJKoZIhvcNAQEBBQAEggEAhvGXL5RxVUs9wdqJvpCyXtfCHrm2HbG/u30L
@@ -1993,6 +2836,7 @@ describe "The lookup function" do
1993
2836
  ovm/gEB4oPlYJswoXuWqcEBfwZzbpy96x3b2Le/yoa72ylbPAUc5GfLENvFQ
1994
2837
  zXpTtSmQE0fixY4JMaBTke65ZRvoiOQO]
1995
2838
  array_a:
2839
+ # - "array_a[0]"
1996
2840
  - >
1997
2841
  ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEw
1998
2842
  DQYJKoZIhvcNAQEBBQAEggEAmXZfyfU77vVCZqHpR10qhD0Jy9DpMGBgal97
@@ -2003,6 +2847,7 @@ describe "The lookup function" do
2003
2847
  MieIkHj93bX3gIEcenECLdWaEzcPa7MHgl6zevQKg4H0JVmcvKYyfHYqcrVE
2004
2848
  PqizKDA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDf259KZEay1widVSFy
2005
2849
  I9zGgBAICjm0x2GeqoCnHdiAA+jt]
2850
+ # - "array_a[1]"
2006
2851
  - >
2007
2852
  ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEw
2008
2853
  DQYJKoZIhvcNAQEBBQAEggEATVy4hHG356INFKOswAhoravh66iJljp+Vn3o
@@ -2013,6 +2858,17 @@ describe "The lookup function" do
2013
2858
  t22zpYK4J8lgCBV2gKfrOWSi9MAs6JhCeOb8wNLMmAUTbc0WrFJxoCwAPX0z
2014
2859
  MAjsNjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBC4v4bNE4gFlbLmVY+9
2015
2860
  BtSLgBBm7U0wu6d6s9wF9Ek9IHPe]
2861
+ # ref_a: "A resolved = '%{hiera('a')}'"
2862
+ ref_a: >
2863
+ ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQEw
2864
+ DQYJKoZIhvcNAQEBBQAEggEAFSuUp+yk+oaA7b5ekT0u360CQ9Q2sIQ/bTcM
2865
+ jT3XLjm8HIGYPcysOEnuo8WcAxJFY5iya4yQ7Y/UhMWXaTi7Vzv/6BmyPDwz
2866
+ +7Z2Mf0r0PvS5+ylue6aem/3bXPOmXTKTf68OCehTRXlDUs8/av9gnsDzojp
2867
+ yiUTBZvKxhIP2n//GyoHgyATveHT0lxPVpdMycB347DtWS7IduCxx0+KiOOw
2868
+ DXYFlYbIVxVInwgERxtsfYSr+Fu0/mkjtRsQm+dPzMQOATE9Val2gGKsV6bi
2869
+ kdm1OM9HrwVsFj6Lma6FYmr89Bcm/1uEc8fiOMtNK3z2+nwunWBMNCGneMYD
2870
+ C5IJejBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAeiZDGQyXHkZlV5ceT
2871
+ iCxpgCDDatuVvbPEEi8rKOC7xhPHZ22zLEEV//l7C9jxq+DZcA==]
2016
2872
  YAML
2017
2873
  }
2018
2874
  end
@@ -2027,6 +2883,10 @@ describe "The lookup function" do
2027
2883
  expect(lookup('hash_a')).to include('hash_aa')
2028
2884
  end
2029
2885
 
2886
+ it 'evaluates interpolations in encrypted values' do
2887
+ expect(lookup('ref_a')).to eql("A resolved = 'Encrypted value 'a' (from environment)'")
2888
+ end
2889
+
2030
2890
  it 'can read encrypted values inside a hash' do
2031
2891
  expect(lookup('hash_a.hash_aa.aaa')).to eql('Encrypted value hash_a.hash_aa.aaa (from environment)')
2032
2892
  end