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
@@ -3,36 +3,32 @@ module Lookup
3
3
  # @api private
4
4
  module DataProvider
5
5
  def self.key_type
6
- ensure_types_initialized
7
6
  @key_type
8
7
  end
9
8
 
10
9
  def self.value_type
11
- ensure_types_initialized
12
10
  @value_type
13
11
  end
14
12
 
15
- def self.ensure_types_initialized
16
- if @key_type.nil?
17
- (@key_type, @value_type) = Pcore::register_aliases(
18
- # The Pcore type for all keys and subkeys in a data hash.
19
- 'Puppet::LookupKey' => 'Variant[String,Numeric]',
13
+ def self.register_types(loader)
14
+ (@key_type, @value_type) = Pcore::register_aliases({
15
+ # The Pcore type for all keys and subkeys in a data hash.
16
+ 'Puppet::LookupKey' => 'Variant[String,Numeric]',
20
17
 
21
- # The Pcore type for all values and sub-values in a data hash. The
22
- # type is self-recursive to enforce the same constraint on values contained
23
- # in arrays and hashes
24
- 'Puppet::LookupValue' => <<-PUPPET
25
- Variant[
26
- Scalar,
27
- Undef,
28
- Sensitive,
29
- Type,
30
- Hash[Puppet::LookupKey, Puppet::LookupValue],
31
- Array[Puppet::LookupValue]
32
- ]
18
+ # The Pcore type for all values and sub-values in a data hash. The
19
+ # type is self-recursive to enforce the same constraint on values contained
20
+ # in arrays and hashes
21
+ 'Puppet::LookupValue' => <<-PUPPET
22
+ Variant[
23
+ Scalar,
24
+ Undef,
25
+ Sensitive,
26
+ Type,
27
+ Hash[Puppet::LookupKey, Puppet::LookupValue],
28
+ Array[Puppet::LookupValue]
29
+ ]
33
30
  PUPPET
34
- )
35
- end
31
+ }, Pcore::RUNTIME_NAME_AUTHORITY, loader)
36
32
  end
37
33
 
38
34
  # Performs a lookup with an endless recursion check.
@@ -45,6 +41,17 @@ module DataProvider
45
41
  lookup_invocation.check(key.to_s) { unchecked_key_lookup(key, lookup_invocation, merge) }
46
42
  end
47
43
 
44
+ # Performs a lookup using a module default hierarchy with an endless recursion check. All providers except
45
+ # the `ModuleDataProvider` will throw `:no_such_key` if this method is called.
46
+ #
47
+ # @param key [LookupKey] The key to lookup
48
+ # @param lookup_invocation [Invocation] The current lookup invocation
49
+ # @param merge [MergeStrategy,String,Hash{String=>Object},nil] Merge strategy or hash with strategy and options
50
+ #
51
+ def key_lookup_in_default(key, lookup_invocation, merge)
52
+ throw :no_such_key
53
+ end
54
+
48
55
  def lookup(key, lookup_invocation, merge)
49
56
  lookup_invocation.check(key.to_s) { unchecked_key_lookup(key, lookup_invocation, merge) }
50
57
  end
@@ -70,23 +77,30 @@ module DataProvider
70
77
  raise NotImplementedError, "Subclass of #{DataProvider.name} must implement 'name' method"
71
78
  end
72
79
 
73
- # Asserts that _data_hash_ is a hash.
80
+ # @returns `true` if the value provided by this instance can always be trusted, `false` otherwise
81
+ def value_is_validated?
82
+ false
83
+ end
84
+
85
+ # Asserts that _data_hash_ is a hash. Will yield to obtain origin of value in case an error is produced
74
86
  #
75
- # @param data_provider [DataProvider] The data provider that produced the hash
76
87
  # @param data_hash [Hash{String=>Object}] The data hash
77
88
  # @return [Hash{String=>Object}] The data hash
78
- def validate_data_hash(data_provider, data_hash)
79
- Types::TypeAsserter.assert_instance_of(nil, Types::PHashType::DEFAULT, data_hash) { "Value returned from #{data_provider.name}" }
89
+ def validate_data_hash(data_hash, &block)
90
+ Types::TypeAsserter.assert_instance_of(nil, Types::PHashType::DEFAULT, data_hash, &block)
80
91
  end
81
92
 
82
- # Asserts that _data_value_ is of valid type.
93
+ # Asserts that _data_value_ is of valid type. Will yield to obtain origin of value in case an error is produced
83
94
  #
84
95
  # @param data_provider [DataProvider] The data provider that produced the hash
85
- # @param data_value [Object] The data value
86
96
  # @return [Object] The data value
87
- def validate_data_value(data_provider, value)
97
+ def validate_data_value(value, &block)
88
98
  # The DataProvider.value_type is self recursive so further recursive check of collections is needed here
89
- Types::TypeAsserter.assert_instance_of(nil, DataProvider.value_type, value) { "Value returned from #{data_provider.name}" }
99
+ unless value_is_validated? || DataProvider.value_type.instance?(value)
100
+ actual_type = Types::TypeCalculator.singleton.infer(value)
101
+ raise Types::TypeAssertionError.new("#{yield} has wrong type, expects Puppet::LookupValue, got #{actual_type}", DataProvider.value_type, actual_type)
102
+ end
103
+ value
90
104
  end
