bolt 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +8 -1
 - data/bolt-modules/system/lib/puppet/functions/system/env.rb +1 -1
 - data/lib/bolt/analytics.rb +7 -1
 - data/lib/bolt/applicator.rb +3 -1
 - data/lib/bolt/apply_result.rb +1 -0
 - data/lib/bolt/config.rb +2 -1
 - data/lib/bolt/executor.rb +20 -2
 - data/lib/bolt/inventory/group.rb +25 -3
 - data/lib/bolt/notifier.rb +5 -4
 - data/lib/bolt/pal.rb +10 -2
 - data/lib/bolt/pal/yaml_plan.rb +163 -0
 - data/lib/bolt/pal/yaml_plan/evaluator.rb +163 -0
 - data/lib/bolt/pal/yaml_plan/loader.rb +86 -0
 - data/lib/bolt/result.rb +12 -14
 - data/lib/bolt/task.rb +15 -2
 - data/lib/bolt/task/puppet_server.rb +9 -6
 - data/lib/bolt/transport/docker.rb +3 -3
 - data/lib/bolt/transport/docker/connection.rb +3 -1
 - data/lib/bolt/transport/local.rb +11 -3
 - data/lib/bolt/transport/orch.rb +17 -11
 - data/lib/bolt/transport/remote.rb +2 -2
 - data/lib/bolt/transport/ssh.rb +12 -3
 - data/lib/bolt/transport/ssh/connection.rb +10 -11
 - data/lib/bolt/transport/winrm.rb +12 -3
 - data/lib/bolt/transport/winrm/connection.rb +3 -1
 - data/lib/bolt/version.rb +1 -1
 - data/lib/bolt_server/file_cache.rb +4 -1
 - data/lib/bolt_server/transport_app.rb +1 -1
 - data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
 - data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
 - data/lib/bolt_spec/run.rb +62 -0
 - data/lib/plan_executor/app.rb +3 -1
 - metadata +7 -5
 - data/lib/bolt/task/remote.rb +0 -25
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 271a7fea2ecaf024054a9ae2266bc352b19a9969300c882f9622341582585d48
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: d10c047f108f62efdfe1e6ac1a35bc7b3303332f0ca0bd50c55635857d41648e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 7d6355d7246bfa1305c02e13edf5fd7873584b70306e505f48ee1470babf6aa282b9b5f141ee5af6703b60a589f937c7997110ad21a2d6705926835a8b42b415
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 4a1e5f3bf454ce08b084c0310b98c71119305f3c34fbeca552dffe3f6029cc16e7c410e2490605a985552e9603d7a9e653509b8a71679f3b2bb5e0a56e13951d
         
     | 
| 
         @@ -59,6 +59,11 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction 
     | 
|
| 
       59 
59 
     | 
    
         
             
                  executor.run_as = run_as
         
     | 
| 
       60 
60 
     | 
    
         
             
                end
         
     | 
| 
       61 
61 
     | 
    
         | 
| 
      
 62 
     | 
    
         
            +
                closure = func.class.dispatcher.dispatchers[0]
         
     | 
| 
      
 63 
     | 
    
         
            +
                if closure.model.is_a?(Bolt::PAL::YamlPlan)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  executor.report_yaml_plan(closure.model.body)
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
       62 
67 
     | 
    
         
             
                # wrap plan execution in logging messages
         
     | 
| 
       63 
68 
     | 
    
         
             
                executor.log_plan(plan_name) do
         
     | 
| 
       64 
69 
     | 
    
         
             
                  result = nil
         
     | 
| 
         @@ -66,7 +71,9 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction 
     | 
|
| 
       66 
71 
     | 
    
         
             
                    # If the plan does not throw :return by calling the return function it's result is
         
     | 
| 
       67 
72 
     | 
    
         
             
                    # undef/nil
         
     | 
| 
       68 
73 
     | 
    
         
             
                    result = catch(:return) do
         
     | 
| 
       69 
     | 
    
         
            -
                       
     | 
| 
      
 74 
     | 
    
         
            +
                      scope.with_global_scope do |global_scope|
         
     | 
| 
      
 75 
     | 
    
         
            +
                        closure.call_by_name_with_scope(global_scope, params, true)
         
     | 
| 
      
 76 
     | 
    
         
            +
                      end
         
     | 
| 
       70 
77 
     | 
    
         
             
                      nil
         
     | 
| 
       71 
78 
     | 
    
         
             
                    end&.value
         
     | 
| 
       72 
79 
     | 
    
         
             
                    # Validate the result is a PlanResult
         
     | 
    
        data/lib/bolt/analytics.rb
    CHANGED
    
    | 
         @@ -21,7 +21,9 @@ module Bolt 
     | 
|
| 
       21 
21 
     | 
    
         
             
                  target_nodes: :cd4,
         
     | 
| 
       22 
22 
     | 
    
         
             
                  output_format: :cd5,
         
     | 
