class Puppet::Pops::Lookup::LookupAdapter
A LookupAdapter is a specialized DataAdapter that uses its hash to store data providers. It also remembers the compiler that it is attached to and maintains a cache of _lookup options_ retrieved from the data providers associated with the compiler's environment.
@api private
Constants
- CONVERT_TO
- GLOBAL_ENV_MERGE
- HASH
- LOOKUP_OPTIONS_PATTERN_START
- LOOKUP_OPTIONS_PREFIX
- MERGE
- NEW
- PROVIDER_STACK
Public Class Methods
# File lib/puppet/pops/lookup/lookup_adapter.rb 23 def self.create_adapter(compiler) 24 new(compiler) 25 end
Puppet::Pops::Lookup::DataAdapter::new
# File lib/puppet/pops/lookup/lookup_adapter.rb 27 def initialize(compiler) 28 super() 29 @compiler = compiler 30 @lookup_options = {} 31 # Get a KeyRecorder from context, and set a "null recorder" if not defined 32 @key_recorder = Puppet.lookup(:lookup_key_recorder) { KeyRecorder.singleton } 33 end
Public Instance Methods
Performs a possible conversion of the result of calling `the_lookup` lambda The conversion takes place if there is a 'convert_to' key in the lookup_options If there is no conversion, the result of calling `the_lookup` is returned otherwise the successfully converted value. Errors are raised if the convert_to is faulty (bad type string, or if a call to new(T, <args>) fails.
@param key [String] The key to lookup @param lookup_options [Hash] a hash of options @param lookup_invocation [Invocation] the lookup invocation @param the_lookup [Lambda] zero arg lambda that performs the lookup of a value @return [Object] the looked up value, or converted value if there was conversion @throw :no_such_key when the object is not found (if thrown by `the_lookup`)
# File lib/puppet/pops/lookup/lookup_adapter.rb 98 def convert_result(key, lookup_options, lookup_invocation, the_lookup) 99 result = the_lookup.call 100 convert_to = lookup_options[CONVERT_TO] 101 return result if convert_to.nil? 102 103 convert_to = convert_to.is_a?(Array) ? convert_to : [convert_to] 104 if convert_to[0].is_a?(String) 105 begin 106 convert_to[0] = Puppet::Pops::Types::TypeParser.singleton.parse(convert_to[0]) 107 rescue StandardError => e 108 raise Puppet::DataBinding::LookupError, 109 _("Invalid data type in lookup_options for key '%{key}' could not parse '%{source}', error: '%{msg}") % 110 { key: key, source: convert_to[0], msg: e.message} 111 end 112 end 113 begin 114 result = lookup_invocation.scope.call_function(NEW, [convert_to[0], result, *convert_to[1..-1]]) 115 # TRANSLATORS 'lookup_options', 'convert_to' and args_string variable should not be translated, 116 args_string = Puppet::Pops::Types::StringConverter.singleton.convert(convert_to) 117 lookup_invocation.report_text { _("Applying convert_to lookup_option with arguments %{args}") % { args: args_string } } 118 rescue StandardError => e 119 raise Puppet::DataBinding::LookupError, 120 _("The convert_to lookup_option for key '%{key}' raised error: %{msg}") % 121 { key: key, msg: e.message} 122 end 123 result 124 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 246 def extract_lookup_options_for_key(key, options) 247 return nil if options.nil? 248 249 rk = key.root_key 250 key_opts = options[0] 251 unless key_opts.nil? 252 key_opt = key_opts[rk] 253 return key_opt unless key_opt.nil? 254 end 255 256 patterns = options[1] 257 patterns.each_pair { |pattern, value| return value if pattern =~ rk } unless patterns.nil? 258 nil 259 end
@return [Pathname] the full path of the hiera.yaml config file
# File lib/puppet/pops/lookup/lookup_adapter.rb 269 def global_hiera_config_path 270 @global_hiera_config_path ||= Pathname.new(Puppet.settings[:hiera_config]) 271 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 280 def global_only? 281 instance_variable_defined?(:@global_only) ? @global_only : false 282 end
@param lookup_invocation [Puppet::Pops::Lookup::Invocation] the lookup invocation @return [Boolean] `true` if an environment data provider version 5 is configured
# File lib/puppet/pops/lookup/lookup_adapter.rb 263 def has_environment_data_provider?(lookup_invocation) 264 ep = env_provider(lookup_invocation) 265 ep.nil? ? false : ep.config(lookup_invocation).version >= 5 266 end
Performs a lookup using global, environment, and module data providers. Merge the result using the given merge strategy. If the merge strategy is nil, then an attempt is made to find merge options in the `lookup_options` hash for an entry associated with the key. If no options are found, the no merge is performed and the first found entry is returned.
@param key [String] The key to lookup @param lookup_invocation [Invocation] the lookup invocation @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies “first found”) @return [Object] the found object @throw :no_such_key when the object is not found
# File lib/puppet/pops/lookup/lookup_adapter.rb 46 def lookup(key, lookup_invocation, merge) 47 # The 'lookup_options' key is reserved and not found as normal data 48 if key == LOOKUP_OPTIONS || key.start_with?(LOOKUP_OPTIONS_PREFIX) 49 lookup_invocation.with(:invalid_key, LOOKUP_OPTIONS) do 50 throw :no_such_key 51 end 52 end 53 54 # Record that the key was looked up. This will record all keys for which a lookup is performed 55 # except 'lookup_options' (since that is illegal from a user perspective, 56 # and from an impact perspective is always looked up). 57 @key_recorder.record(key) 58 59 key = LookupKey.new(key) 60 lookup_invocation.lookup(key, key.module_name) do 61 if lookup_invocation.only_explain_options? 62 catch(:no_such_key) { do_lookup(LookupKey::LOOKUP_OPTIONS, lookup_invocation, HASH) } 63 nil 64 else 65 lookup_options = lookup_lookup_options(key, lookup_invocation) || {} 66 67 if merge.nil? 68 # Used cached lookup_options 69 # merge = lookup_merge_options(key, lookup_invocation) 70 merge = lookup_options[MERGE] 71 lookup_invocation.report_merge_source(LOOKUP_OPTIONS) unless merge.nil? 72 end 73 convert_result(key.to_s, lookup_options, lookup_invocation, lambda do 74 lookup_invocation.with(:data, key.to_s) do 75 catch(:no_such_key) { return do_lookup(key, lookup_invocation, merge) } 76 throw :no_such_key if lookup_invocation.global_only? 77 key.dig(lookup_invocation, lookup_default_in_module(key, lookup_invocation)) 78 end 79 end) 80 end 81 end 82 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 179 def lookup_default_in_module(key, lookup_invocation) 180 module_name = lookup_invocation.module_name 181 182 # Do not attempt to do a lookup in a module unless the name is qualified. 183 throw :no_such_key if module_name.nil? 184 185 provider = module_provider(lookup_invocation, module_name) 186 throw :no_such_key if provider.nil? || !provider.config(lookup_invocation).has_default_hierarchy? 187 188 lookup_invocation.with(:scope, "Searching default_hierarchy of module \"#{module_name}\"") do 189 merge_strategy = nil 190 if merge_strategy.nil? 191 @module_default_lookup_options ||= {} 192 options = @module_default_lookup_options.fetch(module_name) do |k| 193 meta_invocation = Invocation.new(lookup_invocation.scope) 194 meta_invocation.lookup(LookupKey::LOOKUP_OPTIONS, k) do 195 opts = nil 196 lookup_invocation.with(:scope, "Searching for \"#{LookupKey::LOOKUP_OPTIONS}\"") do 197 catch(:no_such_key) do 198 opts = compile_patterns( 199 validate_lookup_options( 200 provider.key_lookup_in_default(LookupKey::LOOKUP_OPTIONS, meta_invocation, MergeStrategy.strategy(HASH)), k)) 201 end 202 end 203 @module_default_lookup_options[k] = opts 204 end 205 end 206 lookup_options = extract_lookup_options_for_key(key, options) 207 merge_strategy = lookup_options[MERGE] unless lookup_options.nil? 208 end 209 210 lookup_invocation.with(:scope, "Searching for \"#{key}\"") do 211 provider.key_lookup_in_default(key, lookup_invocation, merge_strategy) 212 end 213 end 214 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 126 def lookup_global(key, lookup_invocation, merge_strategy) 127 # hiera_xxx will always use global_provider regardless of data_binding_terminus setting 128 terminus = lookup_invocation.hiera_xxx_call? ? :hiera : Puppet[:data_binding_terminus] 129 case terminus 130 when :hiera, 'hiera' 131 provider = global_provider(lookup_invocation) 132 throw :no_such_key if provider.nil? 133 provider.key_lookup(key, lookup_invocation, merge_strategy) 134 when :none, 'none', '', nil 135 # If global lookup is disabled, immediately report as not found 136 lookup_invocation.report_not_found(key) 137 throw :no_such_key 138 else 139 lookup_invocation.with(:global, terminus) do 140 catch(:no_such_key) do 141 return lookup_invocation.report_found(key, Puppet::DataBinding.indirection.find(key.root_key, 142 {:environment => environment, :variables => lookup_invocation.scope, :merge => merge_strategy})) 143 end 144 lookup_invocation.report_not_found(key) 145 throw :no_such_key 146 end 147 end 148 rescue Puppet::DataBinding::LookupError => detail 149 raise detail unless detail.issue_code.nil? 150 error = Puppet::Error.new(_("Lookup of key '%{key}' failed: %{detail}") % { key: lookup_invocation.top_key, detail: detail.message }) 151 error.set_backtrace(detail.backtrace) 152 raise error 153 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 155 def lookup_in_environment(key, lookup_invocation, merge_strategy) 156 provider = env_provider(lookup_invocation) 157 throw :no_such_key if provider.nil? 158 provider.key_lookup(key, lookup_invocation, merge_strategy) 159 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 161 def lookup_in_module(key, lookup_invocation, merge_strategy) 162 module_name = lookup_invocation.module_name 163 164 # Do not attempt to do a lookup in a module unless the name is qualified. 165 throw :no_such_key if module_name.nil? 166 167 provider = module_provider(lookup_invocation, module_name) 168 if provider.nil? 169 if environment.module(module_name).nil? 170 lookup_invocation.report_module_not_found(module_name) 171 else 172 lookup_invocation.report_module_provider_not_found(module_name) 173 end 174 throw :no_such_key 175 end 176 provider.key_lookup(key, lookup_invocation, merge_strategy) 177 end
Retrieve the lookup options that match the given `name`.
@param key [LookupKey] The key for which we want lookup options @param lookup_invocation [Puppet::Pops::Lookup::Invocation] the lookup invocation @return [String,Hash,nil] The found lookup options or nil
# File lib/puppet/pops/lookup/lookup_adapter.rb 233 def lookup_lookup_options(key, lookup_invocation) 234 module_name = key.module_name 235 236 # Retrieve the options for the module. We use nil as a key in case we have no module 237 if !@lookup_options.include?(module_name) 238 options = retrieve_lookup_options(module_name, lookup_invocation, MergeStrategy.strategy(HASH)) 239 @lookup_options[module_name] = options 240 else 241 options = @lookup_options[module_name] 242 end 243 extract_lookup_options_for_key(key, options) 244 end
Retrieve the merge options that match the given `name`.
@param key [LookupKey] The key for which we want merge options @param lookup_invocation [Invocation] the lookup invocation @return [String,Hash,nil] The found merge options or nil
# File lib/puppet/pops/lookup/lookup_adapter.rb 222 def lookup_merge_options(key, lookup_invocation) 223 lookup_options = lookup_lookup_options(key, lookup_invocation) 224 lookup_options.nil? ? nil : lookup_options[MERGE] 225 end
@param path [String] the absolute path name of the global hiera.yaml file. @return [LookupAdapter] self
# File lib/puppet/pops/lookup/lookup_adapter.rb 275 def set_global_hiera_config_path(path) 276 @global_hiera_config_path = Pathname.new(path) 277 self 278 end
Instructs the lookup framework to only perform lookups in the global layer @return [LookupAdapter] self
# File lib/puppet/pops/lookup/lookup_adapter.rb 286 def set_global_only 287 @global_only = true 288 self 289 end
Private Instance Methods
# File lib/puppet/pops/lookup/lookup_adapter.rb 314 def compile_patterns(options) 315 return nil if options.nil? 316 key_options = {} 317 pattern_options = {} 318 options.each_pair do |key, value| 319 if key.start_with?(LOOKUP_OPTIONS_PATTERN_START) 320 pattern_options[Regexp.compile(key)] = value 321 else 322 key_options[key] = value 323 end 324 end 325 [key_options.empty? ? nil : key_options, pattern_options.empty? ? nil : pattern_options] 326 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 328 def do_lookup(key, lookup_invocation, merge) 329 if lookup_invocation.global_only? 330 key.dig(lookup_invocation, lookup_global(key, lookup_invocation, merge)) 331 else 332 merge_strategy = Puppet::Pops::MergeStrategy.strategy(merge) 333 key.dig(lookup_invocation, 334 merge_strategy.lookup(PROVIDER_STACK, lookup_invocation) { |m| send(m, key, lookup_invocation, merge_strategy) }) 335 end 336 end
Retrieve and cache lookup options specific to the environment of the compiler that this adapter is attached to (i.e. a merge of global and environment lookup options).
# File lib/puppet/pops/lookup/lookup_adapter.rb 381 def env_lookup_options(lookup_invocation, merge_strategy) 382 if !instance_variable_defined?(:@env_lookup_options) 383 global_options = global_lookup_options(lookup_invocation, merge_strategy) 384 @env_only_lookup_options = nil 385 catch(:no_such_key) { @env_only_lookup_options = validate_lookup_options(lookup_in_environment(LookupKey::LOOKUP_OPTIONS, lookup_invocation, merge_strategy), nil) } 386 if global_options.nil? 387 @env_lookup_options = @env_only_lookup_options 388 elsif @env_only_lookup_options.nil? 389 @env_lookup_options = global_options 390 else 391 @env_lookup_options = merge_strategy.merge(global_options, @env_only_lookup_options) 392 end 393 end 394 @env_lookup_options 395 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 402 def env_provider(lookup_invocation) 403 @env_provider = initialize_env_provider(lookup_invocation) unless instance_variable_defined?(:@env_provider) 404 @env_provider 405 end
@return [Puppet::Node::Environment] the environment of the compiler that this adapter is associated with
# File lib/puppet/pops/lookup/lookup_adapter.rb 519 def environment 520 @compiler.environment 521 end
Retrieve and cache the global lookup options
# File lib/puppet/pops/lookup/lookup_adapter.rb 371 def global_lookup_options(lookup_invocation, merge_strategy) 372 if !instance_variable_defined?(:@global_lookup_options) 373 @global_lookup_options = nil 374 catch(:no_such_key) { @global_lookup_options = validate_lookup_options(lookup_global(LookupKey::LOOKUP_OPTIONS, lookup_invocation, merge_strategy), nil) } 375 end 376 @global_lookup_options 377 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 397 def global_provider(lookup_invocation) 398 @global_provider = GlobalDataProvider.new unless instance_variable_defined?(:@global_provider) 399 @global_provider 400 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 463 def initialize_env_provider(lookup_invocation) 464 env_conf = environment.configuration 465 return nil if env_conf.nil? || env_conf.path_to_env.nil? 466 467 # Get the name of the data provider from the environment's configuration 468 provider_name = env_conf.environment_data_provider 469 env_path = Pathname(env_conf.path_to_env) 470 config_path = env_path + HieraConfig::CONFIG_FILE_NAME 471 472 ep = nil 473 if config_path.exist? 474 ep = EnvironmentDataProvider.new 475 # A version 5 hiera.yaml trumps any data provider setting in the environment.conf 476 ep_config = ep.config(lookup_invocation) 477 if ep_config.nil? 478 ep = nil 479 elsif ep_config.version >= 5 480 unless provider_name.nil? || Puppet[:strict] == :off 481 Puppet.warn_once('deprecations', 'environment.conf#data_provider', 482 _("Defining environment_data_provider='%{provider_name}' in environment.conf is deprecated") % { provider_name: provider_name }, env_path + 'environment.conf') 483 484 unless provider_name == 'hiera' 485 Puppet.warn_once('deprecations', 'environment.conf#data_provider_overridden', 486 _("The environment_data_provider='%{provider_name}' setting is ignored since '%{config_path}' version >= 5") % { provider_name: provider_name, config_path: config_path }, env_path + 'environment.conf') 487 end 488 end 489 provider_name = nil 490 end 491 end 492 493 if provider_name.nil? 494 ep 495 else 496 unless Puppet[:strict] == :off 497 msg = _("Defining environment_data_provider='%{provider_name}' in environment.conf is deprecated.") % { provider_name: provider_name } 498 msg += " " + _("A '%{hiera_config}' file should be used instead") % { hiera_config: HieraConfig::CONFIG_FILE_NAME } if ep.nil? 499 Puppet.warn_once('deprecations', 'environment.conf#data_provider', msg, env_path + 'environment.conf') 500 end 501 502 case provider_name 503 when 'none' 504 nil 505 when 'hiera' 506 # Use hiera.yaml or default settings if it is missing 507 ep || EnvironmentDataProvider.new 508 when 'function' 509 ep = EnvironmentDataProvider.new 510 ep.config = HieraConfigV5.v4_function_config(env_path, 'environment::data', ep) 511 ep 512 else 513 raise Puppet::Error.new(_("Environment '%{env}', cannot find environment_data_provider '%{provider}'") % { env: environment.name, provider: provider_name }) 514 end 515 end 516 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 416 def initialize_module_provider(lookup_invocation, module_name) 417 mod = environment.module(module_name) 418 return nil if mod.nil? 419 420 metadata = mod.metadata 421 provider_name = metadata.nil? ? nil : metadata['data_provider'] 422 423 mp = nil 424 if mod.has_hiera_conf? 425 mp = ModuleDataProvider.new(module_name) 426 # A version 5 hiera.yaml trumps a data provider setting in the module 427 mp_config = mp.config(lookup_invocation) 428 if mp_config.nil? 429 mp = nil 430 elsif mp_config.version >= 5 431 unless provider_name.nil? || Puppet[:strict] == :off 432 Puppet.warn_once('deprecations', "metadata.json#data_provider-#{module_name}", 433 _("Defining \"data_provider\": \"%{name}\" in metadata.json is deprecated. It is ignored since a '%{config}' with version >= 5 is present") % { name: provider_name, config: HieraConfig::CONFIG_FILE_NAME }, mod.metadata_file) 434 end 435 provider_name = nil 436 end 437 end 438 439 if provider_name.nil? 440 mp 441 else 442 unless Puppet[:strict] == :off 443 msg = _("Defining \"data_provider\": \"%{name}\" in metadata.json is deprecated.") % { name: provider_name } 444 msg += " " + _("A '%{hiera_config}' file should be used instead") % { hiera_config: HieraConfig::CONFIG_FILE_NAME } if mp.nil? 445 Puppet.warn_once('deprecations', "metadata.json#data_provider-#{module_name}", msg, mod.metadata_file) 446 end 447 448 case provider_name 449 when 'none' 450 nil 451 when 'hiera' 452 mp || ModuleDataProvider.new(module_name) 453 when 'function' 454 mp = ModuleDataProvider.new(module_name) 455 mp.config = HieraConfig.v4_function_config(Pathname(mod.path), "#{module_name}::data", mp) 456 mp 457 else 458 raise Puppet::Error.new(_("Environment '%{env}', cannot find module_data_provider '%{provider}'")) % { env: environment.name, provider: provider_name } 459 end 460 end 461 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 407 def module_provider(lookup_invocation, module_name) 408 # Test if the key is present for the given module_name. It might be there even if the 409 # value is nil (which indicates that no module provider is configured for the given name) 410 unless self.include?(module_name) 411 self[module_name] = initialize_module_provider(lookup_invocation, module_name) 412 end 413 self[module_name] 414 end
Retrieve lookup options that applies when using a specific module (i.e. a merge of the pre-cached `env_lookup_options` and the module specific data)
# File lib/puppet/pops/lookup/lookup_adapter.rb 342 def retrieve_lookup_options(module_name, lookup_invocation, merge_strategy) 343 meta_invocation = Invocation.new(lookup_invocation.scope) 344 meta_invocation.lookup(LookupKey::LOOKUP_OPTIONS, lookup_invocation.module_name) do 345 meta_invocation.with(:meta, LOOKUP_OPTIONS) do 346 if meta_invocation.global_only? 347 compile_patterns(global_lookup_options(meta_invocation, merge_strategy)) 348 else 349 opts = env_lookup_options(meta_invocation, merge_strategy) 350 unless module_name.nil? 351 # Store environment options at key nil. This removes the need for an additional lookup for keys that are not prefixed. 352 @lookup_options[nil] = compile_patterns(opts) unless @lookup_options.include?(nil) 353 catch(:no_such_key) do 354 module_opts = validate_lookup_options(lookup_in_module(LookupKey::LOOKUP_OPTIONS, meta_invocation, merge_strategy), module_name) 355 opts = if opts.nil? 356 module_opts 357 elsif module_opts 358 merge_strategy.lookup([GLOBAL_ENV_MERGE, "Module #{lookup_invocation.module_name}"], meta_invocation) do |n| 359 meta_invocation.with(:scope, n) { meta_invocation.report_found(LOOKUP_OPTIONS, n == GLOBAL_ENV_MERGE ? opts : module_opts) } 360 end 361 end 362 end 363 end 364 compile_patterns(opts) 365 end 366 end 367 end 368 end
# File lib/puppet/pops/lookup/lookup_adapter.rb 295 def validate_lookup_options(options, module_name) 296 return nil if options.nil? 297 raise Puppet::DataBinding::LookupError.new(_("value of %{opts} must be a hash") % { opts: LOOKUP_OPTIONS }) unless options.is_a?(Hash) 298 return options if module_name.nil? 299 300 pfx = "#{module_name}::" 301 options.each_pair do |key, value| 302 if key.start_with?(LOOKUP_OPTIONS_PATTERN_START) 303 unless key[1..pfx.length] == pfx 304 raise Puppet::DataBinding::LookupError.new(_("all %{opts} patterns must match a key starting with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }) 305 end 306 else 307 unless key.start_with?(pfx) 308 raise Puppet::DataBinding::LookupError.new(_("all %{opts} keys must start with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }) 309 end 310 end 311 end 312 end