91
105
  end
92
106
  end
@@ -15,7 +15,7 @@ class EnvironmentDataProvider < ConfiguredDataProvider
15
15
  config
16
16
  else
17
17
  if Puppet[:strict] == :error
18
- raise Puppet::DataBinding::LookupError, "#{config.name} cannot be used in an environment"
18
+ config.fail(Issues::HIERA_VERSION_3_NOT_GLOBAL, :where => 'environment')
19
19
  else
20
20
  Puppet.warn_once(:hiera_v3_at_env_root, config.config_path, 'hiera.yaml version 3 found at the environment root was ignored')
21
21
  end
@@ -444,6 +444,7 @@ module Lookup
444
444
  def dump_on(io, indent, first_indent)
445
445
  io << indent << @name << "\n"
446
446
  indent = increase_indent(indent)
447
+ branches.each {|b| b.dump_on(io, indent, indent)}
447
448
  dump_outcome(io, indent)
448
449
  end
449
450
 
@@ -10,6 +10,15 @@ class FunctionProvider
10
10
 
11
11
  attr_reader :parent_data_provider, :function_name, :locations
12
12
 
13
+ # Returns the type that all the return type of all functions must be assignable to.
14
+ # For `lookup_key` and `data_dig`, that will be the `Puppet::LookupValue` type. For
15
+ # `data_hash` it will be a Hash[Puppet::LookupKey,Puppet::LookupValue]`
16
+ #
17
+ # @return [Type] the trusted return type
18
+ def self.trusted_return_type
19
+ DataProvider.value_type
20
+ end
21
+
13
22
  def initialize(name, parent_data_provider, function_name, options, locations)
14
23
  @name = name
15
24
  @parent_data_provider = parent_data_provider
@@ -25,8 +34,7 @@ class FunctionProvider
25
34
  end
26
35
 
27
36
  def create_function_context(lookup_invocation)
28
- scope = lookup_invocation.scope
29
- FunctionContext.new(EnvironmentContext.adapt(scope.compiler.environment), module_name, function(scope))
37
+ FunctionContext.new(EnvironmentContext.adapt(lookup_invocation.scope.compiler.environment), module_name, function(lookup_invocation))
30
38
  end
31
39
 
32
40
  def module_name
@@ -37,6 +45,10 @@ class FunctionProvider
37
45
  "Hierarchy entry \"#{@name}\""
38
46
  end
39
47
 
48
+ def full_name
49
+ "#{self.class::TAG} function '#{@function_name}'"
50
+ end
51
+
40
52
  def to_s
41
53
  name
42
54
  end
@@ -46,9 +58,8 @@ class FunctionProvider
46
58
  # @param [Pathname,URI] location The location to add to the options
47
59
  # @return [Hash{String => Object}] The options hash
48
60
  def options(location = nil)
61
+ location = location.location unless location.nil?
49
62
  case location
50
- when nil
51
- @options
52
63
  when Pathname
53
64
  @options.merge(HieraConfig::KEY_PATH => location.to_s)
54
65
  when URI
@@ -58,14 +69,18 @@ class FunctionProvider
58
69
  end
59
70
  end
60
71
 
72
+ def value_is_validated?
73
+ @value_is_validated
74
+ end
75
+
61
76
  private
62
77
 
63
- def function(scope)
64
- @function ||= load_function(scope)
78
+ def function(lookup_invocation)
79
+ @function ||= load_function(lookup_invocation)
65
80
  end
66
81
 
67
- def load_function(scope)
68
- loaders = scope.compiler.loaders
82
+ def load_function(lookup_invocation)
83
+ loaders = lookup_invocation.scope.compiler.loaders
69
84
  typed_name = Loader::TypedName.new(:function, @function_name)
70
85
  loader = if typed_name.qualified?
71
86
  qualifier = typed_name.name_parts[0]
@@ -75,10 +90,20 @@ class FunctionProvider
75
90
  end
76
91
  te = loader.load_typed(typed_name)
77
92
  if te.nil? || te.value.nil?
78
- raise Puppet::DataBinding::LookupError,
79
- "#{@options[HieraConfig::KEY_NAME]}: Unable to find '#{self.class::TAG}' function named '#{function_name}'"
93
+ @parent_data_provider.config(lookup_invocation).fail(Issues::HIERA_DATA_PROVIDER_FUNCTION_NOT_FOUND,
94
+ :function_type => self.class::TAG, :function_name => @function_name)
95
+ end
96
+ func = te.value
97
+ @value_is_validated = func.class.dispatcher.dispatchers.all? do |dispatcher|
98
+ rt = dispatcher.type.return_type
99
+ if rt.nil?
100
+ false
101
+ else
102
+ Types::TypeAsserter.assert_assignable(nil, self.class.trusted_return_type, rt) { "Return type of '#{self.class::TAG}' function named '#{function_name}'" }
103
+ true
104
+ end
80
105
  end
81
- te.value
106
+ func
82
107
  end
83
108
  end
84
109
  end