| 
       23 
23 
     | 
    
         
             
                  statement_count: :cd6,
         
     | 
| 
       24 
     | 
    
         
            -
                  resource_mean: :cd7
         
     | 
| 
      
 24 
     | 
    
         
            +
                  resource_mean: :cd7,
         
     | 
| 
      
 25 
     | 
    
         
            +
                  plan_steps: :cd8,
         
     | 
| 
      
 26 
     | 
    
         
            +
                  return_type: :cd9
         
     | 
| 
       25 
27 
     | 
    
         
             
                }.freeze
         
     | 
| 
       26 
28 
     | 
    
         | 
| 
       27 
29 
     | 
    
         
             
                def self.build_client
         
     | 
| 
         @@ -62,6 +64,10 @@ module Bolt 
     | 
|
| 
       62 
64 
     | 
    
         
             
                  attr_reader :user_id
         
     | 
| 
       63 
65 
     | 
    
         | 
| 
       64 
66 
     | 
    
         
             
                  def initialize(user_id)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # lazy-load expensive gem code
         
     | 
| 
      
 68 
     | 
    
         
            +
                    require 'concurrent/configuration'
         
     | 
| 
      
 69 
     | 
    
         
            +
                    require 'concurrent/future'
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
       65 
71 
     | 
    
         
             
                    @logger = Logging.logger[self]
         
     | 
| 
       66 
72 
     | 
    
         
             
                    @http = HTTPClient.new
         
     | 
| 
       67 
73 
     | 
    
         
             
                    @user_id = user_id
         
     | 
    
        data/lib/bolt/applicator.rb
    CHANGED
    
    | 
         @@ -1,7 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require 'base64'
         
     | 
| 
       4 
     | 
    
         
            -
            require 'concurrent'
         
     | 
| 
       5 
4 
     | 
    
         
             
            require 'find'
         
     | 
| 
       6 
5 
     | 
    
         
             
            require 'json'
         
     | 
| 
       7 
6 
     | 
    
         
             
            require 'logging'
         
     | 
| 
         @@ -15,6 +14,9 @@ require 'bolt/util/puppet_log_level' 
     | 
|
| 
       15 
14 
     | 
    
         
             
            module Bolt
         
     | 
| 
       16 
15 
     | 
    
         
             
              class Applicator
         
     | 
| 
       17 
16 
     | 
    
         
             
                def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # lazy-load expensive gem code
         
     | 
| 
      
 18 
     | 
    
         
            +
                  require 'concurrent'
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       18 
20 
     | 
    
         
             
                  @inventory = inventory
         
     | 
| 
       19 
21 
     | 
    
         
             
                  @executor = executor
         
     | 
| 
       20 
22 
     | 
    
         
             
                  @modulepath = modulepath
         
     | 
    
        data/lib/bolt/apply_result.rb
    CHANGED
    
    
    
        data/lib/bolt/config.rb
    CHANGED
    
    
    
        data/lib/bolt/executor.rb
    CHANGED
    
    | 
         @@ -3,7 +3,6 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            # Used for $ERROR_INFO. This *must* be capitalized!
         
     | 
| 
       4 
4 
     | 
    
         
             
            require 'English'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'json'
         
     | 
| 
       6 
     | 
    
         
            -
            require 'concurrent'
         
     | 
| 
       7 
6 
     | 
    
         
             
            require 'logging'
         
     | 
| 
       8 
7 
     | 
    
         
             
            require 'set'
         
     | 
| 
       9 
8 
     | 
    
         
             
            require 'bolt/analytics'
         
     | 
| 
         @@ -25,10 +24,13 @@ module Bolt 
     | 
|
| 
       25 
24 
     | 
    
         
             
                               noop = nil,
         
     | 
| 
       26 
25 
     | 
    
         
             
                               bundled_content: nil,
         
     | 
| 
       27 
26 
     | 
    
         
             
                               load_config: true)
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # lazy-load expensive gem code
         
     | 
| 
      
 29 
     | 
    
         
            +
                  require 'concurrent'
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
       28 
31 
     | 
    
         
             
                  @analytics = analytics
         
     | 
| 
       29 
32 
     | 
    
         
             
                  @bundled_content = bundled_content
         
     | 
| 
       30 
33 
     | 
    
         
             
                  @logger = Logging.logger[self]
         
     | 
| 
       31 
     | 
    
         
            -
                  @plan_logging = false
         
     | 
| 
       32 
34 
     | 
    
         
             
                  @load_config = load_config
         
     | 
| 
       33 
35 
     | 
    
         | 
| 
       34 
36 
     | 
    
         
             
                  @transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
         
     | 
| 
         @@ -199,6 +201,22 @@ module Bolt 
     | 
|
| 
       199 
201 
     | 
    
         
             
                  @analytics&.event('Apply', 'ast', data)
         
     | 
| 
       200 
202 
     | 
    
         
             
                end
         
     | 
| 
       201 
