bolt 3.3.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +5 -5
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +19 -2
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/guides/targets.txt +31 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/bolt_option_parser.rb +35 -17
- data/lib/bolt/cli.rb +109 -28
- data/lib/bolt/config.rb +11 -7
- data/lib/bolt/config/options.rb +41 -9
- data/lib/bolt/config/transport/lxd.rb +3 -1
- data/lib/bolt/config/transport/options.rb +7 -0
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +15 -0
- data/lib/bolt/executor.rb +27 -15
- data/lib/bolt/inventory.rb +5 -4
- data/lib/bolt/inventory/inventory.rb +3 -2
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/node/output.rb +14 -4
- data/lib/bolt/outputter/human.rb +243 -84
- data/lib/bolt/outputter/json.rb +6 -4
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal.rb +22 -2
- data/lib/bolt/pal/yaml_plan/step.rb +4 -2
- data/lib/bolt/pal/yaml_plan/step/command.rb +8 -0
- data/lib/bolt/pal/yaml_plan/step/script.rb +4 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -2
- data/lib/bolt/plan_creator.rb +2 -2
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/puppetdb/client.rb +54 -0
- data/lib/bolt/result.rb +5 -14
- data/lib/bolt/shell/bash.rb +33 -22
- data/lib/bolt/shell/powershell.rb +6 -8
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +21 -32
- data/lib/bolt/transport/lxd/connection.rb +5 -5
- data/lib/bolt/transport/orch.rb +13 -5
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/util.rb +42 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -0
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
- data/lib/bolt_spec/plans/mock_executor.rb +91 -11
- data/modules/puppet_connect/plans/test_input_data.pp +22 -0
- metadata +11 -2
@@ -34,6 +34,15 @@ module Bolt
|
|
34
34
|
@container_info["Id"]
|
35
35
|
end
|
36
36
|
|
37
|
+
def run_cmd(cmd, env_vars)
|
38
|
+
Bolt::Util.exec_docker(cmd, env_vars)
|
39
|
+
end
|
40
|
+
|
41
|
+
private def env_hash
|
42
|
+
# Set the DOCKER_HOST if we are using a non-default service-url
|
43
|
+
@docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
|
44
|
+
end
|
45
|
+
|
37
46
|
def connect
|
38
47
|
# We don't actually have a connection, but we do need to
|
39
48
|
# check that the container exists and is running.
|
@@ -54,10 +63,7 @@ module Bolt
|
|
54
63
|
end
|
55
64
|
|
56
65
|
def add_env_vars(env_vars)
|
57
|
-
@env_vars =
|
58
|
-
acc << "--env"
|
59
|
-
acc << "#{env_var[0]}=#{env_var[1]}"
|
60
|
-
end
|
66
|
+
@env_vars = Bolt::Util.format_env_vars_for_cli(env_vars)
|
61
67
|
end
|
62
68
|
|
63
69
|
# Executes a command inside the target container. This is called from the shell class.
|
@@ -88,9 +94,9 @@ module Bolt
|
|
88
94
|
|
89
95
|
def upload_file(source, destination)
|
90
96
|
@logger.trace { "Uploading #{source} to #{destination}" }
|
91
|
-
|
92
|
-
unless
|
93
|
-
raise "Error writing to container #{container_id}: #{
|
97
|
+
_out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
|
98
|
+
unless stat.exitstatus.zero?
|
99
|
+
raise "Error writing to container #{container_id}: #{err}"
|
94
100
|
end
|
95
101
|
rescue StandardError => e
|
96
102
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
@@ -102,31 +108,14 @@ module Bolt
|
|
102
108
|
# copy the *contents* of the directory.
|
103
109
|
# https://docs.docker.com/engine/reference/commandline/cp/
|
104
110
|
FileUtils.mkdir_p(destination)
|
105
|
-
|
106
|
-
unless
|
107
|
-
raise "Error downloading content from container #{container_id}: #{
|
111
|
+
_out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
|
112
|
+
unless stat.exitstatus.zero?
|
113
|
+
raise "Error downloading content from container #{container_id}: #{err}"
|
108
114
|
end
|
109
115
|
rescue StandardError => e
|
110
116
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
111
117
|
end
|
112
118
|
|
113
|
-
# Executes a Docker CLI command. This is useful for running commands as
|
114
|
-
# part of this class without having to go through the `execute`
|
115
|
-
# function and manage pipes.
|
116
|
-
#
|
117
|
-
# @param subcommand [String] The docker subcommand to run
|
118
|
-
# e.g. 'inspect' for `docker inspect`
|
119
|
-
# @param arguments [Array] Arguments to pass to the docker command
|
120
|
-
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
121
|
-
# @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
|
122
|
-
private def execute_local_command(subcommand, arguments = [])
|
123
|
-
# Set the DOCKER_HOST if we are using a non-default service-url
|
124
|
-
env_hash = @docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
|
125
|
-
docker_command = [subcommand].concat(arguments)
|
126
|
-
|
127
|
-
Open3.capture3(env_hash, 'docker', *docker_command, { binmode: true })
|
128
|
-
end
|
129
|
-
|
130
119
|
# Executes a Docker CLI command and parses the output in JSON format
|
131
120
|
#
|
132
121
|
# @param subcommand [String] The docker subcommand to run
|
@@ -134,15 +123,15 @@ module Bolt
|
|
134
123
|
# @param arguments [Array] Arguments to pass to the docker command
|
135
124
|
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
136
125
|
# @return [Object] Ruby object representation of the JSON string
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
extract_json(
|
126
|
+
def execute_local_json_command(subcommand, arguments = [])
|
127
|
+
cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
|
128
|
+
out, _err, _stat = run_cmd(cmd, env_hash)
|
129
|
+
extract_json(out)
|
141
130
|
end
|
142
131
|
|
143
132
|
# Converts the JSON encoded STDOUT string from the docker cli into ruby objects
|
144
133
|
#
|
145
|
-
# @param
|
134
|
+
# @param stdout [String] The string to convert
|
146
135
|
# @return [Object] Ruby object representation of the JSON string
|
147
136
|
private def extract_json(stdout)
|
148
137
|
# The output from the docker format command is a JSON string per line.
|
@@ -24,17 +24,17 @@ module Bolt
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def container_id
|
27
|
-
"
|
27
|
+
"#{@target.transport_config['remote']}:#{@target.host}"
|
28
28
|
end
|
29
29
|
|
30
30
|
def connect
|
31
|
-
out, err, status = execute_local_command(%
|
31
|
+
out, err, status = execute_local_command(%W[list #{container_id} --format json])
|
32
32
|
unless status.exitstatus.zero?
|
33
33
|
raise "Error listing available containers: #{err}"
|
34
34
|
end
|
35
|
-
containers = JSON.parse(out)
|
36
|
-
|
37
|
-
raise "Could not find a container with name or ID matching '#{
|
35
|
+
containers = JSON.parse(out)
|
36
|
+
if containers.empty?
|
37
|
+
raise "Could not find a container with name or ID matching '#{container_id}'"
|
38
38
|
end
|
39
39
|
@logger.trace("Opened session")
|
40
40
|
true
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -59,6 +59,18 @@ module Bolt
|
|
59
59
|
# the result otherwise make sure an error is generated
|
60
60
|
if state == 'finished' || (result && result['_error'])
|
61
61
|
if result['_error']
|
62
|
+
unless result['_error'].is_a?(Hash)
|
63
|
+
result['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
|
64
|
+
'issue_code' => 'TASK_ERROR',
|
65
|
+
'msg' => result['_error'],
|
66
|
+
'details' => {} }
|
67
|
+
end
|
68
|
+
|
69
|
+
result['_error']['details'] ||= {}
|
70
|
+
unless result['_error']['details'].is_a?(Hash)
|
71
|
+
deets = result['_error']['details']
|
72
|
+
result['_error']['details'] = { 'msg' => deets }
|
73
|
+
end
|
62
74
|
file_line = %w[file line].zip(position).to_h.compact
|
63
75
|
result['_error']['details'].merge!(file_line) unless result['_error']['details']['file']
|
64
76
|
end
|
@@ -252,11 +264,7 @@ module Bolt
|
|
252
264
|
|
253
265
|
# If we get here, there's no error so we don't need the file or line
|
254
266
|
# number
|
255
|
-
Bolt::Result.for_command(target,
|
256
|
-
result.value['stdout'],
|
257
|
-
result.value['stderr'],
|
258
|
-
result.value['exit_code'],
|
259
|
-
action, obj, [])
|
267
|
+
Bolt::Result.for_command(target, result.value, action, obj, [])
|
260
268
|
end
|
261
269
|
end
|
262
270
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'bolt/transport/base'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
module Transport
|
9
|
+
class Podman < Docker
|
10
|
+
def with_connection(target)
|
11
|
+
conn = Connection.new(target)
|
12
|
+
conn.connect
|
13
|
+
yield conn
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'bolt/transport/podman/connection'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logging'
|
4
|
+
require 'bolt/node/errors'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
module Transport
|
8
|
+
class Podman < Docker
|
9
|
+
class Connection < Connection
|
10
|
+
attr_reader :user, :target
|
11
|
+
|
12
|
+
def initialize(target)
|
13
|
+
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
|
14
|
+
@target = target
|
15
|
+
@user = ENV['USER'] || Etc.getlogin
|
16
|
+
@logger = Bolt::Logger.logger(target.safe_name)
|
17
|
+
@container_info = {}
|
18
|
+
@logger.trace("Initializing podman connection to #{target.safe_name}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_cmd(cmd, env_vars)
|
22
|
+
Bolt::Util.exec_podman(cmd, env_vars)
|
23
|
+
end
|
24
|
+
|
25
|
+
def shell
|
26
|
+
@shell ||= if Bolt::Util.windows?
|
27
|
+
Bolt::Shell::Powershell.new(target, self)
|
28
|
+
else
|
29
|
+
Bolt::Shell::Bash.new(target, self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def connect
|
34
|
+
# We don't actually have a connection, but we do need to
|
35
|
+
# check that the container exists and is running.
|
36
|
+
ps = execute_local_json_command('ps')
|
37
|
+
container = Array(ps).find { |item|
|
38
|
+
item["ID"].to_s.eql?(@target.host) ||
|
39
|
+
item["Id"].to_s.start_with?(@target.host) ||
|
40
|
+
Array(item["Names"]).include?(@target.host)
|
41
|
+
}
|
42
|
+
raise "Could not find a container with name or ID matching '#{@target.host}'" if container.nil?
|
43
|
+
# Now find the indepth container information
|
44
|
+
id = container["ID"] || container["Id"]
|
45
|
+
output = execute_local_json_command('inspect', [id])
|
46
|
+
# Store the container information for later
|
47
|
+
@container_info = output.first
|
48
|
+
@logger.trace { "Opened session" }
|
49
|
+
true
|
50
|
+
rescue StandardError => e
|
51
|
+
raise Bolt::Node::ConnectError.new(
|
52
|
+
"Failed to connect to #{target.safe_name}: #{e.message}",
|
53
|
+
'CONNECT_ERROR'
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Executes a command inside the target container. This is called from the shell class.
|
58
|
+
#
|
59
|
+
# @param command [string] The command to run
|
60
|
+
def execute(command)
|
61
|
+
args = []
|
62
|
+
args += %w[--interactive]
|
63
|
+
args += %w[--tty] if target.options['tty']
|
64
|
+
args += @env_vars if @env_vars
|
65
|
+
|
66
|
+
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
67
|
+
# escape any double quotes in command
|
68
|
+
command = command.gsub('"', '\"')
|
69
|
+
command = "#{target.options['shell-command']} \"#{command}\""
|
70
|
+
end
|
71
|
+
|
72
|
+
podman_command = %w[podman exec] + args + [container_id] + Shellwords.split(command)
|
73
|
+
@logger.trace { "Executing: #{podman_command.join(' ')}" }
|
74
|
+
|
75
|
+
Open3.popen3(*podman_command)
|
76
|
+
rescue StandardError
|
77
|
+
@logger.trace { "Command aborted" }
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
|
81
|
+
# Converts the JSON encoded STDOUT string from the podman cli into ruby objects
|
82
|
+
#
|
83
|
+
# @param stdout [String] The string to convert
|
84
|
+
# @return [Object] Ruby object representation of the JSON string
|
85
|
+
private def extract_json(stdout)
|
86
|
+
# Podman renders the output in pretty JSON, which results in a newline
|
87
|
+
# appearing in the output before the closing bracket.
|
88
|
+
# should we only get a single line with no newline at all, we also
|
89
|
+
# assume it is a single minified JSON object
|
90
|
+
stdout.strip!
|
91
|
+
newline = stdout.index("\n") || -1
|
92
|
+
bracket = stdout.index('}') || -1
|
93
|
+
JSON.parse(stdout) if bracket > newline
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/bolt/util.rb
CHANGED
@@ -77,6 +77,14 @@ module Bolt
|
|
77
77
|
File.exist?(path) ? read_yaml_hash(path, file_name) : {}
|
78
78
|
end
|
79
79
|
|
80
|
+
def first_runs_free
|
81
|
+
Bolt::Config.user_path + '.first_runs_free'
|
82
|
+
end
|
83
|
+
|
84
|
+
def first_run?
|
85
|
+
Bolt::Config.user_path && !File.exist?(first_runs_free)
|
86
|
+
end
|
87
|
+
|
80
88
|
# Accepts a path with either 'plans' or 'tasks' in it and determines
|
81
89
|
# the name of the module
|
82
90
|
def module_name(path)
|
@@ -324,6 +332,40 @@ module Bolt
|
|
324
332
|
end
|
325
333
|
end
|
326
334
|
|
335
|
+
# Executes a Docker CLI command. This is useful for running commands as
|
336
|
+
# part of this class without having to go through the `execute`
|
337
|
+
# function and manage pipes.
|
338
|
+
#
|
339
|
+
# @param cmd [String] The docker command and arguments to run
|
340
|
+
# e.g. 'cp <src> <dest>' for `docker cp <src> <dest>`
|
341
|
+
# @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
|
342
|
+
def exec_docker(cmd, env = {})
|
343
|
+
Open3.capture3(env, 'docker', *cmd, { binmode: true })
|
344
|
+
end
|
345
|
+
|
346
|
+
# Executes a Podman CLI command. This is useful for running commands as
|
347
|
+
# part of this class without having to go through the `execute`
|
348
|
+
# function and manage pipes.
|
349
|
+
#
|
350
|
+
# @param cmd [String] The podman command and arguments to run
|
351
|
+
# e.g. 'cp <src> <dest>' for `podman cp <src> <dest>`
|
352
|
+
# @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
|
353
|
+
def exec_podman(cmd, env = {})
|
354
|
+
Open3.capture3(env, 'podman', *cmd, { binmode: true })
|
355
|
+
end
|
356
|
+
|
357
|
+
# Formats a map of environment variables to be passed to a command that
|
358
|
+
# accepts repeated `--env` flags
|
359
|
+
#
|
360
|
+
# @param env_vars [Hash] A map of environment variables keys and their values
|
361
|
+
# @return [String]
|
362
|
+
def format_env_vars_for_cli(env_vars)
|
363
|
+
@env_vars = env_vars.each_with_object([]) do |(key, value), acc|
|
364
|
+
acc << "--env"
|
365
|
+
acc << "#{key}=#{value}"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
327
369
|
def unix_basename(path)
|
328
370
|
raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
|
329
371
|
path.split('/').last
|
data/lib/bolt/version.rb
CHANGED
@@ -703,6 +703,9 @@ module BoltServer
|
|
703
703
|
connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
|
704
704
|
plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
|
705
705
|
plugins.add_plugin(connect_plugin)
|
706
|
+
%w[aws_inventory azure_inventory gcloud_inventory].each do |plugin_name|
|
707
|
+
plugins.add_module_plugin(plugin_name) if plugins.known_plugin?(plugin_name)
|
708
|
+
end
|
706
709
|
inventory = Bolt::Inventory.from_config(context[:config], plugins)
|
707
710
|
target_list = inventory.get_targets('all').map do |targ|
|
708
711
|
targ.to_h.merge({ 'transport' => targ.transport, 'plugin_hooks' => targ.plugin_hooks })
|
@@ -29,7 +29,14 @@ module BoltSpec
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def result_for(target, stdout: '', stderr: '')
|
32
|
-
|
32
|
+
value = {
|
33
|
+
'stdout' => stdout,
|
34
|
+
'stderr' => stderr,
|
35
|
+
'merged_output' => "#{stdout}\n#{stderr}".strip,
|
36
|
+
'exit_code' => 0
|
37
|
+
}
|
38
|
+
|
39
|
+
Bolt::Result.for_command(target, value, 'command', '', [])
|
33
40
|
end
|
34
41
|
|
35
42
|
# Public methods
|
@@ -35,7 +35,14 @@ module BoltSpec
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def result_for(target, stdout: '', stderr: '')
|
38
|
-
|
38
|
+
value = {
|
39
|
+
'stdout' => stdout,
|
40
|
+
'stderr' => stderr,
|
41
|
+
'merged_output' => "#{stdout}\n#{stderr}".strip,
|
42
|
+
'exit_code' => 0
|
43
|
+
}
|
44
|
+
|
45
|
+
Bolt::Result.for_command(target, value, 'script', '', [])
|
39
46
|
end
|
40
47
|
|
41
48
|
# Public methods
|
@@ -17,13 +17,14 @@ module BoltSpec
|
|
17
17
|
|
18
18
|
# Nothing on the executor is 'public'
|
19
19
|
class MockExecutor
|
20
|
-
attr_reader :noop, :error_message, :in_parallel
|
20
|
+
attr_reader :noop, :error_message, :in_parallel, :transports, :future
|
21
21
|
attr_accessor :run_as, :transport_features, :execute_any_plan
|
22
22
|
|
23
23
|
def initialize(modulepath)
|
24
24
|
@noop = false
|
25
25
|
@run_as = nil
|
26
26
|
@in_parallel = false
|
27
|
+
@future = {}
|
27
28
|
@error_message = nil
|
28
29
|
@allow_apply = false
|
29
30
|
@modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
|
@@ -91,6 +92,14 @@ module BoltSpec
|
|
91
92
|
result
|
92
93
|
end
|
93
94
|
|
95
|
+
def run_task_with(target_mapping, task, options = {}, _position = [])
|
96
|
+
resultsets = target_mapping.map do |target, arguments|
|
97
|
+
run_task([target], task, arguments, options)
|
98
|
+
end.compact
|
99
|
+
|
100
|
+
Bolt::ResultSet.new(resultsets.map(&:results).flatten)
|
101
|
+
end
|
102
|
+
|
94
103
|
def download_file(targets, source, destination, options = {}, _position = [])
|
95
104
|
result = nil
|
96
105
|
if (doub = @download_doubles[source] || @download_doubles[:default])
|
@@ -210,16 +219,6 @@ module BoltSpec
|
|
210
219
|
yield
|
211
220
|
end
|
212
221
|
|
213
|
-
def report_function_call(_function); end
|
214
|
-
|
215
|
-
def report_bundled_content(_mode, _name); end
|
216
|
-
|
217
|
-
def report_file_source(_plan_function, _source); end
|
218
|
-
|
219
|
-
def report_apply(_statements, _resources); end
|
220
|
-
|
221
|
-
def report_yaml_plan(_plan); end
|
222
|
-
|
223
222
|
def publish_event(event)
|
224
223
|
if event[:type] == :message
|
225
224
|
unless @stub_out_message
|
@@ -257,6 +256,87 @@ module BoltSpec
|
|
257
256
|
end.new(transport_features)
|
258
257
|
end
|
259
258
|
# End apply_prep mocking
|
259
|
+
|
260
|
+
# Evaluates a `parallelize()` block and returns the result. Normally,
|
261
|
+
# Bolt's executor wraps this in a Yarn for each object passed to the
|
262
|
+
# `parallelize()` function, and then executes them in parallel before
|
263
|
+
# returning the result from the block. However, in BoltSpec the block is
|
264
|
+
# executed for each object sequentially, and this function returns the
|
265
|
+
# result itself.
|
266
|
+
#
|
267
|
+
def create_yarn(scope, block, object, _index)
|
268
|
+
# Create the new scope
|
269
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
270
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
271
|
+
|
272
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
273
|
+
current_scope = scope.effective_symtable(true)
|
274
|
+
until current_scope.nil?
|
275
|
+
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
276
|
+
current_scope = current_scope.parent
|
277
|
+
end
|
278
|
+
newscope.push_ephemerals([local])
|
279
|
+
|
280
|
+
begin
|
281
|
+
result = catch(:return) do
|
282
|
+
args = { block.parameters[0][1].to_s => object }
|
283
|
+
block.closure.call_by_name_with_scope(newscope, args, true)
|
284
|
+
end
|
285
|
+
|
286
|
+
# If we got a return from the block, get it's value
|
287
|
+
# Otherwise the result is the last line from the block
|
288
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
289
|
+
|
290
|
+
# Validate the result is a PlanResult
|
291
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
292
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
293
|
+
end
|
294
|
+
|
295
|
+
result
|
296
|
+
rescue Puppet::PreformattedError => e
|
297
|
+
if e.cause.is_a?(Bolt::Error)
|
298
|
+
e.cause
|
299
|
+
else
|
300
|
+
raise e
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# BoltSpec already evaluated the `parallelize()` block for each object
|
306
|
+
# passed to the function, so these results can be returned as-is.
|
307
|
+
#
|
308
|
+
def round_robin(results)
|
309
|
+
results
|
310
|
+
end
|
311
|
+
|
312
|
+
# Public methods on Bolt::Executor that need to be mocked so there aren't
|
313
|
+
# "undefined method" errors.
|
314
|
+
|
315
|
+
def batch_execute(_targets); end
|
316
|
+
|
317
|
+
def finish_plan(_plan_result); end
|
318
|
+
|
319
|
+
def handle_event(_event); end
|
320
|
+
|
321
|
+
def prompt(_prompt, _options); end
|
322
|
+
|
323
|
+
def report_function_call(_function); end
|
324
|
+
|
325
|
+
def report_bundled_content(_mode, _name); end
|
326
|
+
|
327
|
+
def report_file_source(_plan_function, _source); end
|
328
|
+
|
329
|
+
def report_apply(_statements, _resources); end
|
330
|
+
|
331
|
+
def report_yaml_plan(_plan); end
|
332
|
+
|
333
|
+
def shutdown; end
|
334
|
+
|
335
|
+
def start_plan(_plan_context); end
|
336
|
+
|
337
|
+
def subscribe(_subscriber, _types = nil); end
|
338
|
+
|
339
|
+
def unsubscribe(_subscriber, _types = nil); end
|
260
340
|
end
|
261
341
|
end
|
262
342
|
end
|