bolt 3.12.0 → 3.16.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.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
  4. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +2 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +5 -1
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +13 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +47 -7
  8. data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
  9. data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
  10. data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
  11. data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
  12. data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
  13. data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
  14. data/bolt-modules/out/lib/puppet/functions/out/message.rb +9 -49
  15. data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
  16. data/guides/{debugging.txt → debugging.yaml} +5 -6
  17. data/guides/{inventory.txt → inventory.yaml} +6 -7
  18. data/guides/{links.txt → links.yaml} +3 -4
  19. data/guides/{logging.txt → logging.yaml} +5 -6
  20. data/guides/{module.txt → module.yaml} +5 -6
  21. data/guides/{modulepath.txt → modulepath.yaml} +5 -6
  22. data/guides/{project.txt → project.yaml} +6 -7
  23. data/guides/{targets.txt → targets.yaml} +5 -6
  24. data/guides/{transports.txt → transports.yaml} +6 -7
  25. data/lib/bolt/analytics.rb +3 -20
  26. data/lib/bolt/application.rb +620 -0
  27. data/lib/bolt/bolt_option_parser.rb +18 -6
  28. data/lib/bolt/cli.rb +592 -772
  29. data/lib/bolt/config/options.rb +2 -2
  30. data/lib/bolt/config/transport/options.rb +12 -0
  31. data/lib/bolt/config/transport/ssh.rb +7 -0
  32. data/lib/bolt/executor.rb +12 -4
  33. data/lib/bolt/fiber_executor.rb +63 -14
  34. data/lib/bolt/outputter/human.rb +201 -43
  35. data/lib/bolt/outputter/json.rb +68 -43
  36. data/lib/bolt/outputter/logger.rb +6 -0
  37. data/lib/bolt/pal.rb +67 -14
  38. data/lib/bolt/pal/yaml_plan/step.rb +2 -0
  39. data/lib/bolt/pal/yaml_plan/step/message.rb +0 -8
  40. data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
  41. data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
  42. data/lib/bolt/plan_creator.rb +2 -20
  43. data/lib/bolt/plan_future.rb +23 -3
  44. data/lib/bolt/plan_result.rb +1 -1
  45. data/lib/bolt/plugin/task.rb +1 -1
  46. data/lib/bolt/project.rb +0 -7
  47. data/lib/bolt/result_set.rb +2 -1
  48. data/lib/bolt/transport/local/connection.rb +17 -1
  49. data/lib/bolt/transport/orch/connection.rb +13 -1
  50. data/lib/bolt/transport/ssh/exec_connection.rb +3 -1
  51. data/lib/bolt/util/format.rb +68 -0
  52. data/lib/bolt/version.rb +1 -1
  53. data/lib/bolt_server/schemas/partials/target-ssh.json +4 -0
  54. data/lib/bolt_server/schemas/partials/target-winrm.json +4 -0
  55. data/lib/bolt_server/transport_app.rb +92 -50
  56. data/lib/bolt_spec/bolt_context.rb +9 -0
  57. data/lib/bolt_spec/plans.rb +1 -1
  58. data/lib/bolt_spec/plans/mock_executor.rb +31 -7
  59. data/lib/bolt_spec/plans/publish_stub.rb +4 -4
  60. data/resources/bolt_bash_completion.sh +1 -1
  61. metadata +29 -15
  62. data/guides/guide.txt +0 -17
  63. data/lib/bolt/secret.rb +0 -37
@@ -202,7 +202,7 @@ module Bolt
202
202
  "level" => {
203
203
  description: "The type of information to log.",
204
204
  type: String,
205
- enum: %w[trace debug error info warn fatal any],
205
+ enum: %w[trace debug error info warn fatal],
206
206
  _default: "warn"
207
207
  }
208
208
  }
@@ -221,7 +221,7 @@ module Bolt
221
221
  "level" => {
222
222
  description: "The type of information to log.",
223
223
  type: String,
224
- enum: %w[trace debug error info warn fatal any],
224
+ enum: %w[trace debug error info warn fatal],
225
225
  _default: "warn"
226
226
  }
227
227
  }
@@ -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 plan_futures
405
- @fiber_executor.plan_futures
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
@@ -5,18 +5,19 @@ require 'bolt/plan_future'
5
5
 
6
6
  module Bolt
7
7
  class FiberExecutor
8
- attr_reader :plan_futures
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
- @plan_futures = []
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
- plan_futures.length > 1
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
- plan_futures << future
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
- plan_futures.each do |future|
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 plan_futures.map(&:id).include?(0) || future.state == "done"
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
- plan_futures.delete(future)
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 plan_futures.all? { |f| %i[returned_immediately unfinished].include?(f.value) }
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
- plan_futures.empty?
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
@@ -78,6 +78,8 @@ module Bolt
78
78
  @disable_depth += 1