@@ -15,11 +15,16 @@ class GlobalDataProvider < ConfiguredDataProvider
15
15
  # Hiera version 3 needs access to special scope variables
16
16
  scope = lookup_invocation.scope
17
17
  unless scope.is_a?(Hiera::Scope)
18
- lookup_invocation = Invocation.new(
18
+ hiera_invocation = Invocation.new(
19
19
  Hiera::Scope.new(scope),
20
20
  lookup_invocation.override_values,
21
21
  lookup_invocation.default_values,
22
22
  lookup_invocation.explainer)
23
+
24
+ # Confine to global scope unless an environment data provider has been defined (same as for hiera_xxx functions)
25
+ adapter = lookup_invocation.lookup_adapter
26
+ hiera_invocation.set_global_only unless adapter.global_only? || adapter.has_environment_data_provider?(lookup_invocation)
27
+ lookup_invocation = hiera_invocation
23
28
  end
24
29
 
25
30
  merge = MergeStrategy.strategy(merge)
@@ -32,9 +37,17 @@ class GlobalDataProvider < ConfiguredDataProvider
32
37
  end
33
38
 
34
39
  value = super(key, lookup_invocation, merge)
35
- if lookup_invocation.hiera_xxx_call? && (merge.is_a?(HashMergeStrategy) || merge.is_a?(DeepMergeStrategy))
36
- # hiera_hash calls should error when found values are not hashes
37
- Types::TypeAsserter.assert_instance_of('value', Types::PHashType::DEFAULT, value)
40
+ if lookup_invocation.hiera_xxx_call?
41
+ if merge.is_a?(HashMergeStrategy) || merge.is_a?(DeepMergeStrategy)
42
+ # hiera_hash calls should error when found values are not hashes
43
+ Types::TypeAsserter.assert_instance_of('value', Types::PHashType::DEFAULT, value)
44
+ end
45
+ if !key.segments.nil? && (merge.is_a?(HashMergeStrategy) || merge.is_a?(UniqueMergeStrategy))
46
+ strategy = merge.is_a?(HashMergeStrategy) ? 'hash' : 'array'
47
+
48
+ # Fail with old familiar message from Hiera 3
49
+ raise Puppet::DataBinding::LookupError, "Resolution type :#{strategy} is illegal when accessing values using dotted keys. Offending key was '#{key}'"
50
+ end
38
51
  end
39
52
  value
40
53
  else
@@ -45,7 +58,7 @@ class GlobalDataProvider < ConfiguredDataProvider
45
58
  protected
46
59
 
47
60
  def assert_config_version(config)
48
- raise Puppet::DataBinding::LookupError, "#{config.name} cannot be used in the global layer" if config.version == 4
61
+ config.fail(Issues::HIERA_UNSUPPORTED_VERSION_IN_GLOBAL) if config.version == 4
49
62
  config
50
63
  end
51
64
 
@@ -22,6 +22,16 @@ class ScopeLookupCollectingInvocation < Invocation
22
22
  @scope_interpolations.uniq! { |si| si[0] }
23
23
  @scope_interpolations
24
24
  end
25
+
26
+ # Yield invocation that remembers all but the given name
27
+ def with_local_memory_eluding(name)
28
+ save_si = @scope_interpolations
29
+ @scope_interpolations = []
30
+ result = yield
31
+ save_si.concat(@scope_interpolations.reject { |entry| entry[1] == name })
32
+ @scope_interpolations = save_si
33
+ result
34
+ end
25
35
  end
26
36
 
27
37
  # @api private
@@ -34,11 +44,13 @@ class HieraConfig
34
44
  KEY_NAME = 'name'.freeze
35
45
  KEY_VERSION = 'version'.freeze
36
46
  KEY_DATADIR = 'datadir'.freeze
47
+ KEY_DEFAULT_HIERARCHY = 'default_hierarchy'.freeze
37
48
  KEY_HIERARCHY = 'hierarchy'.freeze
38
49
  KEY_LOGGER = 'logger'.freeze
39
50
  KEY_OPTIONS = 'options'.freeze
40
51
  KEY_PATH = 'path'.freeze
41
52
  KEY_PATHS = 'paths'.freeze
53
+ KEY_MAPPED_PATHS = 'mapped_paths'.freeze
42
54
  KEY_GLOB = 'glob'.freeze
43
55
  KEY_GLOBS = 'globs'.freeze
44
56
  KEY_URI = 'uri'.freeze
@@ -56,7 +68,7 @@ class HieraConfig
56
68
 
57
69
  FUNCTION_KEYS = [KEY_DATA_HASH, KEY_LOOKUP_KEY, KEY_DATA_DIG, KEY_V3_BACKEND]
58
70
  ALL_FUNCTION_KEYS = FUNCTION_KEYS + [KEY_V4_DATA_HASH]
59
- LOCATION_KEYS = [KEY_PATH, KEY_PATHS, KEY_GLOB, KEY_GLOBS, KEY_URI, KEY_URIS]
71
+ LOCATION_KEYS = [KEY_PATH, KEY_PATHS, KEY_GLOB, KEY_GLOBS, KEY_URI, KEY_URIS, KEY_MAPPED_PATHS]
60
72
  FUNCTION_PROVIDERS = {
61
73
  KEY_DATA_HASH => DataHashFunctionProvider,
62
74
  KEY_DATA_DIG => DataDigFunctionProvider,
@@ -67,7 +79,7 @@ class HieraConfig
67
79
  KEY_V4_DATA_HASH => V4DataHashFunctionProvider
68
80
  }
69
81
 
70
- def self.v4_function_config(config_root, function_name)
82
+ def self.v4_function_config(config_root, function_name, owner)
71
83
  unless Puppet[:strict] == :off
72
84
  Puppet.warn_once(:deprecation, 'legacy_provider_function',
73
85
  "Using of legacy data provider function '#{function_name}'. Please convert to a 'data_hash' function")
@@ -81,7 +93,8 @@ class HieraConfig
81
93
  KEY_V4_DATA_HASH => function_name
82
94
  }
