bolt 3.0.1 → 3.6.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/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 +123 -37
- 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 +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/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 +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 +2 -126
- data/lib/bolt/transport/docker/connection.rb +81 -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/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 +64 -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 +91 -7
- data/modules/puppet_connect/plans/test_input_data.pp +65 -7
- metadata +12 -2
@@ -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
@@ -6,6 +6,7 @@ module Bolt
|
|
6
6
|
class Outputter
|
7
7
|
class Human < Bolt::Outputter
|
8
8
|
COLORS = {
|
9
|
+
dim: "2", # Dim, the other color of the rainbow
|
9
10
|
red: "31",
|
10
11
|
green: "32",
|
11
12
|
yellow: "33",
|
@@ -53,10 +54,21 @@ module Bolt
|
|
53
54
|
string.sub(/\s\z/, '')
|
54
55
|
end
|
55
56
|
|
57
|
+
# Wraps a string to the specified width. Lines only wrap
|
58
|
+
# at whitespace.
|
59
|
+
#
|
56
60
|
def wrap(string, width = 80)
|
61
|
+
return string unless string.is_a?(String)
|
57
62
|
string.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
58
63
|
end
|
59
64
|
|
65
|
+
# Trims a string to a specified width, adding an ellipsis if it's longer.
|
66
|
+
#
|
67
|
+
def truncate(string, width = 80)
|
68
|
+
return string unless string.is_a?(String) && string.length > width
|
69
|
+
string.lines.first[0...width].gsub(/\s\w+\s*$/, '...')
|
70
|
+
end
|
71
|
+
|
60
72
|
def handle_event(event)
|
61
73
|
case event[:type]
|
62
74
|
when :enable_default_output
|
@@ -81,6 +93,10 @@ module Bolt
|
|
81
93
|
print_plan_start(event)
|
82
94
|
when :plan_finish
|
83
95
|
print_plan_finish(event)
|
96
|
+
when :container_start
|
97
|
+
print_container_start(event) if plan_logging?
|
98
|
+
when :container_finish
|
99
|
+
print_container_finish(event) if plan_logging?
|
84
100
|
when :start_spin
|
85
101
|
start_spin
|
86
102
|
when :stop_spin
|
@@ -101,6 +117,34 @@ module Bolt
|
|
101
117
|
@stream.puts(colorize(:green, "Started on #{target.safe_name}..."))
|
102
118
|
end
|
103
119
|
|
120
|
+
def print_container_result(result)
|
121
|
+
if result.success?
|
122
|
+
@stream.puts(colorize(:green, "Finished running container #{result.object}:"))
|
123
|
+
else
|
124
|
+
@stream.puts(colorize(:red, "Failed running container #{result.object}:"))
|
125
|
+
end
|
126
|
+
|
127
|
+
if result.error_hash
|
128
|
+
@stream.puts(colorize(:red, remove_trail(indent(2, result.error_hash['msg']))))
|
129
|
+
return 0
|
130
|
+
end
|
131
|
+
|
132
|
+
# Only print results if there's something other than empty string and hash
|
133
|
+
safe_value = result.safe_value
|
134
|
+
if safe_value['stdout'].strip.empty? && safe_value['stderr'].strip.empty?
|
135
|
+
@stream.puts(indent(2, "Running container #{result.object} completed successfully with no result"))
|
136
|
+
else
|
137
|
+
unless safe_value['stdout'].strip && safe_value['stdout'].strip.empty?
|
138
|
+
@stream.puts(indent(2, "STDOUT:"))
|
139
|
+
@stream.puts(indent(4, safe_value['stdout']))
|
140
|
+
end
|
141
|
+
unless safe_value['stderr'].strip.empty?
|
142
|
+
@stream.puts(indent(2, "STDERR:"))
|
143
|
+
@stream.puts(indent(4, safe_value['stderr']))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
104
148
|
def print_result(result)
|
105
149
|
if result.success?
|
106
150
|
@stream.puts(colorize(:green, "Finished on #{result.target.safe_name}:"))
|
@@ -131,16 +175,9 @@ module Bolt
|
|
131
175
|
end
|
132
176
|
|
133
177
|
# Use special handling if the result looks like a command or script result
|
134
|
-
if result.generic_value.keys == %w[stdout stderr exit_code]
|
178
|
+
if result.generic_value.keys == %w[stdout stderr merged_output exit_code]
|
135
179
|
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
|
180
|
+
@stream.puts(indent(2, safe_value['merged_output'])) unless safe_value['merged_output'].strip.empty?
|
144
181
|
elsif result.generic_value.any?
|
145
182
|
@stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
|
146
183
|
end
|
@@ -176,6 +213,25 @@ module Bolt
|
|
176
213
|
@stream.puts(colorize(:green, message))
|
177
214
|
end
|
178
215
|
|
216
|
+
def print_container_start(image:, **_kwargs)
|
217
|
+
@stream.puts(colorize(:green, "Starting: run container '#{image}'"))
|
218
|
+
end
|
219
|
+
|
220
|
+
def print_container_finish(event)
|
221
|
+
result = if event[:result].is_a?(Bolt::ContainerFailure)
|
222
|
+
event[:result].result
|
223
|
+
else
|
224
|
+
event[:result]
|
225
|
+
end
|
226
|
+
|
227
|
+
if result.success?
|
228
|
+
@stream.puts(colorize(:green, "Finished: run container '#{result.object}' succeeded."))
|
229
|
+
else
|
230
|
+
@stream.puts(colorize(:red, "Finished: run container '#{result.object}' failed."))
|
231
|
+
end
|
232
|
+
print_container_result(result) if @verbose
|
233
|
+
end
|
234
|
+
|
179
235
|
def print_plan_start(event)
|
180
236
|
@plan_depth += 1
|
181
237
|
# We use this event to both mark the start of a plan _and_ to enable
|
@@ -218,11 +274,11 @@ module Bolt
|
|
218
274
|
@stream.puts total_msg
|
219
275
|
end
|
220
276
|
|
221
|
-
def
|
277
|
+
def format_table(results, padding_left = 0, padding_right = 3)
|
222
278
|
# lazy-load expensive gem code
|
223
279
|
require 'terminal-table'
|
224
280
|
|
225
|
-
|
281
|
+
Terminal::Table.new(
|
226
282
|
rows: results,
|
227
283
|
style: {
|
228
284
|
border_x: '',
|
@@ -238,94 +294,155 @@ module Bolt
|
|
238
294
|
|
239
295
|
def print_tasks(tasks, modulepath)
|
240
296
|
command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
297
|
+
|
298
|
+
tasks = tasks.map do |name, description|
|
299
|
+
description = truncate(description, 72)
|
300
|
+
[name, description]
|
301
|
+
end
|
302
|
+
|
303
|
+
@stream.puts colorize(:cyan, 'Tasks')
|
304
|
+
@stream.puts tasks.any? ? format_table(tasks, 2) : indent(2, 'No available tasks')
|
305
|
+
@stream.puts
|
306
|
+
|
307
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
308
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
309
|
+
@stream.puts
|
310
|
+
|
311
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
312
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
|
245
313
|
end
|
246
314
|
|
247
315
|
# @param [Hash] task A hash representing the task
|
248
316
|
def print_task_info(task)
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
317
|
+
params = (task.parameters || []).sort
|
318
|
+
|
319
|
+
info = +''
|
320
|
+
|
321
|
+
# Add task name and description
|
322
|
+
info << colorize(:cyan, "#{task.name}\n")
|
323
|
+
info << if task.description
|
324
|
+
indent(2, task.description.chomp)
|
254
325
|
else
|
255
|
-
|
326
|
+
indent(2, 'No description')
|
256
327
|
end
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
328
|
+
info << "\n\n"
|
329
|
+
|
330
|
+
# Build usage string
|
331
|
+
usage = +''
|
332
|
+
usage << if Bolt::Util.powershell?
|
333
|
+
"Invoke-BoltTask -Name #{task.name} -Targets <targets>"
|
334
|
+
else
|
335
|
+
"bolt task run #{task.name} --targets <targets>"
|
336
|
+
end
|
337
|
+
usage << (Bolt::Util.powershell? ? ' [-Noop]' : ' [--noop]') if task.supports_noop
|
338
|
+
params.each do |name, data|
|
339
|
+
usage << if data['type']&.start_with?('Optional')
|
340
|
+
" [#{name}=<value>]"
|
264
341
|
else
|
265
|
-
" #{
|
342
|
+
" #{name}=<value>"
|
266
343
|
end
|
267
344
|
end
|
268
345
|
|
269
|
-
|
270
|
-
|
346
|
+
# Add usage
|
347
|
+
info << colorize(:cyan, "Usage\n")
|
348
|
+
info << indent(2, wrap(usage))
|
349
|
+
info << "\n"
|
350
|
+
|
351
|
+
# Add parameters, if any
|
352
|
+
if params.any?
|
353
|
+
info << colorize(:cyan, "Parameters\n")
|
354
|
+
params.each do |name, data|
|
355
|
+
info << indent(2, "#{colorize(:yellow, name)} #{colorize(:dim, data['type'] || 'Any')}\n")
|
356
|
+
info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
|
357
|
+
info << indent(4, "Default: #{data['default'].inspect}\n") if data.key?('default')
|
358
|
+
info << "\n"
|
359
|
+
end
|
271
360
|
end
|
272
361
|
|
273
|
-
|
274
|
-
task_info << " - #{task.description}" if task.description
|
275
|
-
task_info << "\n\n"
|
276
|
-
task_info << "USAGE:\n#{usage}\n\n"
|
277
|
-
task_info << "PARAMETERS:\n#{pretty_params}\n" unless pretty_params.empty?
|
278
|
-
task_info << "MODULE:\n"
|
279
|
-
|
362
|
+
# Add module location
|
280
363
|
path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
364
|
+
info << colorize(:cyan, "Module\n")
|
365
|
+
info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
366
|
+
indent(2, 'built-in module')
|
367
|
+
else
|
368
|
+
indent(2, path)
|
369
|
+
end
|
370
|
+
|
371
|
+
@stream.puts info
|
287
372
|
end
|
288
373
|
|
289
374
|
# @param [Hash] plan A hash representing the plan
|
290
375
|
def print_plan_info(plan)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
376
|
+
params = plan['parameters'].sort
|
377
|
+
|
378
|
+
info = +''
|
379
|
+
|
380
|
+
# Add plan name and description
|
381
|
+
info << colorize(:cyan, "#{plan['name']}\n")
|
382
|
+
info << if plan['description']
|
383
|
+
indent(2, plan['description'].chomp)
|
296
384
|
else
|
297
|
-
|
385
|
+
indent(2, 'No description')
|
298
386
|
end
|
387
|
+
info << "\n\n"
|
388
|
+
|
389
|
+
# Build the usage string
|
390
|
+
usage = +''
|
391
|
+
usage << if Bolt::Util.powershell?
|
392
|
+
"Invoke-BoltPlan -Name #{plan['name']}"
|
393
|
+
else
|
394
|
+
"bolt plan run #{plan['name']}"
|
395
|
+
end
|
396
|
+
params.each do |name, data|
|
397
|
+
usage << (data.include?('default_value') ? " [#{name}=<value>]" : " #{name}=<value>")
|
398
|
+
end
|
299
399
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
400
|
+
# Add usage
|
401
|
+
info << colorize(:cyan, "Usage\n")
|
402
|
+
info << indent(2, wrap(usage))
|
403
|
+
info << "\n"
|
404
|
+
|
405
|
+
# Add parameters, if any
|
406
|
+
if params.any?
|
407
|
+
info << colorize(:cyan, "Parameters\n")
|
408
|
+
|
409
|
+
params.each do |name, data|
|
410
|
+
info << indent(2, "#{colorize(:yellow, name)} #{colorize(:dim, data['type'])}\n")
|
411
|
+
info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
|
412
|
+
info << indent(4, "Default: #{data['default_value']}\n") unless data['default_value'].nil?
|
413
|
+
info << "\n"
|
414
|
+
end
|
305
415
|
end
|
306
416
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
417
|
+
# Add module location
|
418
|
+
info << colorize(:cyan, "Module\n")
|
419
|
+
info << if plan['module'].start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
420
|
+
indent(2, 'built-in module')
|
421
|
+
else
|
422
|
+
indent(2, plan['module'])
|
423
|
+
end
|
313
424
|
|
314
|
-
|
315
|
-
plan_info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
316
|
-
"built-in module"
|
317
|
-
else
|
318
|
-
path
|
319
|
-
end
|
320
|
-
@stream.puts(plan_info)
|
425
|
+
@stream.puts info
|
321
426
|
end
|
322
427
|
|
323
428
|
def print_plans(plans, modulepath)
|
324
429
|
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
430
|
+
|
431
|
+
plans = plans.map do |name, description|
|
432
|
+
description = truncate(description, 72)
|
433
|
+
[name, description]
|
434
|
+
end
|
435
|
+
|
436
|
+
@stream.puts colorize(:cyan, 'Plans')
|
437
|
+
@stream.puts plans.any? ? format_table(plans, 2) : indent(2, 'No available plans')
|
438
|
+
@stream.puts
|
439
|
+
|
440
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
441
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
442
|
+
@stream.puts
|
443
|
+
|
444
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
445
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
|
329
446
|
end
|
330
447
|
|
331
448
|
def print_topics(topics)
|
@@ -359,7 +476,7 @@ module Bolt
|
|
359
476
|
[m[:name], version]
|
360
477
|
end
|
361
478
|
|
362
|
-
|
479
|
+
@stream.puts format_table(module_info, 2, 1)
|
363
480
|
end
|
364
481
|
|
365
482
|
@stream.write("\n")
|
@@ -373,29 +490,77 @@ module Bolt
|
|
373
490
|
targets += target_list[:inventory].map { |target| [target.name, nil] }
|
374
491
|
targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
|
375
492
|
|
376
|
-
|
377
|
-
print_table(targets, 0, 2)
|
378
|
-
@stream.puts
|
379
|
-
end
|
493
|
+
info = +''
|
380
494
|
|
381
|
-
|
382
|
-
|
383
|
-
|
495
|
+
# Add target list
|
496
|
+
info << colorize(:cyan, "Targets\n")
|
497
|
+
info << if targets.any?
|
498
|
+
format_table(targets, 2, 2).to_s
|
499
|
+
else
|
500
|
+
indent(2, 'No targets')
|
501
|
+
end
|
502
|
+
info << "\n\n"
|
503
|
+
|
504
|
+
@stream.puts info
|
505
|
+
|
506
|
+
print_inventory_summary(
|
507
|
+
target_list[:inventory].count,
|
508
|
+
target_list[:adhoc].count,
|
509
|
+
inventoryfile
|
510
|
+
)
|
511
|
+
end
|
512
|
+
|
513
|
+
def print_target_info(target_list, inventoryfile)
|
514
|
+
adhoc_targets = target_list[:adhoc].map(&:name).to_set
|
515
|
+
inventory_targets = target_list[:inventory].map(&:name).to_set
|
516
|
+
targets = target_list.values.flatten.sort_by(&:name)
|
517
|
+
|
518
|
+
info = +''
|
519
|
+
|
520
|
+
if targets.any?
|
521
|
+
adhoc = colorize(:yellow, " (Not found in inventory file)")
|
522
|
+
|
523
|
+
targets.each do |target|
|
524
|
+
info << colorize(:cyan, target.name)
|
525
|
+
info << adhoc if adhoc_targets.include?(target.name)
|
526
|
+
info << "\n"
|
527
|
+
info << indent(2, target.detail.to_yaml.lines.drop(1).join)
|
528
|
+
info << "\n"
|
529
|
+
end
|
384
530
|
else
|
385
|
-
|
531
|
+
info << colorize(:cyan, "Targets\n")
|
532
|
+
info << indent(2, "No targets\n\n")
|
386
533
|
end
|
387
534
|
|
388
|
-
@stream.puts
|
389
|
-
@stream.puts "#{targets.count} total, #{target_list[:inventory].count} from inventory, "\
|
390
|
-
"#{target_list[:adhoc].count} adhoc"
|
391
|
-
end
|
535
|
+
@stream.puts info
|
392
536
|
|
393
|
-
|
394
|
-
|
395
|
-
|
537
|
+
print_inventory_summary(
|
538
|
+
inventory_targets.count,
|
539
|
+
adhoc_targets.count,
|
540
|
+
inventoryfile
|
396
541
|
)
|
397
|
-
|
398
|
-
|
542
|
+
end
|
543
|
+
|
544
|
+
private def print_inventory_summary(inventory_count, adhoc_count, inventoryfile)
|
545
|
+
info = +''
|
546
|
+
|
547
|
+
# Add inventory file source
|
548
|
+
info << colorize(:cyan, "Inventory file\n")
|
549
|
+
info << if File.exist?(inventoryfile)
|
550
|
+
indent(2, "#{inventoryfile}\n")
|
551
|
+
else
|
552
|
+
indent(2, wrap("Tried to load inventory from #{inventoryfile}, but the file does not exist\n"))
|
553
|
+
end
|
554
|
+
info << "\n"
|
555
|
+
|
556
|
+
# Add target count summary
|
557
|
+
count = "#{inventory_count + adhoc_count} total, "\
|
558
|
+
"#{inventory_count} from inventory, "\
|
559
|
+
"#{adhoc_count} adhoc"
|
560
|
+
info << colorize(:cyan, "Target count\n")
|
561
|
+
info << indent(2, count)
|
562
|
+
|
563
|
+
@stream.puts info
|
399
564
|
end
|
400
565
|
|
401
566
|
def print_groups(groups)
|
@@ -417,6 +582,10 @@ module Bolt
|
|
417
582
|
@stream.puts("Plan completed successfully with no result")
|
418
583
|
when Bolt::ApplyFailure, Bolt::RunFailure
|
419
584
|
print_result_set(value.result_set)
|
585
|
+
when Bolt::ContainerResult
|
586
|
+
print_container_result(value)
|
587
|
+
when Bolt::ContainerFailure
|
588
|
+
print_container_result(value.result)
|
420
589
|
when Bolt::ResultSet
|
421
590
|
print_result_set(value)
|
422
591
|
else
|