File: //usr/lib/ruby/site_ruby/1.8/puppet/pops/binder/lookup.rb
# This class is the backing implementation of the Puppet function 'lookup'.
# See puppet/parser/functions/lookup.rb for documentation.
#
class Puppet::Pops::Binder::Lookup
def self.parse_lookup_args(args)
options = {}
pblock = if args[-1].respond_to?(:puppet_lambda)
args.pop
end
case args.size
when 1
# name, or all options
if args[ 0 ].is_a?(Hash)
options = to_symbolic_hash(args[ 0 ])
else
options[ :name ] = args[ 0 ]
end
when 2
# name and type, or name and options
if args[ 1 ].is_a?(Hash)
options = to_symbolic_hash(args[ 1 ])
options[:name] = args[ 0 ] # silently overwrite option with given name
else
options[:name] = args[ 0 ]
options[:type] = args[ 1 ]
end
when 3
# name, type, default (no options)
options[ :name ] = args[ 0 ]
options[ :type ] = args[ 1 ]
options[ :default ] = args[ 2 ]
else
raise Puppet::ParseError, "The lookup function accepts 1-3 arguments, got #{args.size}"
end
options[:pblock] = pblock
options
end
def self.to_symbolic_hash(input)
names = [:name, :type, :default, :accept_undef, :extra, :override]
options = {}
names.each {|n| options[n] = undef_as_nil(input[n.to_s] || input[n]) }
options
end
private_class_method :to_symbolic_hash
def self.type_mismatch(type_calculator, expected, got)
"has wrong type, expected #{type_calculator.string(expected)}, got #{type_calculator.string(got)}"
end
private_class_method :type_mismatch
def self.fail(msg)
raise Puppet::ParseError, "Function lookup() " + msg
end
private_class_method :fail
def self.fail_lookup(names)
name_part = if names.size == 1
"the name '#{names[0]}'"
else
"any of the names ['" + names.join(', ') + "']"
end
fail("did not find a value for #{name_part}")
end
private_class_method :fail_lookup
def self.validate_options(options, type_calculator)
type_parser = Puppet::Pops::Types::TypeParser.new
name_type = type_parser.parse('Variant[Array[String], String]')
if is_nil_or_undef?(options[:name]) || options[:name].is_a?(Array) && options[:name].empty?
fail ("requires a name, or array of names. Got nothing to lookup.")
end
t = type_calculator.infer(options[:name])
if ! type_calculator.assignable?(name_type, t)
fail("given 'name' argument, #{type_mismatch(type_calculator, name_type, t)}")
end
# unless a type is already given (future case), parse the type (or default 'Data'), fails if invalid type is given
unless options[:type].is_a?(Puppet::Pops::Types::PAnyType)
options[:type] = type_parser.parse(options[:type] || 'Data')
end
# default value must comply with the given type
if options[:default]
t = type_calculator.infer(options[:default])
if ! type_calculator.assignable?(options[:type], t)
fail("'default' value #{type_mismatch(type_calculator, options[:type], t)}")
end
end
if options[:extra] && !options[:extra].is_a?(Hash)
# do not perform inference here, it is enough to know that it is not a hash
fail("'extra' value must be a Hash, got #{options[:extra].class}")
end
options[:extra] = {} unless options[:extra]
if options[:override] && !options[:override].is_a?(Hash)
# do not perform inference here, it is enough to know that it is not a hash
fail("'override' value must be a Hash, got #{options[:extra].class}")
end
options[:override] = {} unless options[:override]
end
private_class_method :validate_options
def self.nil_as_undef(x)
x.nil? ? :undef : x
end
private_class_method :nil_as_undef
def self.undef_as_nil(x)
is_nil_or_undef?(x) ? nil : x
end
private_class_method :undef_as_nil
def self.is_nil_or_undef?(x)
x.nil? || x == :undef
end
private_class_method :is_nil_or_undef?
# This is used as a marker - a value that cannot (at least not easily) by mistake be found in
# hiera data.
#
class PrivateNotFoundMarker; end
def self.search_for(scope, type, name, options)
# search in order, override, injector, hiera, then extra
if !(result = options[:override][name]).nil?
result
elsif !(result = scope.compiler.injector.lookup(scope, type, name)).nil?
result
else
result = call_hiera_function(scope, name, PrivateNotFoundMarker)
if !result.nil? && result != PrivateNotFoundMarker
result
else
options[:extra][name]
end
end
end
private_class_method :search_for
def self.call_hiera_function(scope, name, dflt)
loader = scope.compiler.loaders.private_environment_loader
func = loader.load(:function, :hiera) unless loader.nil?
raise Error, 'Function not found: hiera' if func.nil?
func.call(scope, name, dflt)
end
private_class_method :call_hiera_function
# This is the method called from the puppet/parser/functions/lookup.rb
# @param args [Array] array following the puppet function call conventions
def self.lookup(scope, args)
type_calculator = Puppet::Pops::Types::TypeCalculator.new
options = parse_lookup_args(args)
validate_options(options, type_calculator)
names = [options[:name]].flatten
type = options[:type]
result_with_name = names.reduce([]) do |memo, name|
break memo if !memo[1].nil?
[name, search_for(scope, type, name, options)]
end
result = if result_with_name[1].nil?
# not found, use default (which may be nil), the default is already type checked
options[:default]
else
# injector.lookup is type-safe already do no need to type check the result
result_with_name[1]
end
# If a block is given it is called with :undef passed as 'nil' since the lookup function
# is available from 3x with --binder turned on, and the evaluation is always 4x.
# TODO PUPPET4: Simply pass the value
#
result = if pblock = options[:pblock]
result2 = case pblock.parameter_count
when 1
pblock.call(undef_as_nil(result))
when 2
pblock.call(result_with_name[ 0 ], undef_as_nil(result))
else
pblock.call(result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ]))
end
# if the given result was returned, there is no need to type-check it again
if !result2.equal?(result)
t = type_calculator.infer(undef_as_nil(result2))
if !type_calculator.assignable?(type, t)
fail "the value produced by the given code block #{type_mismatch(type_calculator, type, t)}"
end
end
result2
else
result
end
# Finally, the result if nil must be acceptable or an error is raised
if is_nil_or_undef?(result) && !options[:accept_undef]
fail_lookup(names)
else
# Since the function may be used without future parser being in effect, nil is not handled in a good
# way, and should instead be turned into :undef.
# TODO PUPPET4: Simply return the result
#
Puppet.future_parser? ? result : nil_as_undef(result)
end
end
end