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
data/lib/bolt/outputter/json.rb
CHANGED
@@ -100,12 +100,12 @@ module Bolt
|
|
100
100
|
moduledir: moduledir.to_s }.to_json)
|
101
101
|
end
|
102
102
|
|
103
|
-
def print_targets(target_list,
|
103
|
+
def print_targets(target_list, inventory_source, default_inventory, _target_flag)
|
104
104
|
@stream.puts ::JSON.pretty_generate(
|
105
105
|
inventory: {
|
106
106
|
targets: target_list[:inventory].map(&:name),
|
107
107
|
count: target_list[:inventory].count,
|
108
|
-
file:
|
108
|
+
file: (inventory_source || default_inventory).to_s
|
109
109
|
},
|
110
110
|
adhoc: {
|
111
111
|
targets: target_list[:adhoc].map(&:name),
|
@@ -116,14 +116,16 @@ module Bolt
|
|
116
116
|
)
|
117
117
|
end
|
118
118
|
|
119
|
-
def print_target_info(
|
119
|
+
def print_target_info(target_list, _inventory_source, _default_inventory, _target_flag)
|
120
|
+
targets = target_list.values.flatten
|
121
|
+
|
120
122
|
@stream.puts ::JSON.pretty_generate(
|
121
123
|
targets: targets.map(&:detail),
|
122
124
|
count: targets.count
|
123
125
|
)
|
124
126
|
end
|
125
127
|
|
126
|
-
def print_groups(groups)
|
128
|
+
def print_groups(groups, _inventory_source, _default_inventory)
|
127
129
|
count = groups.count
|
128
130
|
@stream.puts({ groups: groups,
|
129
131
|
count: count }.to_json)
|
@@ -20,6 +20,10 @@ module Bolt
|
|
20
20
|
log_plan_start(event)
|
21
21
|
when :plan_finish
|
22
22
|
log_plan_finish(event)
|
23
|
+
when :container_start
|
24
|
+
log_container_start(event)
|
25
|
+
when :container_finish
|
26
|
+
log_container_finish(event)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -48,6 +52,19 @@ module Bolt
|
|
48
52
|
duration = event[:duration]
|
49
53
|
@logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
|
50
54
|
end
|
55
|
+
|
56
|
+
def log_container_start(event)
|
57
|
+
@logger.info("Starting: run container '#{event[:image]}'")
|
58
|
+
end
|
59
|
+
|
60
|
+
def log_container_finish(event)
|
61
|
+
result = event[:result]
|
62
|
+
if result.success?
|
63
|
+
@logger.info("Finished: run container '#{result.object}' succeeded.")
|
64
|
+
else
|
65
|
+
@logger.info("Finished: run container '#{result.object}' failed.")
|
66
|
+
end
|
67
|
+
end
|
51
68
|
end
|
52
69
|
end
|
53
70
|
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -521,10 +521,30 @@ module Bolt
|
|
521
521
|
end
|
522
522
|
end
|
523
523
|
|
524
|
-
def convert_plan(
|
524
|
+
def convert_plan(plan)
|
525
|
+
path = File.expand_path(plan)
|
526
|
+
|
527
|
+
# If the path doesn't exist, check if it's a plan name
|
528
|
+
unless File.exist?(path)
|
529
|
+
in_bolt_compiler do |compiler|
|
530
|
+
sig = compiler.plan_signature(plan)
|
531
|
+
|
532
|
+
# If the plan was loaded, look for it on the module loader
|
533
|
+
# There has to be an easier way to do this...
|
534
|
+
if sig
|
535
|
+
type = compiler.list_plans.find { |p| p.name == plan }
|
536
|
+
path = sig.instance_variable_get(:@plan_func)
|
537
|
+
.loader
|
538
|
+
.find(type)
|
539
|
+
.origin
|
540
|
+
.first
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
525
545
|
Puppet[:tasks] = true
|
526
546
|
transpiler = YamlPlan::Transpiler.new
|
527
|
-
transpiler.transpile(
|
547
|
+
transpiler.transpile(path)
|
528
548
|
end
|
529
549
|
|
530
550
|
# Returns a mapping of all modules available to the Bolt compiler
|
@@ -122,9 +122,11 @@ module Bolt
|
|
122
122
|
raise StepError.new("Parameters key must be a hash", body['name'], step_number)
|
123
123
|
end
|
124
124
|
|
125
|
-
metaparams =
|
125
|
+
metaparams = body['parameters'].keys
|
126
|
+
.select { |key| key.start_with?('_') }
|
127
|
+
.map { |key| key.sub(/^_/, '') }
|
126
128
|
|
127
|
-
if (dups = body
|
129
|
+
if (dups = body.keys & metaparams).any?
|
128
130
|
raise StepError.new(
|
129
131
|
"Cannot specify metaparameters when using top-level keys with same name: #{dups.join(', ')}",
|
130
132
|
body['name'],
|
@@ -13,6 +13,14 @@ module Bolt
|
|
13
13
|
Set['command', 'targets']
|
14
14
|
end
|
15
15
|
|
16
|
+
def self.validate_step_keys(body, number)
|
17
|
+
super
|
18
|
+
|
19
|
+
if body.key?('env_vars') && ![Hash, String].include?(body['env_vars'].class)
|
20
|
+
raise StepError.new('env_vars key must be a hash or evaluable string', body['name'], number)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
16
24
|
# Returns an array of arguments to pass to the step's function call
|
17
25
|
#
|
18
26
|
private def format_args(body)
|
@@ -27,6 +27,10 @@ module Bolt
|
|
27
27
|
if body.key?('pwsh_params') && !body['pwsh_params'].nil? && !body['pwsh_params'].is_a?(Hash)
|
28
28
|
raise StepError.new('pwsh_params key must be a hash', body['name'], number)
|
29
29
|
end
|
30
|
+
|
31
|
+
if body.key?('env_vars') && ![Hash, String].include?(body['env_vars'].class)
|
32
|
+
raise StepError.new('env_vars key must be a hash or evaluable string', body['name'], number)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
# Returns an array of arguments to pass to the step's function call
|
@@ -14,8 +14,8 @@ module Bolt
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
def transpile(
|
18
|
-
@plan_path =
|
17
|
+
def transpile(plan_path)
|
18
|
+
@plan_path = plan_path
|
19
19
|
@modulename = Bolt::Util.module_name(@plan_path)
|
20
20
|
@filename = @plan_path.split(File::SEPARATOR)[-1]
|
21
21
|
validate_path
|
data/lib/bolt/plan_creator.rb
CHANGED
@@ -36,8 +36,8 @@ module Bolt
|
|
36
36
|
prefix, _, basename = segment_plan_name(plan_name)
|
37
37
|
|
38
38
|
unless prefix == project.name
|
39
|
-
message = "
|
40
|
-
|
39
|
+
message = "Incomplete plan name: A plan name must be prefixed with the name of the "\
|
40
|
+
"project or module. Did you mean '#{project.name}::#{plan_name}'?"
|
41
41
|
|
42
42
|
raise Bolt::ValidationError, message
|
43
43
|
end
|
data/lib/bolt/plugin.rb
CHANGED
@@ -178,8 +178,6 @@ module Bolt
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def add_ruby_plugin(plugin_name)
|
181
|
-
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
182
|
-
|
183
181
|
cls_name = Bolt::Util.snake_name_to_class_name(plugin_name)
|
184
182
|
filename = "bolt/plugin/#{plugin_name}"
|
185
183
|
require filename
|
@@ -203,8 +201,6 @@ module Bolt
|
|
203
201
|
}
|
204
202
|
|
205
203
|
mod = modules[plugin_name]
|
206
|
-
raise PluginError::Unknown, plugin_name unless mod&.plugin?
|
207
|
-
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
208
204
|
|
209
205
|
plugin = Bolt::Plugin::Module.load(mod, opts)
|
210
206
|
add_plugin(plugin)
|
@@ -224,6 +220,12 @@ module Bolt
|
|
224
220
|
end
|
225
221
|
end
|
226
222
|
|
223
|
+
def known_plugin?(plugin_name)
|
224
|
+
@plugins.include?(plugin_name) ||
|
225
|
+
RUBY_PLUGINS.include?(plugin_name) ||
|
226
|
+
(modules.include?(plugin_name) && modules[plugin_name].plugin?)
|
227
|
+
end
|
228
|
+
|
227
229
|
def get_hook(plugin_name, hook)
|
228
230
|
plugin = by_name(plugin_name)
|
229
231
|
raise PluginError::Unknown, plugin_name unless plugin
|
@@ -235,16 +237,16 @@ module Bolt
|
|
235
237
|
|
236
238
|
# Calling by_name or get_hook will load any module based plugin automatically
|
237
239
|
def by_name(plugin_name)
|
238
|
-
|
239
|
-
|
240
|
-
|
240
|
+
if known_plugin?(plugin_name)
|
241
|
+
if @plugins.include?(plugin_name)
|
242
|
+
@plugins[plugin_name]
|
243
|
+
elsif !@load_plugins
|
244
|
+
raise PluginError::LoadingDisabled, plugin_name
|
245
|
+
elsif RUBY_PLUGINS.include?(plugin_name)
|
241
246
|
add_ruby_plugin(plugin_name)
|
242
|
-
|
247
|
+
else
|
243
248
|
add_module_plugin(plugin_name)
|
244
249
|
end
|
245
|
-
rescue PluginError::Unknown
|
246
|
-
@unknown << plugin_name
|
247
|
-
nil
|
248
250
|
end
|
249
251
|
end
|
250
252
|
|
data/lib/bolt/puppetdb/client.rb
CHANGED
@@ -95,6 +95,60 @@ module Bolt
|
|
95
95
|
make_query(query, path)
|
96
96
|
end
|
97
97
|
|
98
|
+
# Sends a command to PuppetDB using version 1 of the commands API.
|
99
|
+
# https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html
|
100
|
+
#
|
101
|
+
# @param command [String] The command to invoke.
|
102
|
+
# @param version [Integer] The version of the command to invoke.
|
103
|
+
# @param payload [Hash] The payload to send with the command.
|
104
|
+
# @return A UUID identifying the submitted command.
|
105
|
+
#
|
106
|
+
def send_command(command, version, payload)
|
107
|
+
command = command.dup.force_encoding('utf-8')
|
108
|
+
body = JSON.generate(payload)
|
109
|
+
|
110
|
+
# PDB requires the following query parameters to the POST request.
|
111
|
+
# Error early if there's no certname, as PDB does not return a
|
112
|
+
# message indicating it's required.
|
113
|
+
unless payload['certname']
|
114
|
+
raise Bolt::Error.new(
|
115
|
+
"Payload must include 'certname', unable to invoke command.",
|
116
|
+
'bolt/pdb-command'
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
url = uri.tap do |u|
|
121
|
+
u.path = 'pdb/cmd/v1'
|
122
|
+
u.query_values = { 'command' => command,
|
123
|
+
'version' => version,
|
124
|
+
'certname' => payload['certname'] }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Send the command to PDB
|
128
|
+
begin
|
129
|
+
@logger.debug("Sending PuppetDB command '#{command}' to #{url}")
|
130
|
+
response = http_client.post(url.to_s, body: body, header: headers)
|
131
|
+
rescue StandardError => e
|
132
|
+
raise Bolt::PuppetDBFailoverError, "Failed to invoke PuppetDB command: #{e}"
|
133
|
+
end
|
134
|
+
|
135
|
+
@logger.debug("Got response code #{response.code} from PuppetDB")
|
136
|
+
if response.code != 200
|
137
|
+
raise Bolt::PuppetDBError, "Failed to invoke PuppetDB command: #{response.body}"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return the UUID string from the response body
|
141
|
+
begin
|
142
|
+
JSON.parse(response.body).fetch('uuid', nil)
|
143
|
+
rescue JSON::ParserError
|
144
|
+
raise Bolt::PuppetDBError, "Unable to parse response as JSON: #{response.body}"
|
145
|
+
end
|
146
|
+
rescue Bolt::PuppetDBFailoverError => e
|
147
|
+
@logger.error("Request to puppetdb at #{@current_url} failed with #{e}.")
|
148
|
+
reject_url
|
149
|
+
send_command(command, version, payload)
|
150
|
+
end
|
151
|
+
|
98
152
|
def http_client
|
99
153
|
return @http if @http
|
100
154
|
# lazy-load expensive gem code
|
data/lib/bolt/result.rb
CHANGED
@@ -28,20 +28,14 @@ module Bolt
|
|
28
28
|
%w[file line].zip(position).to_h.compact
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.for_command(target,
|
32
|
-
value = {
|
33
|
-
'stdout' => stdout,
|
34
|
-
'stderr' => stderr,
|
35
|
-
'exit_code' => exit_code
|
36
|
-
}
|
37
|
-
|
31
|
+
def self.for_command(target, value, action, command, position)
|
38
32
|
details = create_details(position)
|
39
|
-
unless exit_code == 0
|
40
|
-
details['exit_code'] = exit_code
|
33
|
+
unless value['exit_code'] == 0
|
34
|
+
details['exit_code'] = value['exit_code']
|
41
35
|
value['_error'] = {
|
42
36
|
'kind' => 'puppetlabs.tasks/command-error',
|
43
37
|
'issue_code' => 'COMMAND_ERROR',
|
44
|
-
'msg' => "The command failed with exit code #{exit_code}",
|
38
|
+
'msg' => "The command failed with exit code #{value['exit_code']}",
|
45
39
|
'details' => details
|
46
40
|
}
|
47
41
|
end
|
@@ -170,15 +164,12 @@ module Bolt
|
|
170
164
|
target == other.target &&
|
171
165
|
value == other.value
|
172
166
|
end
|
167
|
+
alias == eql?
|
173
168
|
|
174
169
|
def [](key)
|
175
170
|
value[key]
|
176
171
|
end
|
177
172
|
|
178
|
-
def ==(other)
|
179
|
-
eql?(other)
|
180
|
-
end
|
181
|
-
|
182
173
|
def to_json(opts = nil)
|
183
174
|
to_data.to_json(opts)
|
184
175
|
end
|
data/lib/bolt/shell/bash.rb
CHANGED
@@ -24,9 +24,7 @@ module Bolt
|
|
24
24
|
running_as(options[:run_as]) do
|
25
25
|
output = execute(command, environment: options[:env_vars], sudoable: true)
|
26
26
|
Bolt::Result.for_command(target,
|
27
|
-
output.
|
28
|
-
output.stderr.string,
|
29
|
-
output.exit_code,
|
27
|
+
output.to_h,
|
30
28
|
'command',
|
31
29
|
command,
|
32
30
|
position)
|
@@ -98,9 +96,7 @@ module Bolt
|
|
98
96
|
dir.chown(run_as)
|
99
97
|
output = execute([path, *arguments], environment: options[:env_vars], sudoable: true)
|
100
98
|
Bolt::Result.for_command(target,
|
101
|
-
output.
|
102
|
-
output.stderr.string,
|
103
|
-
output.exit_code,
|
99
|
+
output.to_h,
|
104
100
|
'script',
|
105
101
|
script,
|
106
102
|
position)
|
@@ -149,21 +145,21 @@ module Bolt
|
|
149
145
|
|
150
146
|
remote_task_path = write_executable(task_dir, executable)
|
151
147
|
|
148
|
+
execute_options[:stdin] = stdin
|
149
|
+
|
152
150
|
# Avoid the horrors of passing data on stdin via a tty on multiple platforms
|
153
151
|
# by writing a wrapper script that directs stdin to the task.
|
154
152
|
if stdin && target.options['tty']
|
155
153
|
wrapper = make_wrapper_stringio(remote_task_path, stdin, execute_options[:interpreter])
|
154
|
+
# Wrapper script handles interpreter and stdin. Delete these execute options
|
156
155
|
execute_options.delete(:interpreter)
|
156
|
+
execute_options.delete(:stdin)
|
157
157
|
execute_options[:wrapper] = true
|
158
158
|
remote_task_path = write_executable(dir, wrapper, 'wrapper.sh')
|
159
159
|
end
|
160
160
|
|
161
161
|
dir.chown(run_as)
|
162
162
|
|
163
|
-
# Don't pass parameters on stdin if using a tty, as the parameters are
|
164
|
-
# already part of the wrapper script.
|
165
|
-
execute_options[:stdin] = stdin unless stdin && target.options['tty']
|
166
|
-
|
167
163
|
execute_options[:sudoable] = true if run_as
|
168
164
|
output = execute(remote_task_path, **execute_options)
|
169
165
|
end
|
@@ -400,7 +396,12 @@ module Bolt
|
|
400
396
|
# See if there's a sudo prompt
|
401
397
|
if use_sudo
|
402
398
|
ready_read = select([err], nil, nil, timeout * 5)
|
403
|
-
|
399
|
+
to_print = check_sudo(err, inp, options[:stdin]) if ready_read
|
400
|
+
unless to_print.nil?
|
401
|
+
log_stream(to_print, 'err')
|
402
|
+
read_streams[err] << to_print
|
403
|
+
result_output.merged_output << to_print
|
404
|
+
end
|
404
405
|
end
|
405
406
|
|
406
407
|
# True while the process is running or waiting for IO input
|
@@ -416,15 +417,9 @@ module Bolt
|
|
416
417
|
else
|
417
418
|
stream.readpartial(CHUNK_SIZE)
|
418
419
|
end
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
"[#{@target.safe_name}] #{stream_name}: #{msg.chomp}"
|
423
|
-
end.join("\n")
|
424
|
-
@stream_logger.warn(formatted)
|
425
|
-
end
|
426
|
-
|
427
|
-
read_streams[stream] << to_print
|
420
|
+
log_stream(to_print, stream_name)
|
421
|
+
read_streams[stream] << to_print
|
422
|
+
result_output.merged_output << to_print
|
428
423
|
rescue EOFError
|
429
424
|
end
|
430
425
|
|
@@ -461,7 +456,13 @@ module Bolt
|
|
461
456
|
# Read any remaining data in the pipe. Do not wait for
|
462
457
|
# EOF in case the pipe is inherited by a child process.
|
463
458
|
read_streams.each do |stream, _|
|
464
|
-
|
459
|
+
stream_name = stream == out ? 'out' : 'err'
|
460
|
+
loop {
|
461
|
+
to_print = stream.read_nonblock(CHUNK_SIZE)
|
462
|
+
log_stream(to_print, stream_name)
|
463
|
+
read_streams[stream] << to_print
|
464
|
+
result_output.merged_output << to_print
|
465
|
+
}
|
465
466
|
rescue Errno::EAGAIN, EOFError
|
466
467
|
end
|
467
468
|
result_output.stdout << read_streams[out]
|
@@ -474,7 +475,8 @@ module Bolt
|
|
474
475
|
when 126
|
475
476
|
msg = "\n\nThis might be caused by the default tmpdir being mounted "\
|
476
477
|
"using 'noexec'. See http://pup.pt/task-failure for details and workarounds."
|
477
|
-
result_output.stderr
|
478
|
+
result_output.stderr << msg
|
479
|
+
result_output.merged_output << msg
|
478
480
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
479
481
|
else
|
480
482
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
@@ -491,6 +493,15 @@ module Bolt
|
|
491
493
|
def sudo_prompt
|
492
494
|
'[sudo] Bolt needs to run as another user, password: '
|
493
495
|
end
|
496
|
+
|
497
|
+
private def log_stream(to_print, stream_name)
|
498
|
+
if !to_print.chomp.empty? && @stream_logger
|
499
|
+
formatted = to_print.lines.map do |msg|
|
500
|
+
"[#{@target.safe_name}] #{stream_name}: #{msg.chomp}"
|
501
|
+
end.join("\n")
|
502
|
+
@stream_logger.warn(formatted)
|
503
|
+
end
|
504
|
+
end
|
494
505
|
end
|
495
506
|
end
|
496
507
|
end
|
@@ -195,9 +195,7 @@ module Bolt
|
|
195
195
|
wrap_command = conn.is_a?(Bolt::Transport::Local::Connection)
|
196
196
|
output = execute(command, wrap_command)
|
197
197
|
Bolt::Result.for_command(target,
|
198
|
-
output.
|
199
|
-
output.stderr.string,
|
200
|
-
output.exit_code,
|
198
|
+
output.to_h,
|
201
199
|
'command',
|
202
200
|
command,
|
203
201
|
position)
|
@@ -224,9 +222,7 @@ module Bolt
|
|
224
222
|
output = execute([shell_init, *env_assignments, command].join("\r\n"))
|
225
223
|
|
226
224
|
Bolt::Result.for_command(target,
|
227
|
-
output.
|
228
|
-
output.stderr.string,
|
229
|
-
output.exit_code,
|
225
|
+
output.to_h,
|
230
226
|
'script',
|
231
227
|
script,
|
232
228
|
position)
|
@@ -322,7 +318,8 @@ module Bolt
|
|
322
318
|
end.join("\n")
|
323
319
|
@stream_logger.warn(formatted)
|
324
320
|
end
|
325
|
-
result.stdout
|
321
|
+
result.stdout << to_print
|
322
|
+
result.merged_output << to_print
|
326
323
|
end
|
327
324
|
stderr = Thread.new do
|
328
325
|
encoding = err.external_encoding
|
@@ -334,7 +331,8 @@ module Bolt
|
|
334
331
|
end.join("\n")
|
335
332
|
@stream_logger.warn(formatted)
|
336
333
|
end
|
337
|
-
result.stderr
|
334
|
+
result.stderr << to_print
|
335
|
+
result.merged_output << to_print
|
338
336
|
end
|
339
337
|
|
340
338
|
stdout.join
|