83
95
  ]
84
- }.freeze
96
+ }.freeze,
97
+ owner
85
98
  )
86
99
  end
87
100
 
@@ -106,8 +119,9 @@ class HieraConfig
106
119
  #
107
120
  # @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults
108
121
  # @param config_path [Pathname] Absolute path to the configuration file
122
+ # @param owner [ConfiguredDataProvider] The data provider that will own the created configuration
109
123
  # @return [LookupConfiguration] the configuration
110
- def self.create(lookup_invocation, config_path)
124
+ def self.create(lookup_invocation, config_path, owner)
111
125
  if config_path.is_a?(Hash)
112
126
  config_path = nil
113
127
  loaded_config = config_path
@@ -137,13 +151,15 @@ class HieraConfig
137
151
  version = version.nil? ? 3 : version.to_i
138
152
  case version
139
153
  when 5
140
- HieraConfigV5.new(config_root, config_path, loaded_config)
154
+ HieraConfigV5.new(config_root, config_path, loaded_config, owner)
141
155
  when 4
142
- HieraConfigV4.new(config_root, config_path, loaded_config)
156
+ HieraConfigV4.new(config_root, config_path, loaded_config, owner)
143
157
  when 3
144
- HieraConfigV3.new(config_root, config_path, loaded_config)
158
+ HieraConfigV3.new(config_root, config_path, loaded_config, owner)
145
159
  else
146
- raise Puppet::DataBinding::LookupError, "#{@config_path}: This runtime does not support #{CONFIG_FILE_NAME} version '#{version}'"
160
+ issue = Issues::HIERA_UNSUPPORTED_VERSION
161
+ raise Puppet::DataBinding::LookupError.new(
162
+ issue.format(:version => version), config_path, nil, nil, nil, issue.issue_code)
147
163
  end
148
164
  end
149
165
 
@@ -154,29 +170,76 @@ class HieraConfig
154
170
  #
155
171
  # @param config_path [Pathname] Absolute path to the configuration
156
172
  # @param loaded_config [Hash] the loaded configuration
157
- def initialize(config_root, config_path, loaded_config)
173
+ def initialize(config_root, config_path, loaded_config, owner)
158
174
  @config_root = config_root
159
175
  @config_path = config_path
160
176
  @loaded_config = loaded_config
161
- @config = validate_config(self.class.symkeys_to_string(@loaded_config))
177
+ @config = validate_config(self.class.symkeys_to_string(@loaded_config), owner)
162
178
  @data_providers = nil
163
179
  end
164
180
 
181
+ def fail(issue, args = EMPTY_HASH, line = nil)
182
+ raise Puppet::DataBinding::LookupError.new(
183
+ issue.format(args.merge(:label => self)), @config_path, line, nil, nil, issue.issue_code)
184
+ end
185
+
186
+ def has_default_hierarchy?
187
+ false
188
+ end
189
+
165
190
  # Returns the data providers for this config
166
191
  #
167
192
  # @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults
168
193
  # @param parent_data_provider [DataProvider] The data provider that loaded this configuration
169
194
  # @return [Array<DataProvider>] the data providers
170
- def configured_data_providers(lookup_invocation, parent_data_provider)
195
+ def configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy = false)
171
196
  unless @data_providers && scope_interpolations_stable?(lookup_invocation)
172
197
  if @data_providers
173
198
  lookup_invocation.report_text { 'Hiera configuration recreated due to change of scope variables used in interpolation expressions' }
174
199
  end
175
200
  slc_invocation = ScopeLookupCollectingInvocation.new(lookup_invocation.scope)
