bolt 3.1.0 → 3.6.1
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 +11 -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/analytics.rb +4 -8
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +6 -3
- data/lib/bolt/cli.rb +121 -36
- data/lib/bolt/config.rb +15 -7
- data/lib/bolt/config/options.rb +62 -12
- data/lib/bolt/config/transport/lxd.rb +23 -0
- data/lib/bolt/config/transport/options.rb +8 -1
- 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 +37 -18
- 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 +259 -90
- data/lib/bolt/outputter/json.rb +3 -1
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal.rb +24 -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/plugin.rb +13 -11
- data/lib/bolt/project_manager.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +1 -1
- data/lib/bolt/result.rb +5 -14
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +68 -30
- data/lib/bolt/shell/bash/tmpdir.rb +2 -2
- data/lib/bolt/shell/powershell.rb +28 -11
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +21 -32
- 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/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +42 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +16 -1
- 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 +91 -7
- data/modules/puppet_connect/plans/test_input_data.pp +22 -0
- metadata +12 -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/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/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
|
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}")
|
@@ -382,7 +396,12 @@ module Bolt
|
|
382
396
|
# See if there's a sudo prompt
|
383
397
|
if use_sudo
|
384
398
|
ready_read = select([err], nil, nil, timeout * 5)
|
385
|
-
|
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
|
386
405
|
end
|
387
406
|
|
388
407
|
# True while the process is running or waiting for IO input
|
@@ -390,14 +409,17 @@ module Bolt
|
|
390
409
|
# See if we can read from out or err, or write to in
|
391
410
|
ready_read, ready_write, = select(read_streams.keys, write_stream, nil, timeout)
|
392
411
|
|
393
|
-
# Read from out and err
|
394
412
|
ready_read&.each do |stream|
|
413
|
+
stream_name = stream == out ? 'out' : 'err'
|
395
414
|
# Check for sudo prompt
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
415
|
+
to_print = if use_sudo
|
416
|
+
check_sudo(stream, inp, options[:stdin])
|
417
|
+
else
|
418
|
+
stream.readpartial(CHUNK_SIZE)
|
419
|
+
end
|
420
|
+
log_stream(to_print, stream_name)
|
421
|
+
read_streams[stream] << to_print
|
422
|
+
result_output.merged_output << to_print
|
401
423
|
rescue EOFError
|
402
424
|
end
|
403
425
|
|
@@ -434,7 +456,13 @@ module Bolt
|
|
434
456
|
# Read any remaining data in the pipe. Do not wait for
|
435
457
|
# EOF in case the pipe is inherited by a child process.
|
436
458
|
read_streams.each do |stream, _|
|
437
|
-
|
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
|
+
}
|
438
466
|
rescue Errno::EAGAIN, EOFError
|
439
467
|
end
|
440
468
|
result_output.stdout << read_streams[out]
|
@@ -445,9 +473,10 @@ module Bolt
|
|
445
473
|
when 0
|
446
474
|
@logger.trace { "Command `#{command_str}` returned successfully" }
|
447
475
|
when 126
|
448
|
-
msg = "\n\nThis
|
476
|
+
msg = "\n\nThis might be caused by the default tmpdir being mounted "\
|
449
477
|
"using 'noexec'. See http://pup.pt/task-failure for details and workarounds."
|
450
|
-
result_output.stderr
|
478
|
+
result_output.stderr << msg
|
479
|
+
result_output.merged_output << msg
|
451
480
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
452
481
|
else
|
453
482
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
@@ -464,6 +493,15 @@ module Bolt
|
|
464
493
|
def sudo_prompt
|
465
494
|
'[sudo] Bolt needs to run as another user, password: '
|
466
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
|
467
505
|
end
|
468
506
|
end
|
469
507
|
end
|
@@ -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)
|
@@ -310,12 +311,28 @@ module Bolt
|
|
310
311
|
# the proper encoding so the string isn't later misinterpreted
|
311
312
|
encoding = out.external_encoding
|
312
313
|
out.binmode
|
313
|
-
|
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
|
314
323
|
end
|
315
324
|
stderr = Thread.new do
|
316
325
|
encoding = err.external_encoding
|
317
326
|
err.binmode
|
318
|
-
|
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
|
319
336
|
end
|
320
337
|
|
321
338
|
stdout.join
|