79
79
  when :message
80
80
  print_message(event[:message])
81
+ when :verbose
82
+ print_message(event[:message]) if @verbose
81
83
  end
82
84
 
83
85
  if enabled?
@@ -308,7 +310,12 @@ module Bolt
308
310
  )
309
311
  end
310
312
 
311
- def print_tasks(tasks, modulepath)
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:)
312
319
  command = Bolt::Util.powershell? ? 'Get-BoltTask -Name <TASK NAME>' : 'bolt task show <TASK NAME>'
313
320
 
314
321
  tasks = tasks.map do |name, description|
@@ -328,8 +335,11 @@ module Bolt
328
335
  @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
329
336
  end
330
337
 
331
- # @param [Hash] task A hash representing the task
332
- def print_task_info(task)
338
+ # Print information about a task.
339
+ #
340
+ # @param task [Bolt::Task] The task information.
341
+ #
342
+ def print_task_info(task:)
333
343
  params = (task.parameters || []).sort
334
344
 
335
345
  info = +''
@@ -339,7 +349,7 @@ module Bolt
339
349
  info << if task.description
340
350
  indent(2, task.description.chomp)
341
351
  else
342
- indent(2, 'No description')
352
+ indent(2, 'No description available.')
343
353
  end
344
354
  info << "\n\n"
345
355
 
@@ -398,7 +408,7 @@ module Bolt
398
408
  info << if plan['description']
399
409
  indent(2, plan['description'].chomp)
400
410
  else
401
- indent(2, 'No description')
411
+ indent(2, 'No description available.')
402
412
  end
403
413
  info << "\n\n"
404
414
 
@@ -441,7 +451,7 @@ module Bolt
441
451
  @stream.puts info
442
452
  end
443
453
 
444
- def print_plans(plans, modulepath)
454
+ def print_plans(plans:, modulepath:)
445
455
  command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
446
456
 
447
457
  plans = plans.map do |name, description|
@@ -461,7 +471,11 @@ module Bolt
461
471
  @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
462
472
  end
463
473
 
464
- def print_topics(topics)
474
+ # Print available guide topics.
475
+ #
476
+ # @param topics [Array] The available topics.
477
+ #
478
+ def print_topics(topics:, **_kwargs)
465
479
  info = +"#{colorize(:cyan, 'Topics')}\n"
466
480
  info << indent(2, topics.join("\n"))
467
481
  info << "\n\n#{colorize(:cyan, 'Additional information')}\n"
@@ -469,8 +483,20 @@ module Bolt
469
483
  @stream.puts info
470
484
  end
471
485
 
472
- def print_guide(guide, _topic)
473
- @stream.puts(guide)
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
474
500
  end
475
501
 
476
502
  def print_plan_lookup(value)
@@ -478,15 +504,19 @@ module Bolt
478
504
  end
479
505
 
480
506
  def print_module_list(module_list)
507
+ info = +''
508
+
481
509
  module_list.each do |path, modules|
482
- if (mod = modules.find { |m| m[:internal_module_group] })
483
- @stream.puts(colorize(:cyan, mod[:internal_module_group]))
484
- else
485
- @stream.puts(colorize(:cyan, path))
486
- end
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"
487
517
 
488
518
  if modules.empty?
489
- @stream.puts('(no modules installed)')
519
+ info << '(no modules installed)'
490
520
  else
491
521
  module_info = modules.map do |m|
492
522
  version = if m[:version].nil?
@@ -498,24 +528,100 @@ module Bolt
498
528
  [m[:name], version]
499
529
  end
500
530
 
501
- @stream.puts format_table(module_info, 2, 1)
531
+ info << format_table(module_info, 2, 1).to_s
502
532
  end
503
533
 
504
- @stream.write("\n")
534
+ info << "\n\n"
505
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
506
542
  end
507
543
 
508
- def print_plugin_list(plugin_list, modulepath)
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:)
509
615
  info = +''
510
- length = plugin_list.values.map(&:keys).flatten.map(&:length).max + 4
616
+ length = plugins.values.map(&:keys).flatten.map(&:length).max + 4
511
617
 
512
- plugin_list.each do |hook, plugins|
513
- next if plugins.empty?
618
+ plugins.each do |hook, plugin|
619
+ next if plugin.empty?
514
620
  next if hook == :validate_resolve_reference
515
621
 
516
622
  info << colorize(:cyan, "#{hook}\n")
517
623
 
518
- plugins.each do |name, description|
624
+ plugin.each do |name, description|
519
625
  info << indent(2, name.ljust(length))
