bolt 3.4.0 → 3.7.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 +2 -2
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
- data/guides/guide.txt +17 -0
- data/guides/links.txt +13 -0
- data/guides/targets.txt +29 -0
- data/guides/transports.txt +23 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/bolt_option_parser.rb +329 -225
- data/lib/bolt/cli.rb +58 -29
- data/lib/bolt/config.rb +11 -7
- data/lib/bolt/config/options.rb +41 -9
- 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 +17 -13
- data/lib/bolt/inventory.rb +5 -4
- data/lib/bolt/inventory/inventory.rb +3 -2
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
- data/lib/bolt/outputter/human.rb +242 -76
- data/lib/bolt/outputter/json.rb +6 -4
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal/yaml_plan/step.rb +4 -2
- data/lib/bolt/plan_creator.rb +2 -2
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/puppetdb/client.rb +54 -0
- data/lib/bolt/result.rb +1 -4
- data/lib/bolt/shell/bash.rb +23 -10
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +23 -34
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +3 -6
- data/lib/bolt/util.rb +34 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -0
- data/lib/bolt_spec/plans/mock_executor.rb +91 -11
- data/modules/puppet_connect/plans/test_input_data.pp +22 -0
- metadata +13 -2
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
@@ -12,34 +12,37 @@ require 'bolt/config'
|
|
12
12
|
require 'bolt/result_set'
|
13
13
|
require 'bolt/puppetdb'
|
14
14
|
# Load transports
|
15
|
-
require 'bolt/transport/
|
16
|
-
require 'bolt/transport/winrm'
|
17
|
-
require 'bolt/transport/orch'
|
15
|
+
require 'bolt/transport/docker'
|
18
16
|
require 'bolt/transport/local'
|
19
17
|
require 'bolt/transport/lxd'
|
20
|
-
require 'bolt/transport/
|
18
|
+
require 'bolt/transport/orch'
|
19
|
+
require 'bolt/transport/podman'
|
21
20
|
require 'bolt/transport/remote'
|
21
|
+
require 'bolt/transport/ssh'
|
22
|
+
require 'bolt/transport/winrm'
|
22
23
|
require 'bolt/yarn'
|
23
24
|
|
24
25
|
module Bolt
|
25
26
|
TRANSPORTS = {
|
26
|
-
|
27
|
-
winrm: Bolt::Transport::WinRM,
|
28
|
-
pcp: Bolt::Transport::Orch,
|
27
|
+
docker: Bolt::Transport::Docker,
|
29
28
|
local: Bolt::Transport::Local,
|
30
29
|
lxd: Bolt::Transport::LXD,
|
31
|
-
|
32
|
-
|
30
|
+
pcp: Bolt::Transport::Orch,
|
31
|
+
podman: Bolt::Transport::Podman,
|
32
|
+
remote: Bolt::Transport::Remote,
|
33
|
+
ssh: Bolt::Transport::SSH,
|
34
|
+
winrm: Bolt::Transport::WinRM
|
33
35
|
}.freeze
|
34
36
|
|
35
37
|
class Executor
|
36
|
-
attr_reader :noop, :transports, :in_parallel
|
38
|
+
attr_reader :noop, :transports, :in_parallel, :future
|
37
39
|
attr_accessor :run_as
|
38
40
|
|
39
41
|
def initialize(concurrency = 1,
|
40
42
|
analytics = Bolt::Analytics::NoopClient.new,
|
41
43
|
noop = false,
|
42
|
-
modified_concurrency = false
|
44
|
+
modified_concurrency = false,
|
45
|
+
future = {})
|
43
46
|
# lazy-load expensive gem code
|
44
47
|
require 'concurrent'
|
45
48
|
@analytics = analytics
|
@@ -64,6 +67,7 @@ module Bolt
|
|
64
67
|
@noop = noop
|
65
68
|
@run_as = nil
|
66
69
|
@in_parallel = false
|
70
|
+
@future = future
|
67
71
|
@pool = if concurrency > 0
|
68
72
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
69
73
|
else
|
@@ -217,7 +221,7 @@ module Bolt
|
|
217
221
|
results
|
218
222
|
end
|
219
223
|
|
220
|
-
def report_transport(transport, count)
|
224
|
+
private def report_transport(transport, count)
|
221
225
|
name = transport.class.name.split('::').last.downcase
|
222
226
|
unless @reported_transports.include?(name)
|
223
227
|
@analytics&.event('Transport', 'initialize', label: name, value: count)
|
@@ -475,7 +479,7 @@ module Bolt
|
|
475
479
|
Time.now
|
476
480
|
end
|
477
481
|
|
478
|
-
def wait_until(timeout, retry_interval)
|
482
|
+
private def wait_until(timeout, retry_interval)
|
479
483
|
start = wait_now
|
480
484
|
until yield
|
481
485
|
raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
|
data/lib/bolt/inventory.rb
CHANGED
@@ -86,6 +86,7 @@ module Bolt
|
|
86
86
|
if config.default_inventoryfile.exist?
|
87
87
|
logger.debug("Loaded inventory from #{config.default_inventoryfile}")
|
88
88
|
else
|
89
|
+
source = nil
|
89
90
|
logger.debug("Tried to load inventory from #{config.default_inventoryfile}, but the file does not exist")
|
90
91
|
end
|
91
92
|
end
|
@@ -100,17 +101,17 @@ module Bolt
|
|
100
101
|
validator.warnings.each { |warning| Bolt::Logger.warn(warning[:id], warning[:msg]) }
|
101
102
|
end
|
102
103
|
|
103
|
-
inventory = create_version(data, config.transport, config.transports, plugins)
|
104
|
+
inventory = create_version(data, config.transport, config.transports, plugins, source)
|
104
105
|
inventory.validate
|
105
106
|
inventory
|
106
107
|
end
|
107
108
|
|
108
|
-
def self.create_version(data, transport, transports, plugins)
|
109
|
+
def self.create_version(data, transport, transports, plugins, source = nil)
|
109
110
|
version = (data || {}).delete('version') { 2 }
|
110
111
|
|
111
112
|
case version
|
112
113
|
when 2
|
113
|
-
Bolt::Inventory::Inventory.new(data, transport, transports, plugins)
|
114
|
+
Bolt::Inventory::Inventory.new(data, transport, transports, plugins, source)
|
114
115
|
else
|
115
116
|
raise ValidationError.new("Unsupported version #{version} specified in inventory", nil)
|
116
117
|
end
|
@@ -120,7 +121,7 @@ module Bolt
|
|
120
121
|
config = Bolt::Config.default
|
121
122
|
plugins = Bolt::Plugin.setup(config, nil)
|
122
123
|
|
123
|
-
create_version({}, config.transport, config.transports, plugins)
|
124
|
+
create_version({}, config.transport, config.transports, plugins, nil)
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
@@ -6,7 +6,7 @@ require 'bolt/inventory/target'
|
|
6
6
|
module Bolt
|
7
7
|
class Inventory
|
8
8
|
class Inventory
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :config, :plugins, :source, :targets, :transport
|
10
10
|
|
11
11
|
class WildcardError < Bolt::Error
|
12
12
|
def initialize(target)
|
@@ -15,7 +15,7 @@ module Bolt
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# TODO: Pass transport config instead of config object
|
18
|
-
def initialize(data, transport, transports, plugins)
|
18
|
+
def initialize(data, transport, transports, plugins, source = nil)
|
19
19
|
@logger = Bolt::Logger.logger(self)
|
20
20
|
@data = data || {}
|
21
21
|
@transport = transport
|
@@ -24,6 +24,7 @@ module Bolt
|
|
24
24
|
@groups = Group.new(@data, plugins, all_group: true)
|
25
25
|
@group_lookup = {}
|
26
26
|
@targets = {}
|
27
|
+
@source = source
|
27
28
|
|
28
29
|
@groups.resolve_string_targets(@groups.target_aliases, @groups.all_targets)
|
29
30
|
|
@@ -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
|
@@ -67,8 +67,7 @@ module Bolt
|
|
67
67
|
elsif git.start_with?('https://github.com')
|
68
68
|
git.split('https://github.com/').last.split('.git').first
|
69
69
|
else
|
70
|
-
raise Bolt::ValidationError,
|
71
|
-
"Invalid git source: #{git}. Only GitHub modules are supported."
|
70
|
+
raise Bolt::ValidationError, invalid_git_msg(git)
|
72
71
|
end
|
73
72
|
|
74
73
|
[git, repo]
|
@@ -89,6 +88,14 @@ module Bolt
|
|
89
88
|
}
|
90
89
|
end
|
91
90
|
|
91
|
+
# Returns an error message that the provided repo is not a git repo or
|
92
|
+
# is private.
|
93
|
+
#
|
94
|
+
private def invalid_git_msg(repo_name)
|
95
|
+
"#{repo_name} is not a public GitHub repository. See https://pup.pt/no-resolve "\
|
96
|
+
"for information on how to install this module."
|
97
|
+
end
|
98
|
+
|
92
99
|
# Returns a PuppetfileResolver::Model::GitModule object for resolving.
|
93
100
|
#
|
94
101
|
def to_resolver_module
|
@@ -157,10 +164,7 @@ module Bolt
|
|
157
164
|
|
158
165
|
raise Bolt::Error.new(message, 'bolt/github-api-rate-limit-error')
|
159
166
|
when Net::HTTPNotFound
|
160
|
-
raise Bolt::Error.new(
|
161
|
-
"#{git} is not a git repository.",
|
162
|
-
"bolt/missing-git-repository-error"
|
163
|
-
)
|
167
|
+
raise Bolt::Error.new(invalid_git_msg(git), "bolt/missing-git-repository-error")
|
164
168
|
else
|
165
169
|
raise Bolt::Error.new(
|
166
170
|
"Ref #{ref} at #{git} is not a commit, tag, or branch.",
|
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",
|
@@ -92,6 +93,10 @@ module Bolt
|
|
92
93
|
print_plan_start(event)
|
93
94
|
when :plan_finish
|
94
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?
|
95
100
|
when :start_spin
|
96
101
|
start_spin
|
97
102
|
when :stop_spin
|
@@ -112,6 +117,34 @@ module Bolt
|
|
112
117
|
@stream.puts(colorize(:green, "Started on #{target.safe_name}..."))
|
113
118
|
end
|
114
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
|
+
|
115
148
|
def print_result(result)
|
116
149
|
if result.success?
|
117
150
|
@stream.puts(colorize(:green, "Finished on #{result.target.safe_name}:"))
|
@@ -180,6 +213,25 @@ module Bolt
|
|
180
213
|
@stream.puts(colorize(:green, message))
|
181
214
|
end
|
182
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
|
+
|
183
235
|
def print_plan_start(event)
|
184
236
|
@plan_depth += 1
|
185
237
|
# We use this event to both mark the start of a plan _and_ to enable
|
@@ -241,7 +293,7 @@ module Bolt
|
|
241
293
|
end
|
242
294
|
|
243
295
|
def print_tasks(tasks, modulepath)
|
244
|
-
command = Bolt::Util.powershell? ? 'Get-BoltTask -
|
296
|
+
command = Bolt::Util.powershell? ? 'Get-BoltTask -Name <TASK NAME>' : 'bolt task show <TASK NAME>'
|
245
297
|
|
246
298
|
tasks = tasks.map do |name, description|
|
247
299
|
description = truncate(description, 72)
|
@@ -262,78 +314,115 @@ module Bolt
|
|
262
314
|
|
263
315
|
# @param [Hash] task A hash representing the task
|
264
316
|
def print_task_info(task)
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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)
|
270
325
|
else
|
271
|
-
|
326
|
+
indent(2, 'No description')
|
272
327
|
end
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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>]"
|
280
341
|
else
|
281
|
-
" #{
|
342
|
+
" #{name}=<value>"
|
282
343
|
end
|
283
344
|
end
|
284
345
|
|
285
|
-
|
286
|
-
|
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
|
287
360
|
end
|
288
361
|
|
289
|
-
|
290
|
-
task_info << " - #{task.description}" if task.description
|
291
|
-
task_info << "\n\n"
|
292
|
-
task_info << "USAGE:\n#{usage}\n\n"
|
293
|
-
task_info << "PARAMETERS:\n#{pretty_params}\n" unless pretty_params.empty?
|
294
|
-
task_info << "MODULE:\n"
|
295
|
-
|
362
|
+
# Add module location
|
296
363
|
path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
303
372
|
end
|
304
373
|
|
305
374
|
# @param [Hash] plan A hash representing the plan
|
306
375
|
def print_plan_info(plan)
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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)
|
312
384
|
else
|
313
|
-
|
385
|
+
indent(2, 'No description')
|
314
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
|
399
|
+
|
400
|
+
# Add usage
|
401
|
+
info << colorize(:cyan, "Usage\n")
|
402
|
+
info << indent(2, wrap(usage))
|
403
|
+
info << "\n"
|
315
404
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
321
415
|
end
|
322
416
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
329
424
|
|
330
|
-
|
331
|
-
plan_info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
332
|
-
"built-in module"
|
333
|
-
else
|
334
|
-
path
|
335
|
-
end
|
336
|
-
@stream.puts(plan_info)
|
425
|
+
@stream.puts info
|
337
426
|
end
|
338
427
|
|
339
428
|
def print_plans(plans, modulepath)
|
@@ -394,42 +483,115 @@ module Bolt
|
|
394
483
|
end
|
395
484
|
end
|
396
485
|
|
397
|
-
def print_targets(target_list,
|
486
|
+
def print_targets(target_list, inventory_source, default_inventory, target_flag)
|
398
487
|
adhoc = colorize(:yellow, "(Not found in inventory file)")
|
399
488
|
|
400
489
|
targets = []
|
401
490
|
targets += target_list[:inventory].map { |target| [target.name, nil] }
|
402
491
|
targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
|
403
492
|
|
404
|
-
|
405
|
-
@stream.puts format_table(targets, 0, 2)
|
406
|
-
@stream.puts
|
407
|
-
end
|
493
|
+
info = +''
|
408
494
|
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
+
info << format_inventory_source(inventory_source, default_inventory)
|
505
|
+
info << format_target_summary(target_list[:inventory].count, target_list[:adhoc].count, target_flag, false)
|
506
|
+
|
507
|
+
@stream.puts info
|
508
|
+
end
|
509
|
+
|
510
|
+
def print_target_info(target_list, inventory_source, default_inventory, target_flag)
|
511
|
+
adhoc_targets = target_list[:adhoc].map(&:name).to_set
|
512
|
+
inventory_targets = target_list[:inventory].map(&:name).to_set
|
513
|
+
targets = target_list.values.flatten.sort_by(&:name)
|
514
|
+
|
515
|
+
info = +''
|
516
|
+
|
517
|
+
if targets.any?
|
518
|
+
adhoc = colorize(:yellow, " (Not found in inventory file)")
|
519
|
+
|
520
|
+
targets.each do |target|
|
521
|
+
info << colorize(:cyan, target.name)
|
522
|
+
info << adhoc if adhoc_targets.include?(target.name)
|
523
|
+
info << "\n"
|
524
|
+
info << indent(2, target.detail.to_yaml.lines.drop(1).join)
|
525
|
+
info << "\n"
|
526
|
+
end
|
412
527
|
else
|
413
|
-
|
528
|
+
info << colorize(:cyan, "Targets\n")
|
529
|
+
info << indent(2, "No targets\n\n")
|
414
530
|
end
|
415
531
|
|
416
|
-
|
417
|
-
|
418
|
-
|
532
|
+
info << format_inventory_source(inventory_source, default_inventory)
|
533
|
+
info << format_target_summary(inventory_targets.count, adhoc_targets.count, target_flag, true)
|
534
|
+
|
535
|
+
@stream.puts info
|
419
536
|
end
|
420
537
|
|
421
|
-
def
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
538
|
+
private def format_inventory_source(inventory_source, default_inventory)
|
539
|
+
info = +''
|
540
|
+
|
541
|
+
# Add inventory file source
|
542
|
+
info << colorize(:cyan, "Inventory source\n")
|
543
|
+
info << if inventory_source
|
544
|
+
indent(2, "#{inventory_source}\n")
|
545
|
+
else
|
546
|
+
indent(2, wrap("Tried to load inventory from #{default_inventory}, but the file does not exist\n"))
|
547
|
+
end
|
548
|
+
info << "\n"
|
549
|
+
end
|
550
|
+
|
551
|
+
private def format_target_summary(inventory_count, adhoc_count, target_flag, detail_flag)
|
552
|
+
info = +''
|
553
|
+
|
554
|
+
# Add target count summary
|
555
|
+
count = "#{inventory_count + adhoc_count} total, "\
|
556
|
+
"#{inventory_count} from inventory, "\
|
557
|
+
"#{adhoc_count} adhoc"
|
558
|
+
info << colorize(:cyan, "Target count\n")
|
559
|
+
info << indent(2, count)
|
560
|
+
|
561
|
+
# Add filtering information
|
562
|
+
unless target_flag && detail_flag
|
563
|
+
info << colorize(:cyan, "\n\nAdditional information\n")
|
564
|
+
|
565
|
+
unless target_flag
|
566
|
+
opt = Bolt::Util.windows? ? "'-Targets', '-Query', or '-Rerun'" : "'--targets', '--query', or '--rerun'"
|
567
|
+
info << indent(2, "Use the #{opt} option to view specific targets\n")
|
568
|
+
end
|
569
|
+
|
570
|
+
unless detail_flag
|
571
|
+
opt = Bolt::Util.windows? ? '-Detail' : '--detail'
|
572
|
+
info << indent(2, "Use the '#{opt}' option to view target configuration and data")
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
info
|
427
577
|
end
|
428
578
|
|
429
|
-
def print_groups(groups)
|
430
|
-
|
431
|
-
|
432
|
-
|
579
|
+
def print_groups(groups, inventory_source, default_inventory)
|
580
|
+
info = +''
|
581
|
+
|
582
|
+
# Add group list
|
583
|
+
info << colorize(:cyan, "Groups\n")
|
584
|
+
info << indent(2, groups.join("\n"))
|
585
|
+
info << "\n\n"
|
586
|
+
|
587
|
+
# Add inventory file source
|
588
|
+
info << format_inventory_source(inventory_source, default_inventory)
|
589
|
+
|
590
|
+
# Add group count summary
|
591
|
+
info << colorize(:cyan, "Group count\n")
|
592
|
+
info << indent(2, "#{groups.count} total")
|
593
|
+
|
594
|
+
@stream.puts info
|
433
595
|
end
|
434
596
|
|
435
597
|
# @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
|
@@ -445,6 +607,10 @@ module Bolt
|
|
445
607
|
@stream.puts("Plan completed successfully with no result")
|
446
608
|
when Bolt::ApplyFailure, Bolt::RunFailure
|
447
609
|
print_result_set(value.result_set)
|
610
|
+
when Bolt::ContainerResult
|
611
|
+
print_container_result(value)
|
612
|
+
when Bolt::ContainerFailure
|
613
|
+
print_container_result(value.result)
|
448
614
|
when Bolt::ResultSet
|
449
615
|
print_result_set(value)
|
450
616
|
else
|