203 
     | 
    
         | 
| 
      
 204 
     | 
    
         
            +
                def report_yaml_plan(plan)
         
     | 
| 
      
 205 
     | 
    
         
            +
                  steps = plan.steps.count
         
     | 
| 
      
 206 
     | 
    
         
            +
                  return_type = case plan.return
         
     | 
| 
      
 207 
     | 
    
         
            +
                                when Bolt::PAL::YamlPlan::EvaluableString
         
     | 
| 
      
 208 
     | 
    
         
            +
                                  'expression'
         
     | 
| 
      
 209 
     | 
    
         
            +
                                when nil
         
     | 
| 
      
 210 
     | 
    
         
            +
                                  nil
         
     | 
| 
      
 211 
     | 
    
         
            +
                                else
         
     | 
| 
      
 212 
     | 
    
         
            +
                                  'value'
         
     | 
| 
      
 213 
     | 
    
         
            +
                                end
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                  @analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
         
     | 
| 
      
 216 
     | 
    
         
            +
                rescue StandardError => e
         
     | 
| 
      
 217 
     | 
    
         
            +
                  @logger.debug { "Failed to submit analytics event: #{e.message}" }
         
     | 
| 
      
 218 
     | 
    
         
            +
                end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
       202 
220 
     | 
    
         
             
                def with_node_logging(description, batch)
         
     | 
| 
       203 
221 
     | 
    
         
             
                  @logger.info("#{description} on #{batch.map(&:uri)}")
         
     | 
| 
       204 
222 
     | 
    
         
             
                  result = yield
         
     | 
    
        data/lib/bolt/inventory/group.rb
    CHANGED
    
    | 
         @@ -10,6 +10,11 @@ module Bolt 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  # Regex used to validate group names and target aliases.
         
     | 
| 
       11 
11 
     | 
    
         
             
                  NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
      
 13 
     | 
    
         
            +
                  DATA_KEYS = %w[name config facts vars features].freeze
         
     | 
| 
      
 14 
     | 
    
         
            +
                  NODE_KEYS = DATA_KEYS + ['alias']
         
     | 
| 
      
 15 
     | 
    
         
            +
                  GROUP_KEYS = DATA_KEYS + %w[groups nodes]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       13 
18 
     | 
    
         
             
                  def initialize(data)
         
     | 
| 
       14 
19 
     | 
    
         
             
                    @logger = Logging.logger[self]
         
     | 
| 
       15 
20 
     | 
    
         | 
| 
         @@ -20,11 +25,21 @@ module Bolt 
     | 
|
| 
       20 
25 
     | 
    
         
             
                    raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
         
     | 
| 
       21 
26 
     | 
    
         
             
                    raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ NAME_REGEX
         
     | 
| 
       22 
27 
     | 
    
         | 
| 
      
 28 
     | 
    
         
            +
                    unless (unexpected_keys = data.keys - GROUP_KEYS).empty?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                      @logger.warn(msg)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
       23 
33 
     | 
    
         
             
                    @vars = fetch_value(data, 'vars', Hash)
         
     | 
| 
       24 
34 
     | 
    
         
             
                    @facts = fetch_value(data, 'facts', Hash)
         
     | 
| 
       25 
35 
     | 
    
         
             
                    @features = fetch_value(data, 'features', Array)
         
     | 
| 
       26 
36 
     | 
    
         
             
                    @config = fetch_value(data, 'config', Hash)
         
     | 
| 
       27 
37 
     | 
    
         | 
| 
      
 38 
     | 
    
         
            +
                    unless (unexpected_keys = @config.keys - CONFIG_KEYS).empty?
         
     | 
| 
      
 39 
     | 
    
         
            +
                      msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for group #{@name}"
         
     | 
| 
      
 40 
     | 
    
         
            +
                      @logger.warn(msg)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
       28 
43 
     | 
    
         
             
                    nodes = fetch_value(data, 'nodes', Array)
         
     | 
| 
       29 
44 
     | 
    
         
             
                    groups = fetch_value(data, 'groups', Array)
         
     | 
| 
       30 
45 
     | 
    
         | 
| 
         @@ -43,6 +58,16 @@ module Bolt 
     | 
|
| 
       43 
58 
     | 
    
         
             
                      raise ValidationError.new("Node #{node} does not have a name", @name) unless node['name']
         
     | 
| 
       44 
59 
     | 
    
         
             
                      @nodes[node['name']] = node
         
     | 
| 
       45 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
                      unless (unexpected_keys = node.keys - NODE_KEYS).empty?
         
     | 
| 
      
 62 
     | 
    
         
            +
                        msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in node #{node['name']}"
         
     | 
| 
      
 63 
     | 
    
         
            +
                        @logger.warn(msg)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      end
         
     | 
| 
      
 65 
     | 
    
         
            +
                      config_keys = node['config']&.keys || []
         
     | 
| 
      
 66 
     | 
    
         
            +
                      unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
         
     | 
