1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
require_relative '../../../puppet/parser/script_compiler'
module Puppet::Pops
module Evaluator
class DeferredValue
def initialize(proc)
@proc = proc
end
def resolve
@proc.call
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)
# If the value is instance of Sensitive - assign the unwrapped value
# and mark it as sensitive if not already marked
#
if resolved.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
resolved = resolved.unwrap
unless r.sensitive_parameters.include?(k.to_sym)
r.sensitive_parameters = (r.sensitive_parameters + [k.to_sym]).freeze
end
end
overrides[ k ] = resolved
end
r.parameters.merge!(overrides) unless overrides.empty?
end
end
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
|