176
- @data_providers = create_configured_data_providers(slc_invocation, parent_data_provider)
201
+ begin
202
+ @data_providers = create_configured_data_providers(slc_invocation, parent_data_provider, false)
203
+ if has_default_hierarchy?
204
+ @default_data_providers = create_configured_data_providers(slc_invocation, parent_data_provider, true)
205
+ end
206
+ rescue StandardError => e
207
+ # Raise a LookupError with a RUNTIME_ERROR issue to prevent this being translated to an evaluation error triggered in the pp file
208
+ # where the lookup started
209
+ if e.message =~ /^Undefined variable '([^']+)'/
210
+ var = $1
211
+ fail(Issues::HIERA_UNDEFINED_VARIABLE, { :name => var }, find_line_matching(/%\{['"]?#{var}['"]?}/))
212
+ end
213
+ raise e
214
+ end
177
215
  @scope_interpolations = slc_invocation.scope_interpolations
178
216
  end
179
- @data_providers
217
+ use_default_hierarchy ? @default_data_providers : @data_providers
218
+ end
219
+
220
+ # Find first line in configuration that matches regexp after given line. Comments are stripped
221
+ def find_line_matching(regexp, start_line = 1)
222
+ line_number = 0
223
+ File.foreach(@config_path) do |line|
224
+ line_number += 1
225
+ next if line_number < start_line
226
+ quote = nil
227
+ stripped = ''
228
+ line.each_codepoint do |cp|
229
+ if cp == 0x22 || cp == 0x27 # double or single quote
230
+ if quote == cp
231
+ quote = nil
232
+ elsif quote.nil?
233
+ quote = cp
234
+ end
235
+ elsif cp == 0x23 # unquoted hash mark
236
+ break
237
+ end
238
+ stripped << cp
239
+ end
240
+ return line_number if stripped =~ regexp
241
+ end
242
+ nil
180
243
  end
181
244
 
182
245
  def scope_interpolations_stable?(lookup_invocation)
@@ -188,7 +251,7 @@ class HieraConfig
188
251
  @scope_interpolations.all? do |key, root_key, segments, old_value|
189
252
  value = scope[root_key]
190
253
  unless value.nil? || segments.empty?
191
- found = '';
254
+ found = nil;
192
255
  catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) }
193
256
  value = found;
194
257
  end
@@ -199,11 +262,11 @@ class HieraConfig
199
262
  end
200
263
 
201
264
  # @api private
202
- def create_configured_data_providers(lookup_invocation, parent_data_provider)
265
+ def create_configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy)
203
266
  self.class.not_implemented(self, 'create_configured_data_providers')
204
267
  end
205
268
 
206
- def validate_config(config)
269
+ def validate_config(config, owner)
207
270
  self.class.not_implemented(self, 'validate_config')
208
271
  end
209
272
 
@@ -298,7 +361,7 @@ class HieraConfigV3 < HieraConfig
298
361
  }
299
362
  end
300
363
 
301
- def create_configured_data_providers(lookup_invocation, parent_data_provider)
364
+ def create_configured_data_providers(lookup_invocation, parent_data_provider, _)
302
365
  scope = lookup_invocation.scope
303
366
  unless scope.is_a?(Hiera::Scope)