| 
      
 67 
     | 
    
         
            +
                        msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for node #{node['name']}"
         
     | 
| 
      
 68 
     | 
    
         
            +
                        @logger.warn(msg)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
       46 
71 
     | 
    
         
             
                      next unless node.include?('alias')
         
     | 
| 
       47 
72 
     | 
    
         | 
| 
       48 
73 
     | 
    
         
             
                      aliases = node['alias']
         
     | 
| 
         @@ -67,9 +92,6 @@ module Bolt 
     | 
|
| 
       67 
92 
     | 
    
         
             
                    @name_or_alias = nodes.select { |node| node.is_a?(String) }
         
     | 
| 
       68 
93 
     | 
    
         | 
| 
       69 
94 
     | 
    
         
             
                    @groups = groups.map { |g| Group.new(g) }
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                    # this allows arbitrary info for the top level
         
     | 
| 
       72 
     | 
    
         
            -
                    @rest = data.reject { |k, _| %w[name nodes config groups vars facts features].include? k }
         
     | 
| 
       73 
95 
     | 
    
         
             
                  end
         
     | 
| 
       74 
96 
     | 
    
         | 
| 
       75 
97 
     | 
    
         
             
                  private def fetch_value(data, key, type)
         
     | 
    
        data/lib/bolt/notifier.rb
    CHANGED
    
    | 
         @@ -1,11 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require 'concurrent'
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
3 
     | 
    
         
             
            module Bolt
         
     | 
| 
       6 
4 
     | 
    
         
             
              class Notifier
         
     | 