520
626
  info << truncate(description, 80 - length) if description
521
627
  info << "\n"
@@ -533,12 +639,37 @@ module Bolt
533
639
  @stream.puts info.chomp
534
640
  end
535
641
 
536
- def print_targets(target_list, inventory_source, default_inventory, target_flag)
537
- adhoc = colorize(:yellow, "(Not found in inventory file)")
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)")
538
669
 
539
670
  targets = []
540
- targets += target_list[:inventory].map { |target| [target.name, nil] }
541
- targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
671
+ targets += inventory[:targets].map { |target| [target['name'], nil] }
672
+ targets += adhoc[:targets].map { |target| [target['name'], adhoc_text] }
542
673
 
543
674
  info = +''
544
675
 
@@ -551,27 +682,31 @@ module Bolt
551
682
  end
552
683
  info << "\n\n"
553
684
 
554
- info << format_inventory_source(inventory_source, default_inventory)
555
- info << format_target_summary(target_list[:inventory].count, target_list[:adhoc].count, target_flag, false)
685
+ info << format_inventory_source(inventory[:file], inventory[:default])
686
+ info << format_target_summary(inventory[:count], adhoc[:count], flag, false)
556
687
 
557
688
  @stream.puts info
558
689
  end
559
690
 
560
- def print_target_info(target_list, inventory_source, default_inventory, target_flag)
561
- adhoc_targets = target_list[:adhoc].map(&:name).to_set
562
- inventory_targets = target_list[:inventory].map(&:name).to_set
563
- targets = target_list.values.flatten.sort_by(&:name)
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'] }
564
699
 
565
700
  info = +''
566
701
 
567
702
  if targets.any?
568
- adhoc = colorize(:yellow, " (Not found in inventory file)")
703
+ adhoc_text = colorize(:yellow, " (Not found in inventory file)")
569
704
 
570
705
  targets.each do |target|
571
- info << colorize(:cyan, target.name)
572
- info << adhoc if adhoc_targets.include?(target.name)
706
+ info << colorize(:cyan, target['name'])
707
+ info << adhoc_text if adhoc[:targets].include?(target)
573
708
  info << "\n"
574
- info << indent(2, target.detail.to_yaml.lines.drop(1).join)
709
+ info << indent(2, target.to_yaml.lines.drop(1).join)
575
710
  info << "\n"
576
711
  end
577
712
  else
@@ -579,8 +714,8 @@ module Bolt
579
714
  info << indent(2, "No targets\n\n")
580
715
  end
581
716
 
582
- info << format_inventory_source(inventory_source, default_inventory)
583
- info << format_target_summary(inventory_targets.count, adhoc_targets.count, target_flag, true)
717
+ info << format_inventory_source(inventory[:file], inventory[:default])
718
+ info << format_target_summary(inventory[:count], adhoc[:count], flag, true)
584
719
 
585
720
  @stream.puts info
586
721
  end
@@ -626,7 +761,13 @@ module Bolt
626
761
  info
627
762
  end
628
763
 
629
- def print_groups(groups, inventory_source, default_inventory)
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:)
630
771
  info = +''
631
772
 
632
773
  # Add group list
@@ -635,18 +776,18 @@ module Bolt
635
776
  info << "\n\n"
636
777
 
637
778
  # Add inventory file source
638
- info << format_inventory_source(inventory_source, default_inventory)
779
+ info << format_inventory_source(inventory[:source], inventory[:default])
639
780
 
640
781
  # Add group count summary
641
782
  info << colorize(:cyan, "Group count\n")
642
- info << indent(2, "#{groups.count} total")
783
+ info << indent(2, "#{count} total")
643
784
 
644
785
  @stream.puts info
645
786
  end
646
787
 
647
788
  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
648
- def print_apply_result(apply_result, elapsed_time)
649
- print_summary(apply_result, elapsed_time)
789
+ def print_apply_result(apply_result)
790
+ print_summary(apply_result, apply_result.elapsed_time)
650
791
  end
651
792
 
652
793
  # @param [Bolt::PlanResult] plan_result A PlanResult object
@@ -663,6 +804,12 @@ module Bolt
663
804
  print_container_result(value.result)
664
805
  when Bolt::ResultSet
665
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))
666
813
  else
667
814
  @stream.puts(::JSON.pretty_generate(plan_result, quirks_mode: true))
668
815
  end
@@ -702,6 +849,17 @@ module Bolt
702
849
  @stream.puts(colorize(:red, message))
703
850
  end
704
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
+
705
863
  def print_prompt(prompt)
706
864
  @stream.print(colorize(:cyan, indent(4, prompt)))
707
865
  end