304
367
  lookup_invocation = Invocation.new(
@@ -312,7 +375,15 @@ class HieraConfigV3 < HieraConfig
312
375
  data_providers = {}
313
376
 
314
377
  [@config[KEY_BACKENDS]].flatten.each do |backend|
315
- raise Puppet::DataBinding::LookupError, "#{@config_path}: Backend '#{backend}' defined more than once" if data_providers.include?(backend)
378
+ if data_providers.include?(backend)
379
+ first_line = find_line_matching(/[^\w]#{backend}(?:[^\w]|$)/)
380
+ line = find_line_matching(/[^\w]#{backend}(?:[^\w]|$)/, first_line + 1) if first_line
381
+ unless line
382
+ line = first_line
383
+ first_line = nil
384
+ end
385
+ fail(Issues::HIERA_BACKEND_MULTIPLY_DEFINED, { :name => backend, :first_line => first_line }, line)
386
+ end
316
387
  original_paths = [@config[KEY_HIERARCHY]].flatten
317
388
  backend_config = @config[backend]
318
389
  if backend_config.nil?
@@ -348,7 +419,7 @@ class HieraConfigV3 < HieraConfig
348
419
  KEY_MERGE_BEHAVIOR => 'native'
349
420
  }
350
421
 
351
- def validate_config(config)
422
+ def validate_config(config, owner)
352
423
  unless Puppet[:strict] == :off
353
424
  Puppet.warn_once(:deprecation, 'hiera.yaml',
354
425
  "#{@config_path}: Use of 'hiera.yaml' version 3 is deprecated. It should be converted to version 5", config_path.to_s)
@@ -423,7 +494,9 @@ class HieraConfigV4 < HieraConfig
423
494
  def factory_create_data_provider(lookup_invocation, name, parent_data_provider, provider_name, datadir, original_paths)
424
495
  service_type = Registry.hash_of_path_based_data_provider_factories
425
496
  provider_factory = Puppet.lookup(:injector).lookup(nil, service_type, PATH_BASED_DATA_PROVIDER_FACTORIES_KEY)[provider_name]
426
- raise Puppet::DataBinding::LookupError, "#{@config_path}: No data provider is registered for backend '#{provider_name}' " unless provider_factory
497
+ unless provider_factory
498
+ fail(Issues::HIERA_NO_PROVIDER_FOR_BACKEND, { :name => provider_name }, find_line_matching(/[^\w]#{provider_name}(?:[^\w]|$)/))
499
+ end
427
500
 
428
501
  paths = original_paths.map { |path| interpolate(path, lookup_invocation, false) }
429
502
  paths = provider_factory.resolve_paths(datadir, original_paths, paths, lookup_invocation)
@@ -437,13 +510,21 @@ class HieraConfigV4 < HieraConfig
437
510
  end
438
511
  end
439
512
 
440
- def create_configured_data_providers(lookup_invocation, parent_data_provider)
513
+ def create_configured_data_providers(lookup_invocation, parent_data_provider, _)
441
514
  default_datadir = @config[KEY_DATADIR]
442
515
  data_providers = {}
443
516
 
444
517
  @config[KEY_HIERARCHY].each do |he|
445
518
  name = he[KEY_NAME]
446
- raise Puppet::DataBinding::LookupError, "#{@config_path}: Name '#{name}' defined more than once" if data_providers.include?(name)
519
+ if data_providers.include?(name)
520
+ first_line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/)
521
+ line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/, first_line + 1) if first_line
522
+ unless line
523
+ line = first_line
524
+ first_line = nil
525
+ end
526
+ fail(Issues::HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED, { :name => name, :first_line => first_line }, line)
527
+ end
447
528
  original_paths = he[KEY_PATHS] || [he[KEY_PATH] || name]
448
529
  datadir = @config_root + (he[KEY_DATADIR] || default_datadir)
449
530
  provider_name = he[KEY_BACKEND]
@@ -461,7 +542,7 @@ class HieraConfigV4 < HieraConfig
461
542
  data_providers.values
462
543
  end
463
544
 
464
- def validate_config(config)
545
+ def validate_config(config, owner)
465
546
  unless Puppet[:strict] == :off
466
547
  Puppet.warn_once(:deprecation, 'hiera.yaml',
467
548
  "#{@config_path}: Use of 'hiera.yaml' version 4 is deprecated. It should be converted to version 5", config_path.to_s)
@@ -489,6 +570,25 @@ class HieraConfigV5 < HieraConfig
489
570
  # The option name must start with a letter and end with a letter or digit. May contain underscore and dash.
490
571
  option_name_t = tf.pattern(/\A[A-Za-z](:?[0-9A-Za-z_-]*[0-9A-Za-z])?\z/)
491
572
 
573
+ hierarchy_t = tf.array_of(tf.struct(
574
+ {
575
+ KEY_NAME => nes_t,
576
+ tf.optional(KEY_OPTIONS) => tf.hash_kv(option_name_t, tf.data),
577
+ tf.optional(KEY_DATA_HASH) => nes_t,
578
+ tf.optional(KEY_LOOKUP_KEY) => nes_t,
579
+ tf.optional(KEY_V3_BACKEND) => nes_t,
580
+ tf.optional(KEY_V4_DATA_HASH) => nes_t,
581
+ tf.optional(KEY_DATA_DIG) => nes_t,
582
+ tf.optional(KEY_PATH) => nes_t,
583
+ tf.optional(KEY_PATHS) => tf.array_of(nes_t, tf.range(1, :default)),
584
+ tf.optional(KEY_GLOB) => nes_t,
585
+ tf.optional(KEY_GLOBS) => tf.array_of(nes_t, tf.range(1, :default)),
586
+ tf.optional(KEY_URI) => uri_t,
587
+ tf.optional(KEY_URIS) => tf.array_of(uri_t, tf.range(1, :default)),
588
+ tf.optional(KEY_MAPPED_PATHS) => tf.array_of(nes_t, tf.range(3, 3)),
589
+ tf.optional(KEY_DATADIR) => nes_t
590
+ }))
591
+
492
592
  @@CONFIG_TYPE = tf.struct({
493
593
  KEY_VERSION => tf.range(5, 5),
494
594
  tf.optional(KEY_DEFAULTS) => tf.struct(
@@ -496,38 +596,41 @@ class HieraConfigV5 < HieraConfig
496
596
  tf.optional(KEY_DATA_HASH) => nes_t,
497
597
  tf.optional(KEY_LOOKUP_KEY) => nes_t,
498
598
  tf.optional(KEY_DATA_DIG) => nes_t,
499
- tf.optional(KEY_DATADIR) => nes_t
500
- }),
501
- tf.optional(KEY_HIERARCHY) => tf.array_of(tf.struct(
502
- {
503
- KEY_NAME => nes_t,
599
+ tf.optional(KEY_DATADIR) => nes_t,
504
600
  tf.optional(KEY_OPTIONS) => tf.hash_kv(option_name_t, tf.data),
505
- tf.optional(KEY_DATA_HASH) => nes_t,
506
- tf.optional(KEY_LOOKUP_KEY) => nes_t,
507
- tf.optional(KEY_V3_BACKEND) => nes_t,
508
- tf.optional(KEY_V4_DATA_HASH) => nes_t,
509
- tf.optional(KEY_DATA_DIG) => nes_t,
510
- tf.optional(KEY_PATH) => nes_t,
511
- tf.optional(KEY_PATHS) => tf.array_of(nes_t, tf.range(1, :default)),
512
- tf.optional(KEY_GLOB) => nes_t,
513
- tf.optional(KEY_GLOBS) => tf.array_of(nes_t, tf.range(1, :default)),
514
- tf.optional(KEY_URI) => uri_t,
515
- tf.optional(KEY_URIS) => tf.array_of(uri_t, tf.range(1, :default)),
516
- tf.optional(KEY_DATADIR) => nes_t
517
- }))
601
+ }),
602
+ tf.optional(KEY_HIERARCHY) => hierarchy_t,
603
+ tf.optional(KEY_DEFAULT_HIERARCHY) => hierarchy_t
518
604
  })
