Current File : //opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/deferred_resolver.rb |
require_relative '../../../puppet/parser/script_compiler'
module Puppet::Pops
module Evaluator
class DeferredValue
def initialize(proc)
@proc = proc
end
def resolve
val = @proc.call
# Deferred sensitive values will be marked as such in resolve_futures()
if val.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
val.unwrap
else
val
end
end
end
# Utility class to help resolve instances of Puppet::Pops::Types::PDeferredType::Deferred
#
class DeferredResolver
DOLLAR = '$'.freeze
DIG = 'dig'.freeze
# Resolves and replaces all Deferred values in a catalog's resource attributes
# found as direct values or nested inside Array, Hash or Sensitive values.
# Deferred values inside of custom Object instances are not resolved as this
# is expected to be done by such objects.
#
# @param facts [Puppet::Node::Facts] the facts object for the node
# @param catalog [Puppet::Resource::Catalog] the catalog where all deferred values should be replaced
# @param environment [Puppet::Node::Environment] the environment whose anonymous module methods
# are to be mixed into the scope
# @return [nil] does not return anything - the catalog is modified as a side effect
#
def self.resolve_and_replace(facts, catalog, environment = catalog.environment_instance, preprocess_deferred = true)
compiler = Puppet::Parser::ScriptCompiler.new(environment, catalog.name, preprocess_deferred)
resolver = new(compiler, preprocess_deferred)
resolver.set_facts_variable(facts)
# TODO:
# # When scripting the trusted data are always local, but set them anyway
# @scope.set_trusted(node.trusted_data)
#
# # Server facts are always about the local node's version etc.
# @scope.set_server_facts(node.server_facts)
resolver.resolve_futures(catalog)
nil
end
# Resolves a value such that a direct Deferred, or any nested Deferred values
# are resolved and used instead of the deferred value.
# A direct Deferred value, or nested deferred values inside of Array, Hash or
# Sensitive values are resolved and replaced inside of freshly created
# containers.
#
# The resolution takes place in the topscope of the given compiler.
# Variable values are supposed to already have been set.
#
# @param value [Object] the (possibly nested) value to resolve
# @param compiler [Puppet::Parser::ScriptCompiler, Puppet::Parser::Compiler] the compiler in effect
# @return [Object] the resolved value (a new Array, Hash, or Sensitive if needed), with all deferred values resolved
#
def self.resolve(value, compiler)
resolver = new(compiler)
resolver.resolve(value)
end
def initialize(compiler, preprocess_deferred = true)
@compiler = compiler
# Always resolve in top scope
@scope = @compiler.topscope
@deferred_class = Puppet::Pops::Types::TypeFactory.deferred.implementation_class
@preprocess_deferred = preprocess_deferred
end
# @param facts [Puppet::Node::Facts] the facts to set in $facts in the compiler's topscope
#
def set_facts_variable(facts)
@scope.set_facts(facts.nil? ? {} : facts.values)
end
def resolve_futures(catalog)
catalog.resources.each do |r|
overrides = {}
r.parameters.each_pair do |k, v|
resolved = resolve(v)
case resolved
when Puppet::Pops::Types::PSensitiveType::Sensitive
# If the resolved value is instance of Sensitive - assign the unwrapped value
# and mark it as sensitive if not already marked
#
resolved = resolved.unwrap
mark_sensitive_parameters(r, k)
when Puppet::Pops::Evaluator::DeferredValue
# If the resolved value is a DeferredValue and it has an argument of type
# PSensitiveType, mark it as sensitive. Since DeferredValues can nest,
# we must walk all arguments, e.g. the DeferredValue may call the `epp`
# function, where one of its arguments is a DeferredValue to call the
# `vault:lookup` function.
#
# The DeferredValue.resolve method will unwrap the sensitive during
# catalog application
#
if contains_sensitive_args?(v)
mark_sensitive_parameters(r, k)
end
end
overrides[ k ] = resolved
end
r.parameters.merge!(overrides) unless overrides.empty?
end
end
# Return true if x contains an argument that is an instance of PSensitiveType:
#
# Deferred('new', [Sensitive, 'password'])
#
# Or an instance of PSensitiveType::Sensitive:
#
# Deferred('join', [['a', Sensitive('b')], ':'])
#
# Since deferred values can nest, descend into Arrays and Hash keys and values,
# short-circuiting when the first occurrence is found.
#
def contains_sensitive_args?(x)
case x
when @deferred_class
contains_sensitive_args?(x.arguments)
when Array
x.any? { |v| contains_sensitive_args?(v) }
when Hash
x.any? { |k, v| contains_sensitive_args?(k) || contains_sensitive_args?(v) }
when Puppet::Pops::Types::PSensitiveType, Puppet::Pops::Types::PSensitiveType::Sensitive
true
else
false
end
end
private :contains_sensitive_args?
def mark_sensitive_parameters(r, k)
unless r.sensitive_parameters.include?(k.to_sym)
r.sensitive_parameters = (r.sensitive_parameters + [k.to_sym]).freeze
end
end
private :mark_sensitive_parameters
def resolve(x)
if x.class == @deferred_class
resolve_future(x)
elsif x.is_a?(Array)
x.map {|v| resolve(v) }
elsif x.is_a?(Hash)
result = {}
x.each_pair {|k,v| result[k] = resolve(v) }
result
elsif x.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
# rewrap in a new Sensitive after resolving any nested deferred values
Puppet::Pops::Types::PSensitiveType::Sensitive.new(resolve(x.unwrap))
elsif x.is_a?(Puppet::Pops::Types::PBinaryType::Binary)
# use the ASCII-8BIT string that it wraps
x.binary_buffer
else
x
end
end
def resolve_lazy_args(x)
if x.is_a?(DeferredValue)
x.resolve
elsif x.is_a?(Array)
x.map {|v| resolve_lazy_args(v) }
elsif x.is_a?(Hash)
result = {}
x.each_pair {|k,v| result[k] = resolve_lazy_args(v) }
result
elsif x.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
# rewrap in a new Sensitive after resolving any nested deferred values
Puppet::Pops::Types::PSensitiveType::Sensitive.new(resolve_lazy_args(x.unwrap))
else
x
end
end
private :resolve_lazy_args
def resolve_future(f)
# If any of the arguments to a future is a future it needs to be resolved first
func_name = f.name
mapped_arguments = map_arguments(f.arguments)
# if name starts with $ then this is a call to dig
if func_name[0] == DOLLAR
var_name = func_name[1..-1]
func_name = DIG
mapped_arguments.insert(0, @scope[var_name])
end
if @preprocess_deferred
# call the function (name in deferred, or 'dig' for a variable)
@scope.call_function(func_name, mapped_arguments)
else
# call the function later
DeferredValue.new(
Proc.new {
# deferred functions can have nested deferred arguments
resolved_arguments = mapped_arguments.map { |arg| resolve_lazy_args(arg) }
@scope.call_function(func_name, resolved_arguments)
}
)
end
end
def map_arguments(args)
return [] if args.nil?
args.map {|v| resolve(v) }
end
private :map_arguments
end
end
end