bolt 3.0.0 → 3.5.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 +13 -11
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
- 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_plan.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +6 -3
- data/lib/bolt/cli.rb +96 -16
- data/lib/bolt/config.rb +4 -0
- data/lib/bolt/config/options.rb +21 -3
- data/lib/bolt/config/transport/lxd.rb +23 -0
- data/lib/bolt/config/transport/options.rb +8 -1
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +15 -0
- data/lib/bolt/executor.rb +22 -7
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/logger.rb +8 -0
- data/lib/bolt/module_installer.rb +2 -2
- data/lib/bolt/module_installer/puppetfile.rb +2 -2
- data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
- data/lib/bolt/node/output.rb +14 -4
- data/lib/bolt/outputter/human.rb +106 -23
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal.rb +25 -4
- data/lib/bolt/pal/yaml_plan.rb +1 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
- data/lib/bolt/pal/yaml_plan/step.rb +91 -31
- data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
- data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
- data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
- data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
- data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
- data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
- data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
- data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
- data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
- data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
- data/lib/bolt/plan_creator.rb +1 -1
- data/lib/bolt/project_manager.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +1 -1
- data/lib/bolt/result.rb +11 -15
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +61 -31
- data/lib/bolt/shell/bash/tmpdir.rb +2 -2
- data/lib/bolt/shell/powershell.rb +34 -12
- data/lib/bolt/shell/powershell/snippets.rb +30 -3
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +0 -9
- data/lib/bolt/transport/docker.rb +1 -125
- data/lib/bolt/transport/docker/connection.rb +77 -167
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/lxd/connection.rb +99 -0
- data/lib/bolt/transport/orch.rb +13 -5
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +31 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +61 -33
- data/lib/bolt_spec/bolt_context.rb +9 -4
- data/lib/bolt_spec/plans.rb +1 -109
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- 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 +90 -7
- data/modules/puppet_connect/plans/test_input_data.pp +65 -7
- metadata +9 -2
@@ -5,31 +5,30 @@ module Bolt
|
|
5
5
|
class YamlPlan
|
6
6
|
class Step
|
7
7
|
class Upload < Step
|
8
|
-
def self.
|
9
|
-
|
8
|
+
def self.option_keys
|
9
|
+
Set['catch_errors', 'run_as']
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.required_keys
|
13
|
-
Set['
|
13
|
+
Set['destination', 'targets', 'upload']
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def transpile
|
23
|
-
code = String.new(" ")
|
24
|
-
code << "$#{@name} = " if @name
|
16
|
+
# Returns an array of arguments to pass to the step's function call
|
17
|
+
#
|
18
|
+
private def format_args(body)
|
19
|
+
opts = format_options(body)
|
25
20
|
|
26
|
-
|
27
|
-
args
|
28
|
-
args <<
|
21
|
+
args = [body['upload'], body['destination'], body['targets']]
|
22
|
+
args << body['description'] if body['description']
|
23
|
+
args << opts if opts.any?
|
29
24
|
|
30
|
-
|
25
|
+
args
|
26
|
+
end
|
31
27
|
|
32
|
-
|
28
|
+
# Returns the function corresponding to the step
|
29
|
+
#
|
30
|
+
private def function
|
31
|
+
'upload_file'
|
33
32
|
end
|
34
33
|
end
|
35
34
|
end
|
@@ -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
|
@@ -29,7 +29,7 @@ module Bolt
|
|
29
29
|
|
30
30
|
plan_string = String.new('')
|
31
31
|
plan_string << "# #{plan_object.description}\n" if plan_object.description
|
32
|
-
plan_string << "# WARNING: This is an autogenerated plan. It
|
32
|
+
plan_string << "# WARNING: This is an autogenerated plan. It might not behave as expected.\n"
|
33
33
|
plan_string << "# @private #{plan_object.private}\n" unless plan_object.private.nil?
|
34
34
|
plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
|
35
35
|
|
data/lib/bolt/plan_creator.rb
CHANGED
@@ -22,7 +22,7 @@ module Bolt
|
|
22
22
|
Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
|
23
23
|
separated by double colons '::'.
|
24
24
|
|
25
|
-
Each name segment must begin with a lowercase letter, and
|
25
|
+
Each name segment must begin with a lowercase letter, and can only include lowercase
|
26
26
|
letters, digits, and underscores.
|
27
27
|
|
28
28
|
Examples of valid plan names:
|
data/lib/bolt/project_manager.rb
CHANGED
@@ -143,7 +143,7 @@ module Bolt
|
|
143
143
|
@outputter.print_message("Migrating project #{@config.project.path}\n\n")
|
144
144
|
|
145
145
|
@outputter.print_action_step(
|
146
|
-
"Migrating a Bolt project
|
146
|
+
"Migrating a Bolt project might make irreversible changes to the project's "\
|
147
147
|
"configuration and inventory files. Before continuing, make sure the "\
|
148
148
|
"project has a backup or uses a version control system."
|
149
149
|
)
|
@@ -66,7 +66,7 @@ module Bolt
|
|
66
66
|
# Attempt to resolve dependencies
|
67
67
|
begin
|
68
68
|
@outputter.print_message('')
|
69
|
-
@outputter.print_action_step("Resolving module dependencies, this
|
69
|
+
@outputter.print_action_step("Resolving module dependencies, this might take a moment")
|
70
70
|
puppetfile = Bolt::ModuleInstaller::Resolver.new.resolve(specs)
|
71
71
|
rescue Bolt::Error => e
|
72
72
|
@outputter.print_action_error("#{e.message}\nSkipping module migration.")
|
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
|
@@ -203,12 +194,17 @@ module Bolt
|
|
203
194
|
end
|
204
195
|
|
205
196
|
def to_data
|
197
|
+
serialized_value = safe_value
|
198
|
+
if serialized_value.key?('_sensitive') &&
|
199
|
+
serialized_value['_sensitive'].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
|
200
|
+
serialized_value['_sensitive'] = serialized_value['_sensitive'].to_s
|
201
|
+
end
|
206
202
|
{
|
207
203
|
"target" => @target.name,
|
208
204
|
"action" => action,
|
209
205
|
"object" => object,
|
210
206
|
"status" => status,
|
211
|
-
"value" =>
|
207
|
+
"value" => serialized_value
|
212
208
|
}
|
213
209
|
end
|
214
210
|
|
data/lib/bolt/shell.rb
CHANGED
@@ -8,6 +8,22 @@ module Bolt
|
|
8
8
|
@target = target
|
9
9
|
@conn = conn
|
10
10
|
@logger = Bolt::Logger.logger(@target.safe_name)
|
11
|
+
|
12
|
+
if Bolt::Logger.stream
|
13
|
+
Bolt::Logger.warn_once("stream_experimental",
|
14
|
+
"The 'stream' option is experimental, and might "\
|
15
|
+
"include breaking changes between minor versions.")
|
16
|
+
@stream_logger = Bolt::Logger.logger(:stream)
|
17
|
+
# Don't send stream messages to the parent logger
|
18
|
+
@stream_logger.additive = false
|
19
|
+
|
20
|
+
# Log stream messages without any other data or color
|
21
|
+
pattern = Logging.layouts.pattern(pattern: '%m\n')
|
22
|
+
@stream_logger.appenders = Logging.appenders.stdout(
|
23
|
+
'console',
|
24
|
+
layout: pattern
|
25
|
+
)
|
26
|
+
end
|
11
27
|
end
|
12
28
|
|
13
29
|
def run_command(*_args)
|
data/lib/bolt/shell/bash.rb
CHANGED
@@ -12,7 +12,6 @@ module Bolt
|
|
12
12
|
super
|
13
13
|
|
14
14
|
@run_as = nil
|
15
|
-
|
16
15
|
@sudo_id = SecureRandom.uuid
|
17
16
|
@sudo_password = @target.options['sudo-password'] || @target.password
|
18
17
|
end
|
@@ -25,9 +24,7 @@ module Bolt
|
|
25
24
|
running_as(options[:run_as]) do
|
26
25
|
output = execute(command, environment: options[:env_vars], sudoable: true)
|
27
26
|
Bolt::Result.for_command(target,
|
28
|
-
output.
|
29
|
-
output.stderr.string,
|
30
|
-
output.exit_code,
|
27
|
+
output.to_h,
|
31
28
|
'command',
|
32
29
|
command,
|
33
30
|
position)
|
@@ -54,19 +51,35 @@ module Bolt
|
|
54
51
|
|
55
52
|
def download(source, destination, options = {})
|
56
53
|
running_as(options[:run_as]) do
|
57
|
-
# Target OS may be either Unix or Windows. Without knowing the target OS before-hand
|
58
|
-
# we can't assume whether the path separator is '/' or '\'. Assume we're connecting
|
59
|
-
# to a target with Unix and then check if the path exists after downloading.
|
60
54
|
download = File.join(destination, Bolt::Util.unix_basename(source))
|
61
55
|
|
62
|
-
|
56
|
+
# If using run-as, the file is copied to a tmpdir and chowned to the
|
57
|
+
# connecting user. This is a workaround for limitations in net-ssh that
|
58
|
+
# only allow for downloading files as the connecting user, which is a
|
59
|
+
# problem for users who cannot connect to targets as the root user.
|
60
|
+
# This temporary copy should *always* be deleted.
|
61
|
+
if run_as
|
62
|
+
with_tmpdir(force_cleanup: true) do |dir|
|
63
|
+
tmpfile = File.join(dir.to_s, Bolt::Util.unix_basename(source))
|
64
|
+
|
65
|
+
result = execute(['cp', '-r', source, dir.to_s], sudoable: true)
|
66
|
+
|
67
|
+
if result.exit_code != 0
|
68
|
+
message = "Could not copy file '#{source}' to temporary directory '#{dir}': #{result.stderr.string}"
|
69
|
+
raise Bolt::Node::FileError.new(message, 'CP_ERROR')
|
70
|
+
end
|
63
71
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
# We need to force the chown, otherwise this will just return
|
73
|
+
# without doing anything since the chown user is the same as the
|
74
|
+
# connecting user.
|
75
|
+
dir.chown(conn.user, force: true)
|
76
|
+
|
77
|
+
conn.download_file(tmpfile, destination, download)
|
78
|
+
end
|
79
|
+
# If not using run-as, we can skip creating a temporary copy and just
|
80
|
+
# download the file directly.
|
81
|
+
else
|
82
|
+
conn.download_file(source, destination, download)
|
70
83
|
end
|
71
84
|
|
72
85
|
Bolt::Result.for_download(target, source, destination, download)
|
@@ -83,9 +96,7 @@ module Bolt
|
|
83
96
|
dir.chown(run_as)
|
84
97
|
output = execute([path, *arguments], environment: options[:env_vars], sudoable: true)
|
85
98
|
Bolt::Result.for_command(target,
|
86
|
-
output.
|
87
|
-
output.stderr.string,
|
88
|
-
output.exit_code,
|
99
|
+
output.to_h,
|
89
100
|
'script',
|
90
101
|
script,
|
91
102
|
position)
|
@@ -134,18 +145,21 @@ module Bolt
|
|
134
145
|
|
135
146
|
remote_task_path = write_executable(task_dir, executable)
|
136
147
|
|
148
|
+
execute_options[:stdin] = stdin
|
149
|
+
|
137
150
|
# Avoid the horrors of passing data on stdin via a tty on multiple platforms
|
138
151
|
# by writing a wrapper script that directs stdin to the task.
|
139
152
|
if stdin && target.options['tty']
|
140
153
|
wrapper = make_wrapper_stringio(remote_task_path, stdin, execute_options[:interpreter])
|
154
|
+
# Wrapper script handles interpreter and stdin. Delete these execute options
|
141
155
|
execute_options.delete(:interpreter)
|
156
|
+
execute_options.delete(:stdin)
|
142
157
|
execute_options[:wrapper] = true
|
143
158
|
remote_task_path = write_executable(dir, wrapper, 'wrapper.sh')
|
144
159
|
end
|
145
160
|
|
146
161
|
dir.chown(run_as)
|
147
162
|
|
148
|
-
execute_options[:stdin] = stdin
|
149
163
|
execute_options[:sudoable] = true if run_as
|
150
164
|
output = execute(remote_task_path, **execute_options)
|
151
165
|
end
|
@@ -291,12 +305,12 @@ module Bolt
|
|
291
305
|
|
292
306
|
# A helper to create and delete a tmpdir on the remote system. Yields the
|
293
307
|
# directory name.
|
294
|
-
def with_tmpdir
|
308
|
+
def with_tmpdir(force_cleanup: false)
|
295
309
|
dir = make_tmpdir
|
296
310
|
yield dir
|
297
311
|
ensure
|
298
312
|
if dir
|
299
|
-
if target.options['cleanup']
|
313
|
+
if target.options['cleanup'] || force_cleanup
|
300
314
|
dir.delete
|
301
315
|
else
|
302
316
|
Bolt::Logger.warn("skip_cleanup", "Skipping cleanup of tmpdir #{dir}")
|
@@ -331,10 +345,15 @@ module Bolt
|
|
331
345
|
# together multiple commands into a single sh invocation
|
332
346
|
commands = [inject_interpreter(options[:interpreter], command)]
|
333
347
|
|
348
|
+
# Let the transport handle adding environment variables if it's custom.
|
334
349
|
if options[:environment]
|
335
|
-
|
336
|
-
|
337
|
-
|
350
|
+
if defined? conn.add_env_vars
|
351
|
+
conn.add_env_vars(options[:environment])
|
352
|
+
else
|
353
|
+
env_decl = options[:environment].map do |env, val|
|
354
|
+
"#{env}=#{Shellwords.shellescape(val)}"
|
355
|
+
end.join(' ')
|
356
|
+
end
|
338
357
|
end
|
339
358
|
|
340
359
|
if escalate
|
@@ -385,14 +404,24 @@ module Bolt
|
|
385
404
|
# See if we can read from out or err, or write to in
|
386
405
|
ready_read, ready_write, = select(read_streams.keys, write_stream, nil, timeout)
|
387
406
|
|
388
|
-
# Read from out and err
|
389
407
|
ready_read&.each do |stream|
|
408
|
+
stream_name = stream == out ? 'out' : 'err'
|
390
409
|
# Check for sudo prompt
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
410
|
+
to_print = if use_sudo
|
411
|
+
check_sudo(stream, inp, options[:stdin])
|
412
|
+
else
|
413
|
+
stream.readpartial(CHUNK_SIZE)
|
414
|
+
end
|
415
|
+
|
416
|
+
if !to_print.chomp.empty? && @stream_logger
|
417
|
+
formatted = to_print.lines.map do |msg|
|
418
|
+
"[#{@target.safe_name}] #{stream_name}: #{msg.chomp}"
|
419
|
+
end.join("\n")
|
420
|
+
@stream_logger.warn(formatted)
|
421
|
+
end
|
422
|
+
|
423
|
+
read_streams[stream] << to_print
|
424
|
+
result_output.merged_output << to_print
|
396
425
|
rescue EOFError
|
397
426
|
end
|
398
427
|
|
@@ -440,9 +469,10 @@ module Bolt
|
|
440
469
|
when 0
|
441
470
|
@logger.trace { "Command `#{command_str}` returned successfully" }
|
442
471
|
when 126
|
443
|
-
msg = "\n\nThis
|
472
|
+
msg = "\n\nThis might be caused by the default tmpdir being mounted "\
|
444
473
|
"using 'noexec'. See http://pup.pt/task-failure for details and workarounds."
|
445
|
-
result_output.stderr
|
474
|
+
result_output.stderr << msg
|
475
|
+
result_output.merged_output << msg
|
446
476
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
447
477
|
else
|
448
478
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
@@ -24,8 +24,8 @@ module Bolt
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def chown(owner)
|
28
|
-
return if owner.nil? || owner == @owner
|
27
|
+
def chown(owner, force: false)
|
28
|
+
return if owner.nil? || (owner == @owner && !force)
|
29
29
|
|
30
30
|
result = @shell.execute(['id', '-g', owner])
|
31
31
|
if result.exit_code != 0
|
@@ -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)
|
@@ -208,20 +206,23 @@ module Bolt
|
|
208
206
|
arguments = unwrap_sensitive_args(arguments)
|
209
207
|
with_tmpdir do |dir|
|
210
208
|
script_path = write_executable(dir, script)
|
211
|
-
command = if powershell_file?(script_path)
|
209
|
+
command = if powershell_file?(script_path) && options[:pwsh_params]
|
210
|
+
# Scripts run with pwsh_params can be run like tasks
|
211
|
+
Snippets.ps_task(script_path, options[:pwsh_params])
|
212
|
+
elsif powershell_file?(script_path)
|
212
213
|
Snippets.run_script(arguments, script_path)
|
213
214
|
else
|
214
215
|
path, args = *process_from_extension(script_path)
|
215
216
|
args += escape_arguments(arguments)
|
216
217
|
execute_process(path, args)
|
217
218
|
end
|
218
|
-
|
219
|
+
env_assignments = options[:env_vars] ? env_declarations(options[:env_vars]) : []
|
220
|
+
shell_init = options[:pwsh_params] ? Snippets.shell_init : ''
|
221
|
+
|
222
|
+
output = execute([shell_init, *env_assignments, command].join("\r\n"))
|
219
223
|
|
220
|
-
output = execute(command)
|
221
224
|
Bolt::Result.for_command(target,
|
222
|
-
output.
|
223
|
-
output.stderr.string,
|
224
|
-
output.exit_code,
|
225
|
+
output.to_h,
|
225
226
|
'script',
|
226
227
|
script,
|
227
228
|
position)
|
@@ -274,7 +275,12 @@ module Bolt
|
|
274
275
|
[]
|
275
276
|
end
|
276
277
|
|
277
|
-
output = execute([
|
278
|
+
output = execute([
|
279
|
+
Snippets.shell_init,
|
280
|
+
Snippets.append_ps_module_path(dir),
|
281
|
+
*env_assignments,
|
282
|
+
command
|
283
|
+
].join("\n"))
|
278
284
|
|
279
285
|
Bolt::Result.for_task(target, output.stdout.string,
|
280
286
|
output.stderr.string,
|
@@ -305,12 +311,28 @@ module Bolt
|
|
305
311
|
# the proper encoding so the string isn't later misinterpreted
|
306
312
|
encoding = out.external_encoding
|
307
313
|
out.binmode
|
308
|
-
|
314
|
+
to_print = out.read.force_encoding(encoding)
|
315
|
+
if !to_print.chomp.empty? && @stream_logger
|
316
|
+
formatted = to_print.lines.map do |msg|
|
317
|
+
"[#{@target.safe_name}] out: #{msg.chomp}"
|
318
|
+
end.join("\n")
|
319
|
+
@stream_logger.warn(formatted)
|
320
|
+
end
|
321
|
+
result.stdout << to_print
|
322
|
+
result.merged_output << to_print
|
309
323
|
end
|
310
324
|
stderr = Thread.new do
|
311
325
|
encoding = err.external_encoding
|
312
326
|
err.binmode
|
313
|
-
|
327
|
+
to_print = err.read.force_encoding(encoding)
|
328
|
+
if !to_print.chomp.empty? && @stream_logger
|
329
|
+
formatted = to_print.lines.map do |msg|
|
330
|
+
"[#{@target.safe_name}] err: #{msg.chomp}"
|
331
|
+
end.join("\n")
|
332
|
+
@stream_logger.warn(formatted)
|
333
|
+
end
|
334
|
+
result.stderr << to_print
|
335
|
+
result.merged_output << to_print
|
314
336
|
end
|
315
337
|
|
316
338
|
stdout.join
|