519
605
  end
520
606
 
521
- def create_configured_data_providers(lookup_invocation, parent_data_provider)
607
+ def create_configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy)
522
608
  defaults = @config[KEY_DEFAULTS] || EMPTY_HASH
523
609
  datadir = defaults[KEY_DATADIR] || 'data'
524
610
 
525
611
  # Hashes enumerate their values in the order that the corresponding keys were inserted so it's safe to use
526
612
  # a hash for the data_providers.
527
613
  data_providers = {}
528
- @config[KEY_HIERARCHY].each do |he|
614
+
615
+ if @config.include?(KEY_DEFAULT_HIERARCHY)
616
+ unless parent_data_provider.is_a?(ModuleDataProvider)
617
+ fail(Issues::HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE, EMPTY_HASH, find_line_matching(/\s+default_hierarchy:/))
618
+ end
619
+ elsif use_default_hierarchy
620
+ return data_providers
621
+ end
622
+
623
+ @config[use_default_hierarchy ? KEY_DEFAULT_HIERARCHY : KEY_HIERARCHY].each do |he|
529
624
  name = he[KEY_NAME]
530
- raise Puppet::DataBinding::LookupError, "#{@config_path}: Name '#{name}' defined more than once" if data_providers.include?(name)
625
+ if data_providers.include?(name)
626
+ first_line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/)
627
+ line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/, first_line + 1) if first_line
628
+ unless line
629
+ line = first_line
630
+ first_line = nil
631
+ end
632
+ fail(Issues::HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED, { :name => name, :first_line => first_line }, line)
633
+ end
531
634
  function_kind = ALL_FUNCTION_KEYS.find { |key| he.include?(key) }
532
635
  if function_kind.nil?
533
636
  function_kind = FUNCTION_KEYS.find { |key| defaults.include?(key) }
@@ -552,22 +655,15 @@ class HieraConfigV5 < HieraConfig
552
655
  expand_uris(he[location_key], lookup_invocation)
553
656
  when KEY_URI
554
657
  expand_uris([he[location_key]], lookup_invocation)
658
+ when KEY_MAPPED_PATHS
659
+ expand_mapped_paths(entry_datadir, he[location_key], lookup_invocation)
555
660
  else
556
661
  nil
557
662
  end
558
663
  next if @config_path.nil? && !locations.nil? && locations.empty? # Default config and no existing paths found
559
- options = he[KEY_OPTIONS]
664
+ options = he[KEY_OPTIONS] || defaults[KEY_OPTIONS]
560
665
  options = options.nil? ? EMPTY_HASH : interpolate(options, lookup_invocation, false)
561
666
  if(function_kind == KEY_V3_BACKEND)
562
- unless parent_data_provider.is_a?(GlobalDataProvider)
563
- # hiera3_backend is not allowed in environments and modules
564
- raise Puppet::DataBinding::LookupError, "#{@config_path}: '#{KEY_V3_BACKEND}' is only allowed in the global layer"
565
- end
566
-
567
- if function_name == 'json' || function_name == 'yaml' || function_name == 'hocon' && Puppet.features.hocon?
568
- # Disallow use of backends that have corresponding "data_hash" functions in version 5
569
- raise Puppet::DataBinding::LookupError, "#{@config_path}: Use \"#{KEY_DATA_HASH}: #{function_name}_data\" instead of \"#{KEY_V3_BACKEND}: #{function_name}\""
570
- end
571
667
  v3options = { :datadir => entry_datadir.to_s }
572
668
  options.each_pair { |k, v| v3options[k.to_sym] = v }
