bolt 3.13.0 → 3.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +5 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +13 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +47 -7
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +4 -2
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +4 -2
- data/guides/{debugging.txt → debugging.yaml} +5 -6
- data/guides/{inventory.txt → inventory.yaml} +6 -7
- data/guides/{links.txt → links.yaml} +3 -4
- data/guides/{logging.txt → logging.yaml} +5 -6
- data/guides/{module.txt → module.yaml} +5 -6
- data/guides/{modulepath.txt → modulepath.yaml} +5 -6
- data/guides/{project.txt → project.yaml} +6 -7
- data/guides/{targets.txt → targets.yaml} +5 -6
- data/guides/{transports.txt → transports.yaml} +6 -7
- data/lib/bolt/analytics.rb +3 -20
- data/lib/bolt/application.rb +620 -0
- data/lib/bolt/bolt_option_parser.rb +17 -5
- data/lib/bolt/cli.rb +592 -772
- data/lib/bolt/config/transport/options.rb +12 -0
- data/lib/bolt/config/transport/ssh.rb +7 -0
- data/lib/bolt/executor.rb +12 -4
- data/lib/bolt/fiber_executor.rb +63 -14
- data/lib/bolt/module_installer/puppetfile.rb +24 -10
- data/lib/bolt/outputter/human.rb +199 -43
- data/lib/bolt/outputter/json.rb +66 -43
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/pal.rb +67 -14
- data/lib/bolt/pal/yaml_plan/step.rb +2 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +0 -8
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
- data/lib/bolt/plan_creator.rb +2 -20
- data/lib/bolt/plan_future.rb +23 -3
- data/lib/bolt/plan_result.rb +1 -1
- data/lib/bolt/plugin/task.rb +1 -1
- data/lib/bolt/project.rb +0 -7
- data/lib/bolt/result_set.rb +2 -1
- data/lib/bolt/transport/local/connection.rb +17 -1
- data/lib/bolt/transport/orch/connection.rb +13 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -0
- data/lib/bolt_server/schemas/action-apply.json +32 -0
- data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
- data/lib/bolt_server/schemas/partials/target-ssh.json +4 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +4 -0
- data/lib/bolt_server/transport_app.rb +180 -60
- data/lib/bolt_spec/plans/mock_executor.rb +16 -6
- metadata +23 -15
- data/guides/guide.txt +0 -17
- data/lib/bolt/secret.rb +0 -37
@@ -16,6 +16,18 @@ module Bolt
|
|
16
16
|
_default: false,
|
17
17
|
_example: true
|
18
18
|
},
|
19
|
+
"batch-mode" => {
|
20
|
+
type: [TrueClass, FalseClass],
|
21
|
+
description: "Whether to disable password querying. When set to `false`, SSH will fall back to "\
|
22
|
+
"prompting for a password if key authentication fails. This might cause Bolt to hang. "\
|
23
|
+
"To prevent Bolt from hanging, you can configure `ssh-command` to use an SSH utility "\
|
24
|
+
"such as sshpass that supports providing a password non-interactively. For more "\
|
25
|
+
"information, see [Providing a password non-interactively using "\
|
26
|
+
"`native-ssh`](troubleshooting.md#providing-a-password-non-interactively-using-native-ssh).",
|
27
|
+
_plugin: true,
|
28
|
+
_default: true,
|
29
|
+
_example: false
|
30
|
+
},
|
19
31
|
"bundled-ruby" => {
|
20
32
|
description: "Whether to use the Ruby bundled with Bolt packages for local targets.",
|
21
33
|
type: [TrueClass, FalseClass],
|
@@ -34,6 +34,7 @@ module Bolt
|
|
34
34
|
|
35
35
|
# Options available when using the native ssh transport
|
36
36
|
NATIVE_OPTIONS = %w[
|
37
|
+
batch-mode
|
37
38
|
cleanup
|
38
39
|
copy-command
|
39
40
|
host
|
@@ -49,6 +50,7 @@ module Bolt
|
|
49
50
|
].concat(RUN_AS_OPTIONS).sort.freeze
|
50
51
|
|
51
52
|
DEFAULTS = {
|
53
|
+
"batch-mode" => true,
|
52
54
|
"cleanup" => true,
|
53
55
|
"connect-timeout" => 10,
|
54
56
|
"disconnect-timeout" => 5,
|
@@ -124,6 +126,11 @@ module Bolt
|
|
124
126
|
msg = 'Cannot use native SSH transport with load-config set to false'
|
125
127
|
raise Bolt::ValidationError, msg
|
126
128
|
end
|
129
|
+
|
130
|
+
if !@config['batch-mode'] && !@config['ssh-command']
|
131
|
+
raise Bolt::ValidationError,
|
132
|
+
'Must set ssh-command when batch-mode is set to false'
|
133
|
+
end
|
127
134
|
end
|
128
135
|
end
|
129
136
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -381,8 +381,16 @@ module Bolt
|
|
381
381
|
# overloaded while also minimizing the Puppet lookups needed from plan
|
382
382
|
# functions
|
383
383
|
#
|
384
|
-
def create_future(scope: nil, name: nil, &block)
|
385
|
-
@fiber_executor.create_future(scope: scope, name: name, &block)
|
384
|
+
def create_future(plan_id:, scope: nil, name: nil, &block)
|
385
|
+
@fiber_executor.create_future(scope: scope, name: name, plan_id: plan_id, &block)
|
386
|
+
end
|
387
|
+
|
388
|
+
def get_current_future(fiber:)
|
389
|
+
@fiber_executor.get_current_future(fiber: fiber)
|
390
|
+
end
|
391
|
+
|
392
|
+
def get_current_plan_id(fiber:)
|
393
|
+
@fiber_executor.get_current_plan_id(fiber: fiber)
|
386
394
|
end
|
387
395
|
|
388
396
|
def plan_complete?
|
@@ -401,8 +409,8 @@ module Bolt
|
|
401
409
|
@fiber_executor.wait(futures, **opts)
|
402
410
|
end
|
403
411
|
|
404
|
-
def
|
405
|
-
@fiber_executor.
|
412
|
+
def get_futures_for_plan(plan_id:)
|
413
|
+
@fiber_executor.get_futures_for_plan(plan_id: plan_id)
|
406
414
|
end
|
407
415
|
|
408
416
|
# Execute a plan function concurrently. This function accepts the executor
|
data/lib/bolt/fiber_executor.rb
CHANGED
@@ -5,18 +5,19 @@ require 'bolt/plan_future'
|
|
5
5
|
|
6
6
|
module Bolt
|
7
7
|
class FiberExecutor
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :active_futures, :finished_futures
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@logger = Bolt::Logger.logger(self)
|
12
12
|
@id = 0
|
13
|
-
@
|
13
|
+
@active_futures = []
|
14
|
+
@finished_futures = []
|
14
15
|
end
|
15
16
|
|
16
17
|
# Whether there is more than one fiber running in parallel.
|
17
18
|
#
|
18
19
|
def in_parallel?
|
19
|
-
|
20
|
+
active_futures.length > 1
|
20
21
|
end
|
21
22
|
|
22
23
|
# Creates a new Puppet scope from the current Plan scope so that variables
|
@@ -24,7 +25,7 @@ module Bolt
|
|
24
25
|
# Then creates a new Fiber to execute the block, wraps the Fiber in a
|
25
26
|
# Bolt::PlanFuture, and returns the Bolt::PlanFuture.
|
26
27
|
#
|
27
|
-
def create_future(scope: nil, name: nil)
|
28
|
+
def create_future(plan_id:, scope: nil, name: nil)
|
28
29
|
newscope = nil
|
29
30
|
if scope
|
30
31
|
# Save existing variables to the new scope before starting the future
|
@@ -46,13 +47,16 @@ module Bolt
|
|
46
47
|
end
|
47
48
|
|
48
49
|
# PlanFutures are assigned an ID, which is just a global incrementing
|
49
|
-
# integer. The main plan should always have ID 0.
|
50
|
+
# integer. The main plan should always have ID 0. They also have a
|
51
|
+
# plan_id, which identifies which plan spawned them. This is used for
|
52
|
+
# tracking which Futures to wait on when `wait()` is called without
|
53
|
+
# arguments.
|
50
54
|
@id += 1
|
51
|
-
future = Bolt::PlanFuture.new(future, @id, name)
|
55
|
+
future = Bolt::PlanFuture.new(future, @id, name: name, plan_id: plan_id, scope: newscope)
|
52
56
|
@logger.trace("Created future #{future.name}")
|
53
57
|
|
54
58
|
# Register the PlanFuture with the FiberExecutor to be executed
|
55
|
-
|
59
|
+
active_futures << future
|
56
60
|
future
|
57
61
|
end
|
58
62
|
|
@@ -63,12 +67,16 @@ module Bolt
|
|
63
67
|
# the PlanFuture and remove the PlanFuture from the FiberExecutor.
|
64
68
|
#
|
65
69
|
def round_robin
|
66
|
-
|
67
|
-
# If the Fiber is still running and can be resumed, then resume it
|
70
|
+
active_futures.each do |future|
|
71
|
+
# If the Fiber is still running and can be resumed, then resume it.
|
72
|
+
# Override Puppet's global_scope to prevent ephemerals in other scopes
|
73
|
+
# from being popped off in the wrong order due to race conditions.
|
74
|
+
# This primarily happens when running executor functions from custom
|
75
|
+
# Puppet language functions, but may happen elsewhere.
|
68
76
|
@logger.trace("Checking future '#{future.name}'")
|
69
77
|
if future.alive?
|
70
78
|
@logger.trace("Resuming future '#{future.name}'")
|
71
|
-
future.resume
|
79
|
+
Puppet.override(global_scope: future.scope) { future.resume }
|
72
80
|
end
|
73
81
|
|
74
82
|
# Once we've restarted the Fiber, check to see if it's finished again
|
@@ -78,19 +86,19 @@ module Bolt
|
|
78
86
|
|
79
87
|
# If the future errored and the main plan has already exited, log the
|
80
88
|
# error at warn level.
|
81
|
-
unless
|
89
|
+
unless active_futures.map(&:id).include?(0) || future.state == "done"
|
82
90
|
Bolt::Logger.warn('errored_futures', "Error in future '#{future.name}': #{future.value}")
|
83
91
|
end
|
84
92
|
|
85
93
|
# Remove the PlanFuture from the FiberExecutor.
|
86
|
-
|
94
|
+
finished_futures.push(active_futures.delete(future))
|
87
95
|
end
|
88
96
|
|
89
97
|
# If the Fiber immediately returned or if the Fiber is blocking on a
|
90
98
|
# `wait` call, Bolt should pause for long enough that something can
|
91
99
|
# execute before checking again. This mitigates CPU
|
92
100
|
# thrashing.
|
93
|
-
return unless
|
101
|
+
return unless active_futures.all? { |f| %i[returned_immediately unfinished].include?(f.value) }
|
94
102
|
@logger.trace("Nothing can be resumed. Rechecking in 0.5 seconds.")
|
95
103
|
|
96
104
|
sleep(0.5)
|
@@ -101,12 +109,53 @@ module Bolt
|
|
101
109
|
# Bolt can exit.
|
102
110
|
#
|
103
111
|
def plan_complete?
|
104
|
-
|
112
|
+
active_futures.empty?
|
113
|
+
end
|
114
|
+
|
115
|
+
def all_futures
|
116
|
+
active_futures + finished_futures
|
117
|
+
end
|
118
|
+
|
119
|
+
# Get the PlanFuture object that is currently executing
|
120
|
+
#
|
121
|
+
def get_current_future(fiber:)
|
122
|
+
all_futures.select { |f| f.fiber == fiber }.first
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get the plan invocation ID for the PlanFuture that is currently executing
|
126
|
+
#
|
127
|
+
def get_current_plan_id(fiber:)
|
128
|
+
get_current_future(fiber: fiber).current_plan
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get the Future objects associated with a particular plan invocation.
|
132
|
+
#
|
133
|
+
def get_futures_for_plan(plan_id:)
|
134
|
+
all_futures.select { |f| f.original_plan == plan_id }
|
105
135
|
end
|
106
136
|
|
107
137
|
# Block until the provided PlanFuture objects have finished, or the timeout is reached.
|
108
138
|
#
|
109
139
|
def wait(futures, timeout: nil, catch_errors: false, **_kwargs)
|
140
|
+
if futures.nil?
|
141
|
+
results = []
|
142
|
+
plan_id = get_current_plan_id(fiber: Fiber.current)
|
143
|
+
# Recollect the futures for this plan until all of the futures have
|
144
|
+
# finished. This ensures that we include futures created inside of
|
145
|
+
# futures being waited on.
|
146
|
+
until (futures = get_futures_for_plan(plan_id: plan_id)).map(&:alive?).none?
|
147
|
+
if futures.map(&:fiber).include?(Fiber.current)
|
148
|
+
msg = "The wait() function cannot be called with no arguments inside a "\
|
149
|
+
"background block in the same plan."
|
150
|
+
raise Bolt::Error.new(msg, 'bolt/infinite-wait')
|
151
|
+
end
|
152
|
+
# Wait for all the futures we know about so far before recollecting
|
153
|
+
# Futures for the plan and waiting again
|
154
|
+
results = wait(futures, timeout: timeout, catch_errors: catch_errors)
|
155
|
+
end
|
156
|
+
return results
|
157
|
+
end
|
158
|
+
|
110
159
|
if timeout.nil?
|
111
160
|
Fiber.yield(:unfinished) until futures.map(&:alive?).none?
|
112
161
|
else
|
@@ -97,20 +97,34 @@ module Bolt
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
|
101
|
-
|
100
|
+
versionless_mods = @modules.select { |mod| mod.is_a?(ForgeModule) && mod.version.nil? }
|
102
101
|
command = Bolt::Util.windows? ? 'Install-BoltModule -Force' : 'bolt module install --force'
|
103
102
|
|
104
|
-
|
105
|
-
|
103
|
+
if unsatisfied_specs.any?
|
104
|
+
message = <<~MESSAGE.chomp
|
105
|
+
Puppetfile does not include modules that satisfy the following specifications:
|
106
|
+
|
107
|
+
#{unsatisfied_specs.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
|
108
|
+
|
109
|
+
This Puppetfile might not be managed by Bolt. To forcibly overwrite the
|
110
|
+
Puppetfile, run '#{command}'.
|
111
|
+
MESSAGE
|
106
112
|
|
107
|
-
|
108
|
-
|
109
|
-
This Puppetfile might not be managed by Bolt. To forcibly overwrite the
|
110
|
-
Puppetfile, run '#{command}'.
|
111
|
-
MESSAGE
|
113
|
+
raise Bolt::Error.new(message, 'bolt/missing-module-specs')
|
114
|
+
end
|
112
115
|
|
113
|
-
|
116
|
+
if versionless_mods.any?
|
117
|
+
message = <<~MESSAGE.chomp
|
118
|
+
Puppetfile includes Forge modules without a version requirement:
|
119
|
+
|
120
|
+
#{versionless_mods.map(&:to_spec).join.chomp}
|
121
|
+
|
122
|
+
This Puppetfile might not be managed by Bolt. To forcibly overwrite the
|
123
|
+
Puppetfile, run '#{command}'.
|
124
|
+
MESSAGE
|
125
|
+
|
126
|
+
raise Bolt::Error.new(message, 'bolt/missing-module-version-specs')
|
127
|
+
end
|
114
128
|
end
|
115
129
|
end
|
116
130
|
end
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -310,7 +310,12 @@ module Bolt
|
|
310
310
|
)
|
311
311
|
end
|
312
312
|
|
313
|
-
|
313
|
+
# List available tasks.
|
314
|
+
#
|
315
|
+
# @param tasks [Array] A list of task names and descriptions.
|
316
|
+
# @param modulepath [Array] The modulepath.
|
317
|
+
#
|
318
|
+
def print_tasks(tasks:, modulepath:)
|
314
319
|
command = Bolt::Util.powershell? ? 'Get-BoltTask -Name <TASK NAME>' : 'bolt task show <TASK NAME>'
|
315
320
|
|
316
321
|
tasks = tasks.map do |name, description|
|
@@ -330,8 +335,11 @@ module Bolt
|
|
330
335
|
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
|
331
336
|
end
|
332
337
|
|
333
|
-
#
|
334
|
-
|
338
|
+
# Print information about a task.
|
339
|
+
#
|
340
|
+
# @param task [Bolt::Task] The task information.
|
341
|
+
#
|
342
|
+
def print_task_info(task:)
|
335
343
|
params = (task.parameters || []).sort
|
336
344
|
|
337
345
|
info = +''
|
@@ -341,7 +349,7 @@ module Bolt
|
|
341
349
|
info << if task.description
|
342
350
|
indent(2, task.description.chomp)
|
343
351
|
else
|
344
|
-
indent(2, 'No description')
|
352
|
+
indent(2, 'No description available.')
|
345
353
|
end
|
346
354
|
info << "\n\n"
|
347
355
|
|
@@ -400,7 +408,7 @@ module Bolt
|
|
400
408
|
info << if plan['description']
|
401
409
|
indent(2, plan['description'].chomp)
|
402
410
|
else
|
403
|
-
indent(2, 'No description')
|
411
|
+
indent(2, 'No description available.')
|
404
412
|
end
|
405
413
|
info << "\n\n"
|
406
414
|
|
@@ -443,7 +451,7 @@ module Bolt
|
|
443
451
|
@stream.puts info
|
444
452
|
end
|
445
453
|
|
446
|
-
def print_plans(plans
|
454
|
+
def print_plans(plans:, modulepath:)
|
447
455
|
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
448
456
|
|
449
457
|
plans = plans.map do |name, description|
|
@@ -463,7 +471,11 @@ module Bolt
|
|
463
471
|
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
|
464
472
|
end
|
465
473
|
|
466
|
-
|
474
|
+
# Print available guide topics.
|
475
|
+
#
|
476
|
+
# @param topics [Array] The available topics.
|
477
|
+
#
|
478
|
+
def print_topics(topics:, **_kwargs)
|
467
479
|
info = +"#{colorize(:cyan, 'Topics')}\n"
|
468
480
|
info << indent(2, topics.join("\n"))
|
469
481
|
info << "\n\n#{colorize(:cyan, 'Additional information')}\n"
|
@@ -471,8 +483,20 @@ module Bolt
|
|
471
483
|
@stream.puts info
|
472
484
|
end
|
473
485
|
|
474
|
-
|
475
|
-
|
486
|
+
# Print the guide for the specified topic.
|
487
|
+
#
|
488
|
+
# @param guide [String] The guide.
|
489
|
+
#
|
490
|
+
def print_guide(topic:, guide:, documentation: nil, **_kwargs)
|
491
|
+
info = +"#{colorize(:cyan, topic)}\n"
|
492
|
+
info << indent(2, guide)
|
493
|
+
|
494
|
+
if documentation
|
495
|
+
info << "\n#{colorize(:cyan, 'Documentation')}\n"
|
496
|
+
info << indent(2, documentation.join("\n"))
|
497
|
+
end
|
498
|
+
|
499
|
+
@stream.puts info
|
476
500
|
end
|
477
501
|
|
478
502
|
def print_plan_lookup(value)
|
@@ -480,15 +504,19 @@ module Bolt
|
|
480
504
|
end
|
481
505
|
|
482
506
|
def print_module_list(module_list)
|
507
|
+
info = +''
|
508
|
+
|
483
509
|
module_list.each do |path, modules|
|
484
|
-
if (mod = modules.find { |m| m[:internal_module_group] })
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
510
|
+
info << if (mod = modules.find { |m| m[:internal_module_group] })
|
511
|
+
colorize(:cyan, mod[:internal_module_group])
|
512
|
+
else
|
513
|
+
colorize(:cyan, path)
|
514
|
+
end
|
515
|
+
|
516
|
+
info << "\n"
|
489
517
|
|
490
518
|
if modules.empty?
|
491
|
-
|
519
|
+
info << '(no modules installed)'
|
492
520
|
else
|
493
521
|
module_info = modules.map do |m|
|
494
522
|
version = if m[:version].nil?
|
@@ -500,24 +528,100 @@ module Bolt
|
|
500
528
|
[m[:name], version]
|
501
529
|
end
|
502
530
|
|
503
|
-
|
531
|
+
info << format_table(module_info, 2, 1).to_s
|
504
532
|
end
|
505
533
|
|
506
|
-
|
534
|
+
info << "\n\n"
|
507
535
|
end
|
536
|
+
|
537
|
+
command = Bolt::Util.powershell? ? 'Get-BoltModule -Name <MODULE>' : 'bolt module show <MODULE>'
|
538
|
+
info << colorize(:cyan, "Additional information\n")
|
539
|
+
info << indent(2, "Use '#{command}' to view details for a specific module.")
|
540
|
+
|
541
|
+
@stream.puts info
|
508
542
|
end
|
509
543
|
|
510
|
-
|
544
|
+
# Prints detailed module information.
|
545
|
+
#
|
546
|
+
# @param name [String] The module's short name.
|
547
|
+
# @param metadata [Hash] The module's metadata.
|
548
|
+
# @param path [String] The path to the module.
|
549
|
+
# @param plans [Array] The module's plans.
|
550
|
+
# @param tasks [Array] The module's tasks.
|
551
|
+
#
|
552
|
+
def print_module_info(name:, metadata:, path:, plans:, tasks:, **_kwargs)
|
553
|
+
info = +''
|
554
|
+
|
555
|
+
info << colorize(:cyan, name)
|
556
|
+
|
557
|
+
info << colorize(:dim, " [#{metadata['version']}]") if metadata['version']
|
558
|
+
info << "\n"
|
559
|
+
|
560
|
+
info << if metadata['summary']
|
561
|
+
indent(2, wrap(metadata['summary'].strip, 76))
|
562
|
+
else
|
563
|
+
indent(2, "No description available.\n")
|
564
|
+
end
|
565
|
+
info << "\n"
|
566
|
+
|
567
|
+
if tasks.any?
|
568
|
+
length = tasks.map(&:first).map(&:length).max
|
569
|
+
data = tasks.map { |task, desc| [task, truncate(desc, 76 - length)] }
|
570
|
+
info << colorize(:cyan, "Tasks\n")
|
571
|
+
info << format_table(data, 2).to_s
|
572
|
+
info << "\n\n"
|
573
|
+
end
|
574
|
+
|
575
|
+
if plans.any?
|
576
|
+
length = plans.map(&:first).map(&:length).max
|
577
|
+
data = plans.map { |plan, desc| [plan, truncate(desc, 76 - length)] }
|
578
|
+
info << colorize(:cyan, "Plans\n")
|
579
|
+
info << format_table(data, 2).to_s
|
580
|
+
info << "\n\n"
|
581
|
+
end
|
582
|
+
|
583
|
+
if metadata['operatingsystem_support']&.any?
|
584
|
+
supported = metadata['operatingsystem_support'].map do |os|
|
585
|
+
[os['operatingsystem'], os['operatingsystemrelease']&.join(', ')]
|
586
|
+
end
|
587
|
+
|
588
|
+
info << colorize(:cyan, "Operating system support\n")
|
589
|
+
info << format_table(supported, 2).to_s
|
590
|
+
info << "\n\n"
|
591
|
+
end
|
592
|
+
|
593
|
+
if metadata['dependencies']&.any?
|
594
|
+
dependencies = metadata['dependencies'].map do |dep|
|
595
|
+
[dep['name'], dep['version_requirement']]
|
596
|
+
end
|
597
|
+
|
598
|
+
info << colorize(:cyan, "Dependencies\n")
|
599
|
+
info << format_table(dependencies, 2).to_s
|
600
|
+
info << "\n\n"
|
601
|
+
end
|
602
|
+
|
603
|
+
info << colorize(:cyan, "Path\n")
|
604
|
+
info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH) ||
|
605
|
+
path.start_with?(Bolt::Config::Modulepath::BOLTLIB_PATH)
|
606
|
+
indent(2, 'built-in module')
|
607
|
+
else
|
608
|
+
indent(2, path)
|
609
|
+
end
|
610
|
+
|
611
|
+
@stream.puts info
|
612
|
+
end
|
613
|
+
|
614
|
+
def print_plugin_list(plugins:, modulepath:)
|
511
615
|
info = +''
|
512
|
-
length =
|
616
|
+
length = plugins.values.map(&:keys).flatten.map(&:length).max + 4
|
513
617
|
|
514
|
-
|
515
|
-
next if
|
618
|
+
plugins.each do |hook, plugin|
|
619
|
+
next if plugin.empty?
|
516
620
|
next if hook == :validate_resolve_reference
|
517
621
|
|
518
622
|
info << colorize(:cyan, "#{hook}\n")
|
519
623
|
|
520
|
-
|
624
|
+
plugin.each do |name, description|
|
521
625
|
info << indent(2, name.ljust(length))
|
522
626
|
info << truncate(description, 80 - length) if description
|
523
627
|
info << "\n"
|
@@ -535,12 +639,37 @@ module Bolt
|
|
535
639
|
@stream.puts info.chomp
|
536
640
|
end
|
537
641
|
|
538
|
-
def
|
539
|
-
|
642
|
+
def print_new_plan(name:, path:)
|
643
|
+
if Bolt::Util.powershell?
|
644
|
+
show_command = 'Get-BoltPlan -Name '
|
645
|
+
run_command = 'Invoke-BoltPlan -Name '
|
646
|
+
else
|
647
|
+
show_command = 'bolt plan show'
|
648
|
+
run_command = 'bolt plan run'
|
649
|
+
end
|
650
|
+
|
651
|
+
print_message(<<~OUTPUT)
|
652
|
+
Created plan '#{name}' at '#{path}'
|
653
|
+
|
654
|
+
Show this plan with:
|
655
|
+
#{show_command} #{name}
|
656
|
+
Run this plan with:
|
657
|
+
#{run_command} #{name}
|
658
|
+
OUTPUT
|
659
|
+
end
|
660
|
+
|
661
|
+
# Print target names and where they came from.
|
662
|
+
#
|
663
|
+
# @param adhoc [Hash] Adhoc targets provided on the command line.
|
664
|
+
# @param inventory [Hash] Targets provided from the inventory.
|
665
|
+
# @param flag [Boolean] Whether a targeting command-line option was used.
|
666
|
+
#
|
667
|
+
def print_targets(adhoc:, inventory:, flag:, **_kwargs)
|
668
|
+
adhoc_text = colorize(:yellow, "(Not found in inventory file)")
|
540
669
|
|
541
670
|
targets = []
|
542
|
-
targets +=
|
543
|
-
targets +=
|
671
|
+
targets += inventory[:targets].map { |target| [target['name'], nil] }
|
672
|
+
targets += adhoc[:targets].map { |target| [target['name'], adhoc_text] }
|
544
673
|
|
545
674
|
info = +''
|
546
675
|
|
@@ -553,27 +682,31 @@ module Bolt
|
|
553
682
|
end
|
554
683
|
info << "\n\n"
|
555
684
|
|
556
|
-
info << format_inventory_source(
|
557
|
-
info << format_target_summary(
|
685
|
+
info << format_inventory_source(inventory[:file], inventory[:default])
|
686
|
+
info << format_target_summary(inventory[:count], adhoc[:count], flag, false)
|
558
687
|
|
559
688
|
@stream.puts info
|
560
689
|
end
|
561
690
|
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
691
|
+
# Print detailed target information.
|
692
|
+
#
|
693
|
+
# @param adhoc [Hash] Adhoc targets provided on the command line.
|
694
|
+
# @param inventory [Hash] Targets provided from the inventory.
|
695
|
+
# @param flag [Boolean] Whether a targeting command-line option was used.
|
696
|
+
#
|
697
|
+
def print_target_info(adhoc:, inventory:, flag:, **_kwargs)
|
698
|
+
targets = (adhoc[:targets] + inventory[:targets]).sort_by { |t| t['name'] }
|
566
699
|
|
567
700
|
info = +''
|
568
701
|
|
569
702
|
if targets.any?
|
570
|
-
|
703
|
+
adhoc_text = colorize(:yellow, " (Not found in inventory file)")
|
571
704
|
|
572
705
|
targets.each do |target|
|
573
|
-
info << colorize(:cyan, target
|
574
|
-
info <<
|
706
|
+
info << colorize(:cyan, target['name'])
|
707
|
+
info << adhoc_text if adhoc[:targets].include?(target)
|
575
708
|
info << "\n"
|
576
|
-
info << indent(2, target.
|
709
|
+
info << indent(2, target.to_yaml.lines.drop(1).join)
|
577
710
|
info << "\n"
|
578
711
|
end
|
579
712
|
else
|
@@ -581,8 +714,8 @@ module Bolt
|
|
581
714
|
info << indent(2, "No targets\n\n")
|
582
715
|
end
|
583
716
|
|
584
|
-
info << format_inventory_source(
|
585
|
-
info << format_target_summary(
|
717
|
+
info << format_inventory_source(inventory[:file], inventory[:default])
|
718
|
+
info << format_target_summary(inventory[:count], adhoc[:count], flag, true)
|
586
719
|
|
587
720
|
@stream.puts info
|
588
721
|
end
|
@@ -628,7 +761,13 @@ module Bolt
|
|
628
761
|
info
|
629
762
|
end
|
630
763
|
|
631
|
-
|
764
|
+
# Print inventory group information.
|
765
|
+
#
|
766
|
+
# @param count [Integer] Number of groups in the inventory.
|
767
|
+
# @param groups [Array] Names of groups in the inventory.
|
768
|
+
# @param inventory [Hash] Where the inventory was loaded from.
|
769
|
+
#
|
770
|
+
def print_groups(count:, groups:, inventory:)
|
632
771
|
info = +''
|
633
772
|
|
634
773
|
# Add group list
|
@@ -637,18 +776,18 @@ module Bolt
|
|
637
776
|
info << "\n\n"
|
638
777
|
|
639
778
|
# Add inventory file source
|
640
|
-
info << format_inventory_source(
|
779
|
+
info << format_inventory_source(inventory[:source], inventory[:default])
|
641
780
|
|
642
781
|
# Add group count summary
|
643
782
|
info << colorize(:cyan, "Group count\n")
|
644
|
-
info << indent(2, "#{
|
783
|
+
info << indent(2, "#{count} total")
|
645
784
|
|
646
785
|
@stream.puts info
|
647
786
|
end
|
648
787
|
|
649
788
|
# @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
|
650
|
-
def print_apply_result(apply_result
|
651
|
-
print_summary(apply_result, elapsed_time)
|
789
|
+
def print_apply_result(apply_result)
|
790
|
+
print_summary(apply_result, apply_result.elapsed_time)
|
652
791
|
end
|
653
792
|
|
654
793
|
# @param [Bolt::PlanResult] plan_result A PlanResult object
|
@@ -665,6 +804,12 @@ module Bolt
|
|
665
804
|
print_container_result(value.result)
|
666
805
|
when Bolt::ResultSet
|
667
806
|
print_result_set(value)
|
807
|
+
when Bolt::Result
|
808
|
+
print_result(value)
|
809
|
+
when Bolt::ApplyResult
|
810
|
+
print_apply_result(value)
|
811
|
+
when Bolt::Error
|
812
|
+
print_bolt_error(**value.to_h.transform_keys(&:to_sym))
|
668
813
|
else
|
669
814
|
@stream.puts(::JSON.pretty_generate(plan_result, quirks_mode: true))
|
670
815
|
end
|
@@ -704,6 +849,17 @@ module Bolt
|
|
704
849
|
@stream.puts(colorize(:red, message))
|
705
850
|
end
|
706
851
|
|
852
|
+
def print_bolt_error(msg:, details:, **_kwargs)
|
853
|
+
err = msg
|
854
|
+
if (f = details[:file])
|
855
|
+
err += "\n (file: #{f}"
|
856
|
+
err += ", line: #{details[:line]}" if details[:line]
|
857
|
+
err += ", column: #{details[:column]}" if details[:column]
|
858
|
+
err += ")"
|
859
|
+
end
|
860
|
+
@stream.puts(colorize(:red, err))
|
861
|
+
end
|
862
|
+
|
707
863
|
def print_prompt(prompt)
|
708
864
|
@stream.print(colorize(:cyan, indent(4, prompt)))
|
709
865
|
end
|