File: deferred_resolver.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (172 lines) | stat: -rw-r--r-- 5,835 bytes parent folder | download
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