| 
       7 
     | 
    
         
            -
                def initialize(executor =  
     | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(executor = nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # lazy-load expensive gem code
         
     | 
| 
      
 7 
     | 
    
         
            +
                  require 'concurrent'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  @executor = executor || Concurrent::SingleThreadExecutor.new
         
     | 
| 
       9 
10 
     | 
    
         
             
                end
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
                def notify(callback, event)
         
     | 
    
        data/lib/bolt/pal.rb
    CHANGED
    
    | 
         @@ -81,6 +81,7 @@ module Bolt 
     | 
|
| 
       81 
81 
     | 
    
         | 
| 
       82 
82 
     | 
    
         
             
                  require 'bolt/pal/logging'
         
     | 
| 
       83 
83 
     | 
    
         
             
                  require 'bolt/pal/issues'
         
     | 
| 
      
 84 
     | 
    
         
            +
                  require 'bolt/pal/yaml_plan/loader'
         
     | 
| 
       84 
85 
     | 
    
         | 
| 
       85 
86 
     | 
    
         
             
                  # Now that puppet is loaded we can include puppet mixins in data types
         
     | 
| 
       86 
87 
     | 
    
         
             
                  Bolt::ResultSet.include_iterable
         
     | 
| 
         @@ -103,7 +104,9 @@ module Bolt 
     | 
|
| 
       103 
104 
     | 
    
         
             
                    pal.with_script_compiler do |compiler|
         
     | 
| 
       104 
105 
     | 
    
         
             
                      alias_types(compiler)
         
     | 
| 
       105 
106 
     | 
    
         
             
                      begin
         
     | 
| 
       106 
     | 
    
         
            -
                         
     | 
| 
      
 107 
     | 
    
         
            +
                        Puppet.override(yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
         
     | 
| 
      
 108 
     | 
    
         
            +
                          yield compiler
         
     | 
| 
      
 109 
     | 
    
         
            +
                        end
         
     | 
| 
       107 
110 
     | 
    
         
             
                      rescue Bolt::Error => err
         
     | 
| 
       108 
111 
     | 
    
         
             
                        err
         
     | 
| 
       109 
112 
     | 
    
         
             
                      rescue Puppet::PreformattedError => err
         
     | 
| 
         @@ -247,7 +250,12 @@ module Bolt 
     | 
|
| 
       247 
250 
     | 
    
         | 
| 
       248 
251 
     | 
    
         
             
                def list_plans
         
     | 
| 
       249 
252 
     | 
    
         
             
                  in_bolt_compiler do |compiler|
         
     | 
| 
       250 
     | 
    
         
            -
                     
     | 
| 
      
 253 
     | 
    
         
            +
                    errors = []
         
     | 
| 
      
 254 
     | 
    
         
            +
                    plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
         
     | 
| 
      
 255 
     | 
    
         
            +
                    errors.each do |error|
         
     | 
| 
      
 256 
     | 
    
         
            +
                      @logger.warn(error.details['original_error'])
         
     | 
| 
      
 257 
     | 
    
         
            +
                    end
         
     | 
| 
      
 258 
     | 
    
         
            +
                    plans
         
     | 
| 
       251 
259 
     | 
    
         
             
                  end
         
     | 
| 
       252 
260 
     | 
    
         
             
                end
         
     | 
| 
       253 
261 
     | 
    
         | 
| 
         @@ -0,0 +1,163 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Bolt
         
     | 
| 
      
 4 
     | 
    
         
            +
              class PAL
         
     | 
| 
      
 5 
     | 
    
         
            +
                class YamlPlan
         
     | 
| 
      
 6 
     | 
    
         
            +
                  Parameter = Struct.new(:name, :value, :type_expr) do
         
     | 
| 
      
 7 
     | 
    
         
            +
                    def captures_rest
         
     | 
| 
      
 8 
     | 
    
         
            +
                      false
         
     | 
| 
      
 9 
     | 
    
         
            +
                    end
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_reader :name, :parameters, :steps, :return
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(name, plan)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    # Top-level plan keys aren't allowed to be Puppet code, so force them
         
     | 
| 
      
 16 
     | 
    
         
            +
                    # all to strings.
         
     | 
| 
      
 17 
     | 
    
         
            +
                    plan = Bolt::Util.walk_keys(plan) { |key| stringify(key) }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    @name = name.freeze
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    # Nothing in parameters is allowed to be code, since no variables are defined yet
         
     | 
| 
      
 22 
     | 
    
         
            +
                    params_hash = stringify(plan.fetch('parameters', {}))
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    # Munge parameters into an array of Parameter objects, which is what
         
     | 
| 
      
 25 
     | 
    
         
            +
                    # the Puppet API expects
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @parameters = params_hash.map do |param, definition|
         
     | 
| 
      
 27 
     | 
    
         
            +
                      definition ||= {}
         
     | 
| 
      
 28 
     | 
    
         
            +
                      type = Puppet::Pops::Types::TypeParser.singleton.parse(definition['type']) if definition.key?('type')
         
     | 
| 
      
 29 
     | 
    
         
            +
                      Parameter.new(param, definition['default'], type)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end.freeze
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    @steps = plan['steps']&.map do |step|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      # Step keys also aren't allowed to be code and neither is the value of "name"
         
     | 
| 
      
 34 
     | 
    
         
            +
                      stringified_step = Bolt::Util.walk_keys(step) { |key| stringify(key) }
         
     | 
| 
      
 35 
     | 
    
         
            +
                      stringified_step['name'] = stringify(stringified_step['name']) if stringified_step.key?('name')
         
     | 
| 
      
 36 
     | 
    
         
            +
                      stringified_step
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end.freeze
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    @return = plan['return']
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    validate
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  VAR_NAME_PATTERN = /\A[a-z_][a-z0-9_]*\z/.freeze
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def validate
         
     | 
| 
      
 47 
     | 
    
         
            +
                    unless @steps.is_a?(Array)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      raise Bolt::Error.new("Plan must specify an array of steps", "bolt/invalid-plan")
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    used_names = Set.new
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    # Parameters come in a hash, so they must be unique
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @parameters.each do |param|
         
     | 
| 
      
 55 
     | 
    
         
            +
                      unless param.name.is_a?(String) && param.name.match?(VAR_NAME_PATTERN)
         
     | 
| 
      
 56 
     | 
    
         
            +
                        raise Bolt::Error.new("Invalid parameter name #{param.name.inspect}", "bolt/invalid-plan")
         
     | 
| 
      
 57 
     | 
    
         
            +
                      end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                      used_names << param.name
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    @steps.each do |step|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      next unless step.key?('name')
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                      unless step['name'].is_a?(String) && step['name'].match?(VAR_NAME_PATTERN)
         
     | 
| 
      
 66 
     | 
    
         
            +
                        raise Bolt::Error.new("Invalid step name #{step['name'].inspect}", "bolt/invalid-plan")
         
     | 
| 
      
 67 
     | 
    
         
            +
                      end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                      if used_names.include?(step['name'])
         
     | 
| 
      
 70 
     | 
    
         
            +
                        msg = "Step name #{step['name'].inspect} matches an existing parameter or step name"
         
     | 
| 
      
 71 
     | 
    
         
            +
                        raise Bolt::Error.new(msg, "bolt/invalid-plan")
         
     | 
| 
      
 72 
     | 
    
         
            +
                      end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                      used_names << step['name']
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  def body
         
     | 
| 
      
 79 
     | 
    
         
            +
                    self
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  # Turn all "potential" strings in the object into actual strings.
         
     | 
| 
      
 83 
     | 
    
         
            +
                  # Because we interpret bare strings as potential Puppet code, even in
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # places where Puppet code isn't allowed (like some hash keys), we need
         
     | 
| 
      
 85 
     | 
    
         
            +
                  # to be able to force them back into regular strings, as if we had
         
     | 
| 
      
 86 
     | 
    
         
            +
                  # parsed them normally.
         
     | 
| 
      
 87 
     | 
    
         
            +
                  def stringify(value)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 89 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 90 
     | 
    
         
            +
                      value.map { |element| stringify(element) }
         
     | 
| 
      
 91 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 92 
     | 
    
         
            +
                      value.each_with_object({}) do |(k, v), o|
         
     | 
| 
      
 93 
     | 
    
         
            +
                        o[stringify(k)] = stringify(v)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      end
         
     | 
| 
      
 95 
     | 
    
         
            +
                    when EvaluableString
         
     | 
| 
      
 96 
     | 
    
         
            +
                      value.value
         
     | 
| 
      
 97 
     | 
    
         
            +
                    else
         
     | 
| 
      
 98 
     | 
    
         
            +
                      value
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  def return_type
         
     | 
| 
      
 103 
     | 
    
         
            +
                    Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult')
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  # This class wraps a value parsed from YAML which may be Puppet code.
         
     | 
| 
      
 107 
     | 
    
         
            +
                  # That includes double-quoted strings and string literals, each of which
         
     | 
| 
      
 108 
     | 
    
         
            +
                  # subclasses this parent class in order to implement its own evaluation
         
     | 
| 
      
 109 
     | 
    
         
            +
                  # logic.
         
     | 
| 
      
 110 
     | 
    
         
            +
                  class EvaluableString
         
     | 
| 
      
 111 
     | 
    
         
            +
                    attr_reader :value
         
     | 
| 
      
 112 
     | 
    
         
            +
                    def initialize(value)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      @value = value
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  # This class represents a double-quoted YAML string, which is interpreted
         
     | 
| 
      
 118 
     | 
    
         
            +
                  # as though it were a double-quoted Puppet string (with associated
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # variable interpolations)
         
     | 
| 
      
 120 
     | 
    
         
            +
                  class DoubleQuotedString < EvaluableString
         
     | 
| 
      
 121 
     | 
    
         
            +
                    def evaluate(scope, evaluator)
         
     | 
| 
      
 122 
     | 
    
         
            +
                      # "inspect" allows us to get back a double-quoted string literal with
         
     | 
| 
      
 123 
     | 
    
         
            +
                      # special characters escaped. This is based on the assumption that
         
     | 
| 
      
 124 
     | 
    
         
            +
                      # YAML, Ruby and Puppet all support similar escape sequences.
         
     | 
| 
      
 125 
     | 
    
         
            +
                      parse_result = evaluator.parse_string(@value.inspect)
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                      scope.with_local_scope({}) do
         
     | 
| 
      
 128 
     | 
    
         
            +
                        evaluator.evaluate(scope, parse_result)
         
     | 
| 
      
 129 
     | 
    
         
            +
                      end
         
     | 
| 
      
 130 
     | 
    
         
            +
                    end
         
     | 
| 
      
 131 
     | 
    
         
            +
                  end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                  # This represents a literal snippet of Puppet code
         
     | 
| 
      
 134 
     | 
    
         
            +
                  class CodeLiteral < EvaluableString
         
     | 
| 
      
 135 
     | 
    
         
            +
                    def evaluate(scope, evaluator)
         
     | 
| 
      
 136 
     | 
    
         
            +
                      parse_result = evaluator.parse_string(@value)
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                      scope.with_local_scope({}) do
         
     | 
| 
      
 139 
     | 
    
         
            +
                        evaluator.evaluate(scope, parse_result)
         
     | 
| 
      
 140 
     | 
    
         
            +
                      end
         
     | 
| 
      
 141 
     | 
    
         
            +
                    end
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                  # This class stores a bare YAML string, which is fuzzily interpreted as
         
     | 
| 
      
 145 
     | 
    
         
            +
                  # either Puppet code or a literal string, depending on whether it starts
         
     | 
| 
      
 146 
     | 
    
         
            +
                  # with a variable reference.
         
     | 
| 
      
 147 
     | 
    
         
            +
                  class BareString < EvaluableString
         
     | 
| 
      
 148 
     | 
    
         
            +
                    def evaluate(scope, evaluator)
         
     | 
| 
      
 149 
     | 
    
         
            +
                      if @value.start_with?('$')
         
     | 
| 
      
 150 
     | 
    
         
            +
                        # Try to parse the string as Puppet code. If it's invalid code,
         
     | 
| 
      
 151 
     | 
    
         
            +
                        # return the original string.
         
     | 
| 
      
 152 
     | 
    
         
            +
                        parse_result = evaluator.parse_string(@value)
         
     | 
| 
      
 153 
     | 
    
         
            +
                        scope.with_local_scope({}) do
         
     | 
| 
      
 154 
     | 
    
         
            +
                          evaluator.evaluate(scope, parse_result)
         
     | 
| 
      
 155 
     | 
    
         
            +
                        end
         
     | 
| 
      
 156 
     | 
    
         
            +
                      else
         
     | 
| 
      
 157 
     | 
    
         
            +
                        @value
         
     | 
| 
      
 158 
     | 
    
         
            +
                      end
         
     | 
| 
      
 159 
     | 
    
         
            +
                    end
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
              end
         
     | 
| 
      
 163 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,163 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bolt/pal/yaml_plan'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Bolt
         
     | 
| 
      
 6 
     | 
    
         
            +
              class PAL
         
     | 
| 
      
 7 
     | 
    
         
            +
                class YamlPlan
         
     | 
| 
      
 8 
     | 
    
         
            +
                  class Evaluator
         
     | 
| 
      
 9 
     | 
    
         
            +
                    def initialize(analytics = Bolt::Analytics::NoopClient.new)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      @logger = Logging.logger[self]
         
     | 
| 
      
 11 
     | 
    
         
            +
                      @analytics = analytics
         
     | 
| 
      
 12 
     | 
    
         
            +
                      @evaluator = Puppet::Pops::Parser::EvaluatingParser.new
         
     | 
| 
      
 13 
     | 
    
         
            +
                    end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    STEP_KEYS = %w[task command eval script source plan].freeze
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    def dispatch_step(scope, step)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      step = evaluate_code_blocks(scope, step)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                      step_type, *extra_keys = STEP_KEYS.select { |key| step.key?(key) }
         
     | 
| 
      
 21 
     | 
    
         
            +
                      if !step_type || extra_keys.any?
         
     | 
| 
      
 22 
     | 
    
         
            +
                        unsupported_step(scope, step)
         
     | 
| 
      
 23 
     | 
    
         
            +
                      end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      case step_type
         
     | 
| 
      
 26 
     | 
    
         
            +
                      when 'task'
         
     | 
| 
      
 27 
     | 
    
         
            +
                        task_step(scope, step)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      when 'command'
         
     | 
| 
      
 29 
     | 
    
         
            +
                        command_step(scope, step)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      when 'plan'
         
     | 
| 
      
 31 
     | 
    
         
            +
                        plan_step(scope, step)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      when 'script'
         
     | 
| 
      
 33 
     | 
    
         
            +
                        script_step(scope, step)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      when 'source'
         
     | 
| 
      
 35 
     | 
    
         
            +
                        upload_file_step(scope, step)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      when 'eval'
         
     | 
| 
      
 37 
     | 
    
         
            +
                        eval_step(scope, step)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      else
         
     | 
| 
      
 39 
     | 
    
         
            +
                        # This shouldn't be able to happen since this case statement should
         
     | 
| 
      
 40 
     | 
    
         
            +
                        # match the STEP_KEYS list, but raise an error *just in case*,
         
     | 
| 
      
 41 
     | 
    
         
            +
                        # instead of silently skipping the step.
         
     | 
| 
      
 42 
     | 
    
         
            +
                        unsupported_step(scope, step)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    def task_step(scope, step)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      task = step['task']
         
     | 
| 
      
 48 
     | 
    
         
            +
                      target = step['target']
         
     | 
| 
      
 49 
     | 
    
         
            +
                      description = step['description']
         
     | 
| 
      
 50 
     | 
    
         
            +
                      params = step['parameters'] || {}
         
     | 
| 
      
 51 
     | 
    
         
            +
                      raise "Can't run a task without specifying a target" unless target
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                      args = if description
         
     | 
| 
      
 54 
     | 
    
         
            +
                               [task, target, description, params]
         
     | 
| 
      
 55 
     | 
    
         
            +
                             else
         
     | 
| 
      
 56 
     | 
    
         
            +
                               [task, target, params]
         
     | 
| 
      
 57 
     | 
    
         
            +
                             end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                      scope.call_function('run_task', args)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    def plan_step(scope, step)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      plan = step['plan']
         
     | 
| 
      
 64 
     | 
    
         
            +
                      parameters = step['parameters'] || {}
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                      args = [plan, parameters]
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                      scope.call_function('run_plan', args)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                    def script_step(scope, step)
         
     | 
| 
      
 72 
     | 
    
         
            +
                      script = step['script']
         
     | 
| 
      
 73 
     | 
    
         
            +
                      target = step['target']
         
     | 
| 
      
 74 
     | 
    
         
            +
                      description = step['description']
         
     | 
| 
      
 75 
     | 
    
         
            +
                      arguments = step['arguments'] || []
         
     | 
| 
      
 76 
     | 
    
         
            +
                      raise "Can't run a script without specifying a target" unless target
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                      options = { 'arguments' => arguments }
         
     | 
| 
      
 79 
     | 
    
         
            +
                      args = if description
         
     | 
| 
      
 80 
     | 
    
         
            +
                               [script, target, description, options]
         
     | 
| 
      
 81 
     | 
    
         
            +
                             else
         
     | 
| 
      
 82 
     | 
    
         
            +
                               [script, target, options]
         
     | 
| 
      
 83 
     | 
    
         
            +
                             end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                      scope.call_function('run_script', args)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    def command_step(scope, step)
         
     | 
| 
      
 89 
     | 
    
         
            +
                      command = step['command']
         
     | 
| 
      
 90 
     | 
    
         
            +
                      target = step['target']
         
     | 
| 
      
 91 
     | 
    
         
            +
                      description = step['description']
         
     | 
| 
      
 92 
     | 
    
         
            +
                      raise "Can't run a command without specifying a target" unless target
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                      args = [command, target]
         
     | 
| 
      
 95 
     | 
    
         
            +
                      args << description if description
         
     | 
| 
      
 96 
     | 
    
         
            +
                      scope.call_function('run_command', args)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    def upload_file_step(scope, step)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      source = step['source']
         
     | 
| 
      
 101 
     | 
    
         
            +
                      destination = step['destination']
         
     | 
| 
      
 102 
     | 
    
         
            +
                      target = step['target']
         
     | 
| 
      
 103 
     | 
    
         
            +
                      description = step['description']
         
     | 
| 
      
 104 
     | 
    
         
            +
                      raise "Can't upload a file without specifying a target" unless target
         
     | 
| 
      
 105 
     | 
    
         
            +
                      raise "Can't upload a file without specifying a destination" unless destination
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                      args = [source, destination, target]
         
     | 
| 
      
 108 
     | 
    
         
            +
                      args << description if description
         
     | 
| 
      
 109 
     | 
    
         
            +
                      scope.call_function('upload_file', args)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    def eval_step(_scope, step)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      step['eval']
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                    def unsupported_step(_scope, step)
         
     | 
| 
      
 117 
     | 
    
         
            +
                      raise Bolt::Error.new("Unsupported plan step", "bolt/unsupported-step", step: step)
         
     | 
| 
      
 118 
     | 
    
         
            +
                    end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    # This is the method that Puppet calls to evaluate the plan. The name
         
     | 
| 
      
 121 
     | 
    
         
            +
                    # makes more sense for .pp plans.
         
     | 
| 
      
 122 
     | 
    
         
            +
                    def evaluate_block_with_bindings(closure_scope, args_hash, plan)
         
     | 
| 
      
 123 
     | 
    
         
            +
                      plan_result = closure_scope.with_local_scope(args_hash) do |scope|
         
     | 
| 
      
 124 
     | 
    
         
            +
                        plan.steps.each do |step|
         
     | 
| 
      
 125 
     | 
    
         
            +
                          step_result = dispatch_step(scope, step)
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                          scope.setvar(step['name'], step_result) if step.key?('name')
         
     | 
| 
      
 128 
     | 
    
         
            +
                        end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                        evaluate_code_blocks(scope, plan.return)
         
     | 
| 
      
 131 
     | 
    
         
            +
                      end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                      throw :return, Puppet::Pops::Evaluator::Return.new(plan_result, nil, nil)
         
     | 
| 
      
 134 
     | 
    
         
            +
                    end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                    # Recursively evaluate any EvaluableString instances in the object.
         
     | 
| 
      
 137 
     | 
    
         
            +
                    def evaluate_code_blocks(scope, value)
         
     | 
| 
      
 138 
     | 
    
         
            +
                      # XXX We should establish a local scope here probably
         
     | 
| 
      
 139 
     | 
    
         
            +
                      case value
         
     | 
| 
      
 140 
     | 
    
         
            +
                      when Array
         
     | 
| 
      
 141 
     | 
    
         
            +
                        value.map { |element| evaluate_code_blocks(scope, element) }
         
     | 
| 
      
 142 
     | 
    
         
            +
                      when Hash
         
     | 
| 
      
 143 
     | 
    
         
            +
                        value.each_with_object({}) do |(k, v), o|
         
     | 
| 
      
 144 
     | 
    
         
            +
                          key = k.is_a?(EvaluableString) ? k.value : k
         
     | 
| 
      
 145 
     | 
    
         
            +
                          o[key] = evaluate_code_blocks(scope, v)
         
     | 
| 
      
 146 
     | 
    
         
            +
                        end
         
     | 
| 
      
 147 
     | 
    
         
            +
                      when EvaluableString
         
     | 
| 
      
 148 
     | 
    
         
            +
                        value.evaluate(scope, @evaluator)
         
     | 
| 
      
 149 
     | 
    
         
            +
                      else
         
     | 
| 
      
 150 
     | 
    
         
            +
                        value
         
     | 
| 
      
 151 
     | 
    
         
            +
                      end
         
     | 
| 
      
 152 
     | 
    
         
            +
                    end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    # Occasionally the Closure will ask us to evaluate what it assumes are
         
     | 
| 
      
 155 
     | 
    
         
            +
                    # AST objects. Because we've sidestepped the AST, they aren't, so just
         
     | 
| 
      
 156 
     | 
    
         
            +
                    # return the values as already evaluated.
         
     | 
| 
      
 157 
     | 
    
         
            +
                    def evaluate(value, _scope)
         
     | 
| 
      
 158 
     | 
    
         
            +
                      value
         
     | 
| 
      
 159 
     | 
    
         
            +
                    end
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
              end
         
     | 
| 
      
 163 
     | 
    
         
            +
            end
         
     |