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
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
require 'bolt/config/transport/base'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class Config
|
8
|
+
module Transport
|
9
|
+
class LXD < Base
|
10
|
+
OPTIONS = %w[
|
11
|
+
cleanup
|
12
|
+
remote
|
13
|
+
tmpdir
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
DEFAULTS = {
|
17
|
+
'cleanup' => true,
|
18
|
+
'remote' => 'local'
|
19
|
+
}.freeze
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -32,7 +32,7 @@ module Bolt
|
|
32
32
|
"cleanup" => {
|
33
33
|
type: [TrueClass, FalseClass],
|
34
34
|
description: "Whether to clean up temporary files created on targets. When running commands on a target, "\
|
35
|
-
"Bolt
|
35
|
+
"Bolt might create temporary files. After completing the command, these files are "\
|
36
36
|
"automatically deleted. This value can be set to 'false' if you wish to leave these "\
|
37
37
|
"temporary files on the target.",
|
38
38
|
_plugin: true,
|
@@ -266,6 +266,13 @@ module Bolt
|
|
266
266
|
_plugin: true,
|
267
267
|
_example: "BOLT.PRODUCTION"
|
268
268
|
},
|
269
|
+
"remote" => {
|
270
|
+
type: String,
|
271
|
+
description: "The LXD remote host to use.",
|
272
|
+
_default: "local",
|
273
|
+
_plugin: false,
|
274
|
+
_example: 'myremote'
|
275
|
+
},
|
269
276
|
"run-as" => {
|
270
277
|
type: String,
|
271
278
|
description: "The user to run commands as after login. The run-as user must be different than the "\
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'bolt/error'
|
5
|
+
require 'bolt/result'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
class ContainerResult
|
9
|
+
attr_reader :value, :object
|
10
|
+
|
11
|
+
def self.from_exception(exception, exit_code, image, position: [])
|
12
|
+
details = Bolt::Result.create_details(position)
|
13
|
+
error = {
|
14
|
+
'kind' => 'puppetlabs.tasks/container-error',
|
15
|
+
'issue_code' => 'CONTAINER_ERROR',
|
16
|
+
'msg' => "Error running container '#{image}': #{exception}",
|
17
|
+
'details' => details
|
18
|
+
}
|
19
|
+
error['details']['exit_code'] = exit_code
|
20
|
+
ContainerResult.new({ '_error' => error }, object: image)
|
21
|
+
end
|
22
|
+
|
23
|
+
def _pcore_init_hash
|
24
|
+
{ 'value' => @value,
|
25
|
+
'object' => @image }
|
26
|
+
end
|
27
|
+
|
28
|
+
# First argument can't be named given the way that Puppet deserializes variables
|
29
|
+
def initialize(value = nil, object: nil)
|
30
|
+
@value = value || {}
|
31
|
+
@object = object
|
32
|
+
end
|
33
|
+
|
34
|
+
def eql?(other)
|
35
|
+
self.class == other.class &&
|
36
|
+
value == other.value
|
37
|
+
end
|
38
|
+
alias == eql?
|
39
|
+
|
40
|
+
def [](key)
|
41
|
+
value[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_json(opts = nil)
|
45
|
+
to_data.to_json(opts)
|
46
|
+
end
|
47
|
+
alias to_s to_json
|
48
|
+
|
49
|
+
# This is the value with all non-UTF-8 characters removed, suitable for
|
50
|
+
# printing or converting to JSON. It *should* only be possible to have
|
51
|
+
# non-UTF-8 characters in stdout/stderr keys as they are not allowed from
|
52
|
+
# tasks but we scrub the whole thing just in case.
|
53
|
+
def safe_value
|
54
|
+
Bolt::Util.walk_vals(value) do |val|
|
55
|
+
if val.is_a?(String)
|
56
|
+
# Replace invalid bytes with hex codes, ie. \xDE\xAD\xBE\xEF
|
57
|
+
val.scrub { |c| c.bytes.map { |b| "\\x" + b.to_s(16).upcase }.join }
|
58
|
+
else
|
59
|
+
val
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stdout
|
65
|
+
value['stdout']
|
66
|
+
end
|
67
|
+
|
68
|
+
def stderr
|
69
|
+
value['stderr']
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_data
|
73
|
+
{
|
74
|
+
"object" => object,
|
75
|
+
"status" => status,
|
76
|
+
"value" => safe_value
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def status
|
81
|
+
ok? ? 'success' : 'failure'
|
82
|
+
end
|
83
|
+
|
84
|
+
def ok?
|
85
|
+
error_hash.nil?
|
86
|
+
end
|
87
|
+
alias ok ok?
|
88
|
+
alias success? ok?
|
89
|
+
|
90
|
+
# This allows access to errors outside puppet compilation
|
91
|
+
# it should be prefered over error in bolt code
|
92
|
+
def error_hash
|
93
|
+
value['_error']
|
94
|
+
end
|
95
|
+
|
96
|
+
# Warning: This will fail outside of a compilation.
|
97
|
+
# Use error_hash inside bolt.
|
98
|
+
# Is it crazy for this to behave differently outside a compiler?
|
99
|
+
def error
|
100
|
+
if error_hash
|
101
|
+
Puppet::DataTypes::Error.from_asserted_hash(error_hash)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/bolt/error.rb
CHANGED
@@ -61,6 +61,21 @@ module Bolt
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
class ContainerFailure < Bolt::Error
|
65
|
+
attr_reader :result
|
66
|
+
|
67
|
+
def initialize(result)
|
68
|
+
details = {
|
69
|
+
'value' => result.value,
|
70
|
+
'object' => result.object
|
71
|
+
}
|
72
|
+
message = "Plan aborted: Running container '#{result.object}' failed."
|
73
|
+
super(message, 'bolt/container-failure', details)
|
74
|
+
@result = result
|
75
|
+
@error_code = 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
64
79
|
class RunFailure < Bolt::Error
|
65
80
|
attr_reader :result_set
|
66
81
|
|
data/lib/bolt/executor.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'English'
|
5
5
|
require 'json'
|
6
6
|
require 'logging'
|
7
|
+
require 'pathname'
|
7
8
|
require 'set'
|
8
9
|
require 'bolt/analytics'
|
9
10
|
require 'bolt/result'
|
@@ -15,6 +16,7 @@ require 'bolt/transport/ssh'
|
|
15
16
|
require 'bolt/transport/winrm'
|
16
17
|
require 'bolt/transport/orch'
|
17
18
|
require 'bolt/transport/local'
|
19
|
+
require 'bolt/transport/lxd'
|
18
20
|
require 'bolt/transport/docker'
|
19
21
|
require 'bolt/transport/remote'
|
20
22
|
require 'bolt/yarn'
|
@@ -25,6 +27,7 @@ module Bolt
|
|
25
27
|
winrm: Bolt::Transport::WinRM,
|
26
28
|
pcp: Bolt::Transport::Orch,
|
27
29
|
local: Bolt::Transport::Local,
|
30
|
+
lxd: Bolt::Transport::LXD,
|
28
31
|
docker: Bolt::Transport::Docker,
|
29
32
|
remote: Bolt::Transport::Remote
|
30
33
|
}.freeze
|
@@ -39,7 +42,6 @@ module Bolt
|
|
39
42
|
modified_concurrency = false)
|
40
43
|
# lazy-load expensive gem code
|
41
44
|
require 'concurrent'
|
42
|
-
|
43
45
|
@analytics = analytics
|
44
46
|
@logger = Bolt::Logger.logger(self)
|
45
47
|
|
@@ -121,8 +123,8 @@ module Bolt
|
|
121
123
|
def queue_execute(targets)
|
122
124
|
if @warn_concurrency && targets.length > @concurrency
|
123
125
|
@warn_concurrency = false
|
124
|
-
msg = "The ulimit is low, which
|
125
|
-
"'#{@concurrency}' to mitigate those issues, which
|
126
|
+
msg = "The ulimit is low, which might cause file limit issues. Default concurrency has been set to "\
|
127
|
+
"'#{@concurrency}' to mitigate those issues, which might cause Bolt to run slow. "\
|
126
128
|
"Disable this warning by configuring ulimit using 'ulimit -n <limit>' in your shell "\
|
127
129
|
"configuration, or by configuring Bolt's concurrency. "\
|
128
130
|
"See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
|
@@ -215,7 +217,7 @@ module Bolt
|
|
215
217
|
results
|
216
218
|
end
|
217
219
|
|
218
|
-
def report_transport(transport, count)
|
220
|
+
private def report_transport(transport, count)
|
219
221
|
name = transport.class.name.split('::').last.downcase
|
220
222
|
unless @reported_transports.include?(name)
|
221
223
|
@analytics&.event('Transport', 'initialize', label: name, value: count)
|
@@ -231,6 +233,11 @@ module Bolt
|
|
231
233
|
@analytics.report_bundled_content(mode, name)
|
232
234
|
end
|
233
235
|
|
236
|
+
def report_file_source(plan_function, source)
|
237
|
+
label = Pathname.new(source).absolute? ? 'absolute' : 'module'
|
238
|
+
@analytics&.event('Plan', plan_function, label: label)
|
239
|
+
end
|
240
|
+
|
234
241
|
def report_apply(statement_count, resource_counts)
|
235
242
|
data = { statement_count: statement_count }
|
236
243
|
|
@@ -468,7 +475,7 @@ module Bolt
|
|
468
475
|
Time.now
|
469
476
|
end
|
470
477
|
|
471
|
-
def wait_until(timeout, retry_interval)
|
478
|
+
private def wait_until(timeout, retry_interval)
|
472
479
|
start = wait_now
|
473
480
|
until yield
|
474
481
|
raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
|
@@ -477,22 +484,30 @@ module Bolt
|
|
477
484
|
end
|
478
485
|
|
479
486
|
def prompt(prompt, options)
|
480
|
-
@prompting = true
|
481
487
|
unless $stdin.tty?
|
488
|
+
return options[:default] if options[:default]
|
482
489
|
raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
|
483
490
|
end
|
484
491
|
|
485
|
-
|
492
|
+
@prompting = true
|
493
|
+
|
494
|
+
if options[:default] && !options[:sensitive]
|
495
|
+
$stderr.print("#{prompt} [#{options[:default]}]: ")
|
496
|
+
else
|
497
|
+
$stderr.print("#{prompt}: ")
|
498
|
+
end
|
486
499
|
|
487
500
|
value = if options[:sensitive]
|
488
501
|
$stdin.noecho(&:gets).to_s.chomp
|
489
502
|
else
|
490
503
|
$stdin.gets.to_s.chomp
|
491
504
|
end
|
505
|
+
|
492
506
|
@prompting = false
|
493
507
|
|
494
508
|
$stderr.puts if options[:sensitive]
|
495
509
|
|
510
|
+
value = options[:default] if value.empty?
|
496
511
|
value
|
497
512
|
end
|
498
513
|
|
@@ -11,8 +11,10 @@ module Bolt
|
|
11
11
|
facts
|
12
12
|
features
|
13
13
|
groups
|
14
|
+
plugin_hooks
|
14
15
|
targets
|
15
16
|
vars
|
17
|
+
version
|
16
18
|
].freeze
|
17
19
|
|
18
20
|
# Definitions used to validate the data.
|
@@ -123,6 +125,13 @@ module Bolt
|
|
123
125
|
description: "A map of variables for the group or target.",
|
124
126
|
type: Hash,
|
125
127
|
_plugin: true
|
128
|
+
},
|
129
|
+
"version" => {
|
130
|
+
description: "The version of the inventory file.",
|
131
|
+
type: Integer,
|
132
|
+
_plugin: false,
|
133
|
+
_example: 2,
|
134
|
+
_default: 2
|
126
135
|
}
|
127
136
|
}.freeze
|
128
137
|
end
|
@@ -92,6 +92,7 @@ module Bolt
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def add_facts(new_facts = {})
|
95
|
+
validate_fact_names(new_facts)
|
95
96
|
@facts = Bolt::Util.deep_merge(@facts, new_facts)
|
96
97
|
end
|
97
98
|
|
@@ -153,9 +154,24 @@ module Bolt
|
|
153
154
|
raise Bolt::UnknownTransportError.new(transport, uri)
|
154
155
|
end
|
155
156
|
|
157
|
+
validate_fact_names(facts)
|
158
|
+
|
156
159
|
transport_config
|
157
160
|
end
|
158
161
|
|
162
|
+
# Validate fact names and issue a deprecation warning if any fact names have a dot.
|
163
|
+
#
|
164
|
+
private def validate_fact_names(facts)
|
165
|
+
if (dotted = facts.keys.select { |name| name.include?('.') }).any?
|
166
|
+
Bolt::Logger.deprecate(
|
167
|
+
'dotted_fact_name',
|
168
|
+
"Target '#{safe_name}' includes dotted fact names: '#{dotted.join("', '")}'. Dotted fact "\
|
169
|
+
"names are deprecated and Bolt does not automatically convert facts with dotted names to "\
|
170
|
+
"structured facts. For more information, see https://pup.pt/bolt-dotted-facts"
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
159
175
|
def host
|
160
176
|
@uri_obj.hostname || transport_config['host']
|
161
177
|
end
|
data/lib/bolt/logger.rb
CHANGED
@@ -91,6 +91,14 @@ module Bolt
|
|
91
91
|
Logging.logger[:root].appenders.any?
|
92
92
|
end
|
93
93
|
|
94
|
+
def self.stream
|
95
|
+
@stream
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.stream=(stream)
|
99
|
+
@stream = stream
|
100
|
+
end
|
101
|
+
|
94
102
|
# A helper to ensure the Logging library is always initialized with our
|
95
103
|
# custom log levels before retrieving a Logger instance.
|
96
104
|
def self.logger(name)
|
@@ -45,7 +45,7 @@ module Bolt
|
|
45
45
|
# specss. If that fails, fall back to resolving from project specs.
|
46
46
|
# This prevents Bolt from modifying installed modules unless there is
|
47
47
|
# a version conflict.
|
48
|
-
@outputter.print_action_step("Resolving module dependencies, this
|
48
|
+
@outputter.print_action_step("Resolving module dependencies, this might take a moment")
|
49
49
|
|
50
50
|
@outputter.start_spin
|
51
51
|
begin
|
@@ -156,7 +156,7 @@ module Bolt
|
|
156
156
|
# If forcibly installing or if there is no Puppetfile, resolve
|
157
157
|
# and write a Puppetfile.
|
158
158
|
if force || !path.exist?
|
159
|
-
@outputter.print_action_step("Resolving module dependencies, this
|
159
|
+
@outputter.print_action_step("Resolving module dependencies, this might take a moment")
|
160
160
|
|
161
161
|
# This doesn't use the block as it's more testable to just mock *_spin
|
162
162
|
@outputter.start_spin
|
@@ -36,7 +36,7 @@ module Bolt
|
|
36
36
|
raise Bolt::ValidationError, <<~MSG
|
37
37
|
Unable to parse Puppetfile #{path}:
|
38
38
|
#{parsed.validation_errors.join("\n\n")}.
|
39
|
-
This
|
39
|
+
This Puppetfile might not be managed by Bolt.
|
40
40
|
MSG
|
41
41
|
end
|
42
42
|
|
@@ -106,7 +106,7 @@ module Bolt
|
|
106
106
|
|
107
107
|
#{unsatisfied_specs.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
|
108
108
|
|
109
|
-
This
|
109
|
+
This Puppetfile might not be managed by Bolt. To forcibly overwrite the
|
110
110
|
Puppetfile, run '#{command}'.
|
111
111
|
MESSAGE
|
112
112
|
|
@@ -39,8 +39,8 @@ module Bolt
|
|
39
39
|
unless (match = name.match(NAME_REGEX))
|
40
40
|
raise Bolt::ValidationError,
|
41
41
|
"Invalid name for Forge module specification: #{name}. Name must match "\
|
42
|
-
"'owner/name'. Owner segment
|
43
|
-
"segment must start with a lowercase letter and
|
42
|
+
"'owner/name'. Owner segment can only include letters or digits. Name "\
|
43
|
+
"segment must start with a lowercase letter and can only include lowercase "\
|
44
44
|
"letters, digits, and underscores."
|
45
45
|
end
|
46
46
|
|
@@ -49,8 +49,8 @@ module Bolt
|
|
49
49
|
unless (match = name.match(NAME_REGEX))
|
50
50
|
raise Bolt::ValidationError,
|
51
51
|
"Invalid name for Git module specification: #{name}. Name must match "\
|
52
|
-
"'name' or 'owner/name'. Owner segment
|
53
|
-
"Name segment must start with a lowercase letter and
|
52
|
+
"'name' or 'owner/name'. Owner segment can only include letters or digits. "\
|
53
|
+
"Name segment must start with a lowercase letter and can only include "\
|
54
54
|
"lowercase letters, digits, and underscores."
|
55
55
|
end
|
56
56
|
|
data/lib/bolt/node/output.rb
CHANGED
@@ -6,13 +6,23 @@ require 'bolt/result'
|
|
6
6
|
module Bolt
|
7
7
|
class Node
|
8
8
|
class Output
|
9
|
-
attr_reader :stdout, :
|
9
|
+
attr_reader :stderr, :stdout, :merged_output
|
10
10
|
attr_accessor :exit_code
|
11
11
|
|
12
12
|
def initialize
|
13
|
-
@stdout
|
14
|
-
@stderr
|
15
|
-
@
|
13
|
+
@stdout = StringIO.new
|
14
|
+
@stderr = StringIO.new
|
15
|
+
@merged_output = StringIO.new
|
16
|
+
@exit_code = 'unknown'
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
{
|
21
|
+
'stdout' => @stdout.string,
|
22
|
+
'stderr' => @stderr.string,
|
23
|
+
'merged_output' => @merged_output.string,
|
24
|
+
'exit_code' => @exit_code
|
25
|
+
}
|
16
26
|
end
|
17
27
|
end
|
18
28
|
end
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -53,10 +53,21 @@ module Bolt
|
|
53
53
|
string.sub(/\s\z/, '')
|
54
54
|
end
|
55
55
|
|
56
|
+
# Wraps a string to the specified width. Lines only wrap
|
57
|
+
# at whitespace.
|
58
|
+
#
|
56
59
|
def wrap(string, width = 80)
|
60
|
+
return string unless string.is_a?(String)
|
57
61
|
string.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
58
62
|
end
|
59
63
|
|
64
|
+
# Trims a string to a specified width, adding an ellipsis if it's longer.
|
65
|
+
#
|
66
|
+
def truncate(string, width = 80)
|
67
|
+
return string unless string.is_a?(String) && string.length > width
|
68
|
+
string.lines.first[0...width].gsub(/\s\w+\s*$/, '...')
|
69
|
+
end
|
70
|
+
|
60
71
|
def handle_event(event)
|
61
72
|
case event[:type]
|
62
73
|
when :enable_default_output
|
@@ -81,6 +92,10 @@ module Bolt
|
|
81
92
|
print_plan_start(event)
|
82
93
|
when :plan_finish
|
83
94
|
print_plan_finish(event)
|
95
|
+
when :container_start
|
96
|
+
print_container_start(event) if plan_logging?
|
97
|
+
when :container_finish
|
98
|
+
print_container_finish(event) if plan_logging?
|
84
99
|
when :start_spin
|
85
100
|
start_spin
|
86
101
|
when :stop_spin
|
@@ -101,6 +116,34 @@ module Bolt
|
|
101
116
|
@stream.puts(colorize(:green, "Started on #{target.safe_name}..."))
|
102
117
|
end
|
103
118
|
|
119
|
+
def print_container_result(result)
|
120
|
+
if result.success?
|
121
|
+
@stream.puts(colorize(:green, "Finished running container #{result.object}:"))
|
122
|
+
else
|
123
|
+
@stream.puts(colorize(:red, "Failed running container #{result.object}:"))
|
124
|
+
end
|
125
|
+
|
126
|
+
if result.error_hash
|
127
|
+
@stream.puts(colorize(:red, remove_trail(indent(2, result.error_hash['msg']))))
|
128
|
+
return 0
|
129
|
+
end
|
130
|
+
|
131
|
+
# Only print results if there's something other than empty string and hash
|
132
|
+
safe_value = result.safe_value
|
133
|
+
if safe_value['stdout'].strip.empty? && safe_value['stderr'].strip.empty?
|
134
|
+
@stream.puts(indent(2, "Running container #{result.object} completed successfully with no result"))
|
135
|
+
else
|
136
|
+
unless safe_value['stdout'].strip && safe_value['stdout'].strip.empty?
|
137
|
+
@stream.puts(indent(2, "STDOUT:"))
|
138
|
+
@stream.puts(indent(4, safe_value['stdout']))
|
139
|
+
end
|
140
|
+
unless safe_value['stderr'].strip.empty?
|
141
|
+
@stream.puts(indent(2, "STDERR:"))
|
142
|
+
@stream.puts(indent(4, safe_value['stderr']))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
104
147
|
def print_result(result)
|
105
148
|
if result.success?
|
106
149
|
@stream.puts(colorize(:green, "Finished on #{result.target.safe_name}:"))
|
@@ -131,16 +174,9 @@ module Bolt
|
|
131
174
|
end
|
132
175
|
|
133
176
|
# Use special handling if the result looks like a command or script result
|
134
|
-
if result.generic_value.keys == %w[stdout stderr exit_code]
|
177
|
+
if result.generic_value.keys == %w[stdout stderr merged_output exit_code]
|
135
178
|
safe_value = result.safe_value
|
136
|
-
unless safe_value['
|
137
|
-
@stream.puts(indent(2, "STDOUT:"))
|
138
|
-
@stream.puts(indent(4, safe_value['stdout']))
|
139
|
-
end
|
140
|
-
unless safe_value['stderr'].strip.empty?
|
141
|
-
@stream.puts(indent(2, "STDERR:"))
|
142
|
-
@stream.puts(indent(4, safe_value['stderr']))
|
143
|
-
end
|
179
|
+
@stream.puts(indent(2, safe_value['merged_output'])) unless safe_value['merged_output'].strip.empty?
|
144
180
|
elsif result.generic_value.any?
|
145
181
|
@stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
|
146
182
|
end
|
@@ -176,6 +212,25 @@ module Bolt
|
|
176
212
|
@stream.puts(colorize(:green, message))
|
177
213
|
end
|
178
214
|
|
215
|
+
def print_container_start(image:, **_kwargs)
|
216
|
+
@stream.puts(colorize(:green, "Starting: run container '#{image}'"))
|
217
|
+
end
|
218
|
+
|
219
|
+
def print_container_finish(event)
|
220
|
+
result = if event[:result].is_a?(Bolt::ContainerFailure)
|
221
|
+
event[:result].result
|
222
|
+
else
|
223
|
+
event[:result]
|
224
|
+
end
|
225
|
+
|
226
|
+
if result.success?
|
227
|
+
@stream.puts(colorize(:green, "Finished: run container '#{result.object}' succeeded."))
|
228
|
+
else
|
229
|
+
@stream.puts(colorize(:red, "Finished: run container '#{result.object}' failed."))
|
230
|
+
end
|
231
|
+
print_container_result(result) if @verbose
|
232
|
+
end
|
233
|
+
|
179
234
|
def print_plan_start(event)
|
180
235
|
@plan_depth += 1
|
181
236
|
# We use this event to both mark the start of a plan _and_ to enable
|
@@ -218,11 +273,11 @@ module Bolt
|
|
218
273
|
@stream.puts total_msg
|
219
274
|
end
|
220
275
|
|
221
|
-
def
|
276
|
+
def format_table(results, padding_left = 0, padding_right = 3)
|
222
277
|
# lazy-load expensive gem code
|
223
278
|
require 'terminal-table'
|
224
279
|
|
225
|
-
|
280
|
+
Terminal::Table.new(
|
226
281
|
rows: results,
|
227
282
|
style: {
|
228
283
|
border_x: '',
|
@@ -238,10 +293,22 @@ module Bolt
|
|
238
293
|
|
239
294
|
def print_tasks(tasks, modulepath)
|
240
295
|
command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
296
|
+
|
297
|
+
tasks = tasks.map do |name, description|
|
298
|
+
description = truncate(description, 72)
|
299
|
+
[name, description]
|
300
|
+
end
|
301
|
+
|
302
|
+
@stream.puts colorize(:cyan, 'Tasks')
|
303
|
+
@stream.puts tasks.any? ? format_table(tasks, 2) : indent(2, 'No available tasks')
|
304
|
+
@stream.puts
|
305
|
+
|
306
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
307
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
308
|
+
@stream.puts
|
309
|
+
|
310
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
311
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
|
245
312
|
end
|
246
313
|
|
247
314
|
# @param [Hash] task A hash representing the task
|
@@ -259,7 +326,7 @@ module Bolt
|
|
259
326
|
pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
|
260
327
|
pretty_params << " Default: #{v['default'].inspect}\n" if v.key?('default')
|
261
328
|
pretty_params << " #{v['description']}\n" if v['description']
|
262
|
-
usage << if v['type']
|
329
|
+
usage << if v['type']&.start_with?("Optional")
|
263
330
|
" [#{k}=<value>]"
|
264
331
|
else
|
265
332
|
" #{k}=<value>"
|
@@ -267,7 +334,7 @@ module Bolt
|
|
267
334
|
end
|
268
335
|
|
269
336
|
if task.supports_noop
|
270
|
-
usage << Bolt::Util.powershell? ? '[-Noop]' : '[--noop]'
|
337
|
+
usage << (Bolt::Util.powershell? ? ' [-Noop]' : ' [--noop]')
|
271
338
|
end
|
272
339
|
|
273
340
|
task_info << "\n#{task.name}"
|
@@ -322,10 +389,22 @@ module Bolt
|
|
322
389
|
|
323
390
|
def print_plans(plans, modulepath)
|
324
391
|
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
392
|
+
|
393
|
+
plans = plans.map do |name, description|
|
394
|
+
description = truncate(description, 72)
|
395
|
+
[name, description]
|
396
|
+
end
|
397
|
+
|
398
|
+
@stream.puts colorize(:cyan, 'Plans')
|
399
|
+
@stream.puts plans.any? ? format_table(plans, 2) : indent(2, 'No available plans')
|
400
|
+
@stream.puts
|
401
|
+
|
402
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
403
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
404
|
+
@stream.puts
|
405
|
+
|
406
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
407
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
|
329
408
|
end
|
330
409
|
|
331
410
|
def print_topics(topics)
|
@@ -359,7 +438,7 @@ module Bolt
|
|
359
438
|
[m[:name], version]
|
360
439
|
end
|
361
440
|
|
362
|
-
|
441
|
+
@stream.puts format_table(module_info, 2, 1)
|
363
442
|
end
|
364
443
|
|
365
444
|
@stream.write("\n")
|
@@ -374,7 +453,7 @@ module Bolt
|
|
374
453
|
targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
|
375
454
|
|
376
455
|
if targets.any?
|
377
|
-
|
456
|
+
@stream.puts format_table(targets, 0, 2)
|
378
457
|
@stream.puts
|
379
458
|
end
|
380
459
|
|
@@ -417,6 +496,10 @@ module Bolt
|
|
417
496
|
@stream.puts("Plan completed successfully with no result")
|
418
497
|
when Bolt::ApplyFailure, Bolt::RunFailure
|
419
498
|
print_result_set(value.result_set)
|
499
|
+
when Bolt::ContainerResult
|
500
|
+
print_container_result(value)
|
501
|
+
when Bolt::ContainerFailure
|
502
|
+
print_container_result(value.result)
|
420
503
|
when Bolt::ResultSet
|
421
504
|
print_result_set(value)
|
422
505
|
else
|