573
669
  data_providers[name] = create_hiera3_backend_provider(name, function_name, parent_data_provider, entry_datadir, locations, {
@@ -587,6 +683,10 @@ class HieraConfigV5 < HieraConfig
587
683
  data_providers.values
588
684
  end
589
685
 
686
+ def has_default_hierarchy?
687
+ @config.include?(KEY_DEFAULT_HIERARCHY)
688
+ end
689
+
590
690
  RESERVED_OPTION_KEYS = ['path', 'uri'].freeze
591
691
 
592
692
  DEFAULT_CONFIG_HASH = {
@@ -603,46 +703,59 @@ class HieraConfigV5 < HieraConfig
603
703
  ]
604
704
  }.freeze
605
705
 
606
- def validate_config(config)
706
+ def validate_config(config, owner)
607
707
  config[KEY_DEFAULTS] ||= DEFAULT_CONFIG_HASH[KEY_DEFAULTS]
608
708
  config[KEY_HIERARCHY] ||= DEFAULT_CONFIG_HASH[KEY_HIERARCHY]
609
709
 
610
710
  Types::TypeAsserter.assert_instance_of(["The Lookup Configuration at '%s'", @config_path], self.class.config_type, config)
611
711
  defaults = config[KEY_DEFAULTS]
612
712
  validate_defaults(defaults) unless defaults.nil?
613
- config[KEY_HIERARCHY].each do |he|
614
- name = he[KEY_NAME]
615
- case ALL_FUNCTION_KEYS.count { |key| he.include?(key) }
616
- when 0
617
- if defaults.nil? || FUNCTION_KEYS.count { |key| defaults.include?(key) } == 0
618
- raise Puppet::DataBinding::LookupError,
619
- "#{@config_path}: One of #{combine_strings(FUNCTION_KEYS)} must defined in hierarchy '#{name}'"
620
- end
621
- when 1
622
- # OK
623
- when 0
624
- raise Puppet::DataBinding::LookupError,
625
- "#{@config_path}: One of #{combine_strings(FUNCTION_KEYS)} must defined in hierarchy '#{name}'"
626
- else
627
- raise Puppet::DataBinding::LookupError,
628
- "#{@config_path}: Only one of #{combine_strings(FUNCTION_KEYS)} can be defined in hierarchy '#{name}'"
713
+ config[KEY_HIERARCHY].each { |he| validate_hierarchy(he, defaults, owner) }
714
+
715
+ if config.include?(KEY_DEFAULT_HIERARCHY)
716
+ unless owner.is_a?(ModuleDataProvider)
717
+ fail(Issues::HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE, EMPTY_HASH, find_line_matching(/(?:^|\s+)#{KEY_DEFAULT_HIERARCHY}:/))
718
+ end
719
+ config[KEY_DEFAULT_HIERARCHY].each { |he| validate_hierarchy(he, defaults, owner) }
720
+ end
721
+ config
722
+ end
723
+
724
+ def validate_hierarchy(he, defaults, owner)
725
+ name = he[KEY_NAME]
726
+ case ALL_FUNCTION_KEYS.count { |key| he.include?(key) }
727
+ when 0
728
+ if defaults.nil? || FUNCTION_KEYS.count { |key| defaults.include?(key) } == 0
729
+ fail(Issues::HIERA_MISSING_DATA_PROVIDER_FUNCTION, :name => name)
629
730
  end
731
+ when 1
732
+ # OK
733
+ else
734
+ fail(Issues::HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS, :name => name)
735
+ end
630
736
 
631
- if LOCATION_KEYS.count { |key| he.include?(key) } > 1
632
- raise Puppet::DataBinding::LookupError,
633
- "#{@config_path}: Only one of #{combine_strings(LOCATION_KEYS)} can be defined in hierarchy '#{name}'"
737
+ v3_backend = he[KEY_V3_BACKEND]
738
+ unless v3_backend.nil?
739
+ unless owner.is_a?(GlobalDataProvider)
740
+ fail(Issues::HIERA_V3_BACKEND_NOT_GLOBAL, EMPTY_HASH, find_line_matching(/\s+#{KEY_V3_BACKEND}:/))
634
741
  end
742
+ if v3_backend == 'json' || v3_backend == 'yaml' || v3_backend == 'hocon' && Puppet.features.hocon?
743
+ # Disallow use of backends that have corresponding "data_hash" functions in version 5
744
+ fail(Issues::HIERA_V3_BACKEND_REPLACED_BY_DATA_HASH, { :function_name => v3_backend },
745
+ find_line_matching(/\s+#{KEY_V3_BACKEND}:\s*['"]?#{v3_backend}(?:[^\w]|$)/))
746
+ end
747
+ end
635
748
 
636
- options = he[KEY_OPTIONS]
637
- unless options.nil?
638
- RESERVED_OPTION_KEYS.each do |key|
639
- if options.include?(key)
640
- raise Puppet::DataBinding::LookupError, "#{@config_path}: Option key '#{key}' used in hierarchy '#{name}' is reserved by Puppet"
641
- end
642
- end
749
+ if LOCATION_KEYS.count { |key| he.include?(key) } > 1
750
+ fail(Issues::HIERA_MULTIPLE_LOCATION_SPECS, :name => name)
751
+ end
752
+
753
+ options = he[KEY_OPTIONS]
754
+ unless options.nil?
755
+ RESERVED_OPTION_KEYS.each do |key|
756
+ fail(Issues::HIERA_OPTION_RESERVED_BY_PUPPET, :key => key, :name => name) if options.include?(key)
643
757
  end
644
758
  end
645
- config
646
759
  end
647
760
 
648
761
  def validate_defaults(defaults)
@@ -650,8 +763,14 @@ class HieraConfigV5 < HieraConfig
650
763
  when 0, 1
651
764
  # OK
652
765
  else
653
- raise Puppet::DataBinding::LookupError,
654
- "#{@config_path}: Only one of #{combine_strings(FUNCTION_KEYS)} can be defined in defaults"
766
+ fail(Issues::HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS_IN_DEFAULT)
767
+ end
768
+
769
+ options = defaults[KEY_OPTIONS]
770
+ unless options.nil?
771
+ RESERVED_OPTION_KEYS.each do |key|
772
+ fail(Issues::HIERA_DEFAULT_OPTION_RESERVED_BY_PUPPET, :key => key) if options.include?(key)
773
+ end
655
774
  end
656
775
  end
657
776