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
@@ -23,6 +23,8 @@ module Bolt
23
23
  print_result(event[:result])
24
24
  when :message
25
25
  print_message(event[:message])
26
+ when :verbose
27
+ print_message(event[:message]) if @verbose
26
28
  end
27
29
  end
28
30
 
@@ -45,8 +47,13 @@ module Bolt
45
47
  @stream.puts results.to_json
46
48
  end
47
49
  alias print_module_list print_table
50
+ alias print_module_info print_table
48
51
 
49
- def print_task_info(task)
52
+ # Print information about a task.
53
+ #
54
+ # @param task [Bolt::Task] The task information.
55
+ #
56
+ def print_task_info(task:)
50
57
  path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
51
58
  module_dir = if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
52
59
  "built-in module"
@@ -56,11 +63,16 @@ module Bolt
56
63
  @stream.puts task.to_h.merge(module_dir: module_dir).to_json
57
64
  end
58
65
 
59
- def print_tasks(tasks, modulepath)
60
- print_table('tasks' => tasks, 'modulepath' => modulepath)
66
+ # List available tasks.
67
+ #
68
+ # @param tasks [Array] A list of task names and descriptions.
69
+ # @param modulepath [Array] The modulepath.
70
+ #
71
+ def print_tasks(**kwargs)
72
+ print_table(**kwargs)
61
73
  end
62
74
 
63
- def print_plugin_list(plugins, modulepath)
75
+ def print_plugin_list(plugins:, modulepath:)
64
76
  plugins.delete(:validate_resolve_reference)
65
77
  print_table('plugins' => plugins, 'modulepath' => modulepath)
66
78
  end
@@ -75,11 +87,15 @@ module Bolt
75
87
  @stream.puts plan.to_json
76
88
  end
77
89
 
78
- def print_plans(plans, modulepath)
79
- print_table('plans' => plans, 'modulepath' => modulepath)
90
+ def print_plans(**kwargs)
91
+ print_table(**kwargs)
80
92
  end
81
93
 
82
- def print_apply_result(apply_result, _elapsed_time)
94
+ def print_new_plan(**kwargs)
95
+ print_table(**kwargs)
96
+ end
97
+
98
+ def print_apply_result(apply_result)
83
99
  @stream.puts apply_result.to_json
84
100
  end
85
101
 
@@ -92,15 +108,21 @@ module Bolt
92
108
  @stream.puts result_set.to_json
93
109
  end
94
110
 
95
- def print_topics(topics)
96
- print_table('topics' => topics)
111
+ # Print available guide topics.
112
+ #
113
+ # @param topics [Array] The available topics.
114
+ #
115
+ def print_topics(**kwargs)
116
+ print_table(kwargs)
97
117
  end
98
118
 
99
- def print_guide(guide, topic)
100
- @stream.puts({
101
- 'topic' => topic,
102
- 'guide' => guide
103
- }.to_json)
119
+ # Print the guide for the specified topic.
120
+ #
121
+ # @param guide [String] The guide.
122
+ # @param topic [String] The topic.
123
+ #
124
+ def print_guide(**kwargs)
125
+ @stream.puts(kwargs.to_json)
104
126
  end
105
127
 
106
128
  def print_plan_lookup(value)
@@ -113,35 +135,38 @@ module Bolt
113
135
  moduledir: moduledir.to_s }.to_json)
114
136
  end
115
137
 
116
- def print_targets(target_list, inventory_source, default_inventory, _target_flag)
117
- @stream.puts ::JSON.pretty_generate(
118
- inventory: {
119
- targets: target_list[:inventory].map(&:name),
120
- count: target_list[:inventory].count,
121
- file: (inventory_source || default_inventory).to_s
122
- },
123
- adhoc: {
124
- targets: target_list[:adhoc].map(&:name),
125
- count: target_list[:adhoc].count
126
- },
127
- targets: target_list.values.flatten.map(&:name),
128
- count: target_list.values.flatten.count
129
- )
130
- end
131
-
132
- def print_target_info(target_list, _inventory_source, _default_inventory, _target_flag)
133
- targets = target_list.values.flatten
134
-
135
- @stream.puts ::JSON.pretty_generate(
136
- targets: targets.map(&:detail),
137
- count: targets.count
138
- )
139
- end
140
-
141
- def print_groups(groups, _inventory_source, _default_inventory)
142
- count = groups.count
143
- @stream.puts({ groups: groups,
144
- count: count }.to_json)
138
+ # Print target names and where they came from.
139
+ #
140
+ # @param adhoc [Hash] Adhoc targets provided on the command line.
141
+ # @param inventory [Hash] Targets provided from the inventory.
142
+ # @param targets [Array] All targets.
143
+ # @param count [Integer] Number of targets.
144
+ #
145
+ def print_targets(adhoc:, inventory:, targets:, count:, **_kwargs)
146
+ adhoc[:targets] = adhoc[:targets].map { |t| t['name'] }
147
+ inventory[:targets] = inventory[:targets].map { |t| t['name'] }
148
+ targets = targets.map { |t| t['name'] }
149
+ @stream.puts({ adhoc: adhoc, inventory: inventory, targets: targets, count: count }.to_json)
150
+ end
151
+
152
+ # Print target names and where they came from.
153
+ #
154
+ # @param adhoc [Hash] Adhoc targets provided on the command line.
155
+ # @param inventory [Hash] Targets provided from the inventory.
156
+ # @param targets [Array] All targets.
157
+ # @param count [Integer] Number of targets.
158
+ #
159
+ def print_target_info(adhoc:, inventory:, targets:, count:, **_kwargs)
160
+ @stream.puts({ adhoc: adhoc, inventory: inventory, targets: targets, count: count }.to_json)
161
+ end
162
+
163
+ # Print inventory group information.
164
+ #
165
+ # @param count [Integer] Number of groups in the inventory.
166
+ # @param groups [Array] Names of groups in the inventory.
167
+ #
168
+ def print_groups(count:, groups:, **_kwargs)
169
+ @stream.puts({ count: count, groups: groups }.to_json)
145
170
  end
146
171
 
147
172
  def fatal_error(err)
@@ -24,6 +24,8 @@ module Bolt
24
24
  log_container_start(event)
25
25
  when :container_finish
26
26
  log_container_finish(event)
27
+ when :log, :message, :verbose
28
+ log_message(**event)
27
29
  end
28
30
  end
29
31
 
@@ -65,6 +67,10 @@ module Bolt
65
67
  @logger.info("Finished: run container '#{result.object}' failed.")
66
68
  end
67
69
  end
70
+
71
+ def log_message(level:, message:, **_kwargs)
72
+ @logger.send(level, message)
73
+ end
68
74
  end
69
75
  end
70
76
  end
data/lib/bolt/pal.rb CHANGED
@@ -339,7 +339,7 @@ module Bolt
339
339
  end
340
340
 
341
341
  # Write the cache if any entries were updated
342
- File.write(@project.task_cache_file, task_cache.to_json) if updated
342
+ File.write(@project.task_cache_file, task_cache.to_json) if updated && @project
343
343
  filter_content ? filter_content(task_list, @project&.tasks) : task_list
344
344
  end
345
345
 
@@ -435,7 +435,7 @@ module Bolt
435
435
  list << [plan_name, data['description']] unless data['private']
436
436
  end
437
437
 
438
- File.write(@project.plan_cache_file, plan_cache.to_json) if updated
438
+ File.write(@project.plan_cache_file, plan_cache.to_json) if updated && @project
439
439
 
440
440
  filter_content ? filter_content(plan_list, @project&.plans) : plan_list
441
441
  end
@@ -447,7 +447,7 @@ module Bolt
447
447
  # @param mtime [String] The last time the file was modified.
448
448
  #
449
449
  private def file_modified?(path, mtime)
450
- path && !(File.exist?(path) && File.mtime(path).to_s == mtime)
450
+ path && !(File.exist?(path) && File.mtime(path).to_s == mtime.to_s)
451
451
  end
452
452
 
453
453
  def list_plans(filter_content: false)
@@ -512,19 +512,14 @@ module Bolt
512
512
  end
513
513
  end
514
514
 
515
- privie = plan.tag(:private)&.text
516
- unless privie.nil? || %w[true false].include?(privie.downcase)
517
- msg = "Plan #{plan_name} key 'private' must be a boolean, received: #{privie}"
518
- raise Bolt::Error.new(msg, 'bolt/invalid-plan')
519
- end
520
-
521
515
  pp_info = {
522
516
  'name' => plan_name,
523
517
  'description' => description,
524
518
  'parameters' => parameters,
525
- 'module' => mod
519
+ 'module' => mod,
520
+ 'private' => private_plan?(plan)
526
521
  }
527
- pp_info.merge!({ 'private' => privie&.downcase == 'true' }) unless privie.nil?
522
+
528
523
  pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
529
524
  pp_info
530
525
 
@@ -554,14 +549,39 @@ module Bolt
554
549
  'name' => plan_name,
555
550
  'description' => plan.description,
556
551
  'parameters' => parameters,
557
- 'module' => mod
552
+ 'module' => mod,
553
+ 'private' => !!plan.private
558
554
  }
559
- yaml_info.merge!({ 'private' => plan.private }) unless plan.private.nil?
555
+
560
556
  yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
561
557
  yaml_info
562
558
  end
563
559
  end
564
560
 
561
+ # Returns true if the plan is private, false otherwise.
562
+ #
563
+ # @param plan [PuppetStrings::Yard::CodeObjects::Plan] The puppet-strings plan documentation.
564
+ # @return [Boolean]
565
+ #
566
+ private def private_plan?(plan)
567
+ if plan.tag(:private)
568
+ value = plan.tag(:private).text
569
+ api_value = value.downcase == 'true' ? 'private' : 'public'
570
+
571
+ Bolt::Logger.deprecate(
572
+ 'plan_private_tag',
573
+ "Tag '@private #{value}' in plan '#{plan.name}' is deprecated, use '@api #{api_value}' instead"
574
+ )
575
+
576
+ unless %w[true false].include?(plan.tag(:private).text.downcase)
577
+ msg = "Value for '@private' tag in plan '#{plan.name}' must be a boolean, received: #{value}"
578
+ raise Bolt::Error.new(msg, 'bolt/invalid-plan')
579
+ end
580
+ end
581
+
582
+ plan.tag(:api).text == 'private' || plan.tag(:private)&.text&.downcase == 'true'
583
+ end
584
+
565
585
  def get_plan_mtime(path)
566
586
  # If the plan is from the project modules/ directory, or is in the
567
587
  # project itself, include the last mtime of the file so we can compare
@@ -635,6 +655,36 @@ module Bolt
635
655
  end
636
656
  end
637
657
 
658
+ # Return information about a module.
659
+ #
660
+ # @param name [String] The name of the module.
661
+ # @return [Hash]
662
+ #
663
+ def show_module(name)
664
+ name = name.tr('-', '/')
665
+
666
+ data = in_bolt_compiler do |_compiler|
667
+ mod = Puppet.lookup(:current_environment).module(name.split(%r{[/-]}, 2).last)
668
+
669
+ unless mod && (mod.forge_name == name || mod.name == name)
670
+ raise Bolt::Error.new("Could not find module '#{name}' on the modulepath.", 'bolt/unknown-module')
671
+ end
672
+
673
+ {
674
+ name: mod.forge_name || mod.name,
675
+ metadata: mod.metadata,
676
+ path: mod.path,
677
+ plans: mod.plans.map(&:name).sort,
678
+ tasks: mod.tasks.map(&:name).sort
679
+ }
680
+ end
681
+
682
+ data[:plans] = list_plans_with_cache.to_h.slice(*data[:plans]).to_a
683
+ data[:tasks] = list_tasks_with_cache.to_h.slice(*data[:tasks]).to_a
684
+
685
+ data
686
+ end
687
+
638
688
  def generate_types(cache: false)
639
689
  require 'puppet/face/generate'
640
690
  in_bolt_compiler do
@@ -683,7 +733,10 @@ module Bolt
683
733
  # Create a Fiber for the main plan. This will be run along with any
684
734
  # other Fibers created during the plan run in the round_robin, with the
685
735
  # main plan always taking precedence in being resumed.
686
- future = executor.create_future(name: plan_name) do |_scope|
736
+ #
737
+ # Every future except for the main plan needs to have a plan id in
738
+ # order to be tracked for the `wait()` function with no arguments.
739
+ future = executor.create_future(name: plan_name, plan_id: 1) do |_scope|
687
740
  r = compiler.call_function('run_plan', plan_name, params.merge('_bolt_api_call' => true))
688
741
  Bolt::PlanResult.from_pcore(r, 'success')
689
742
  rescue Bolt::Error => e
@@ -13,6 +13,7 @@ module Bolt
13
13
  download
14
14
  eval
15
15
  message
16
+ verbose
16
17
  plan
17
18
  resources
18
19
  script
@@ -219,3 +220,4 @@ require 'bolt/pal/yaml_plan/step/task'
219
220
  require 'bolt/pal/yaml_plan/step/upload'
220
221
  require 'bolt/pal/yaml_plan/step/download'
221
222
  require 'bolt/pal/yaml_plan/step/message'
223
+ require 'bolt/pal/yaml_plan/step/verbose'
@@ -24,14 +24,6 @@ module Bolt
24
24
  private def function
25
25
  'out::message'
26
26
  end
27
-
28
- # Transpiles the step into the plan language
29
- #
30
- def transpile
31
- code = String.new(" ")
32
- code << function_call(function, format_args(body))
33
- code << "\n"
34
- end
35
27
  end
36
28
  end
37
29
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class PAL
5
+ class YamlPlan
6
+ class Step
7
+ class Verbose < Step
8
+ def self.allowed_keys
9
+ super + Set['verbose']
10
+ end
11
+
12
+ def self.required_keys
13
+ Set['verbose']
14
+ end
15
+
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ [body['verbose']]
20
+ end
21
+
22
+ # Returns the function corresponding to the step
23
+ #
24
+ private def function
25
+ 'out::verbose'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -30,7 +30,7 @@ module Bolt
30
30
  plan_string = String.new('')
31
31
  plan_string << "# #{plan_object.description}\n" if plan_object.description
32
32
  plan_string << "# WARNING: This is an autogenerated plan. It might not behave as expected.\n"
33
- plan_string << "# @private #{plan_object.private}\n" unless plan_object.private.nil?
33
+ plan_string << "# @api #{plan_object.private ? 'private' : 'public'}\n" unless plan_object.private.nil?
34
34
  plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
35
35
 
36
36
  plan_string << "plan #{plan_object.name}("
@@ -51,7 +51,7 @@ module Bolt
51
51
  end
52
52
  end
53
53
 
54
- def self.create_plan(plans_path, plan_name, outputter, is_puppet)
54
+ def self.create_plan(plans_path, plan_name, is_puppet)
55
55
  _, name_segments, basename = segment_plan_name(plan_name)
56
56
  dir_path = plans_path.join(*name_segments)
57
57
 
@@ -77,25 +77,7 @@ module Bolt
77
77
  )
78
78
  end
79
79
 
80
- if Bolt::Util.powershell?
81
- show_command = 'Get-BoltPlan -Name '
82
- run_command = 'Invoke-BoltPlan -Name '
83
- else
84
- show_command = 'bolt plan show'
85
- run_command = 'bolt plan run'
86
- end
87
-
88
- output = <<~OUTPUT
89
- Created plan '#{plan_name}' at '#{plan_path}'
90
-
91
- Show this plan with:
92
- #{show_command} #{plan_name}
93
- Run this plan with:
94
- #{run_command} #{plan_name}
95
- OUTPUT
96
-
97
- outputter.print_message(output)
98
- 0
80
+ { name: plan_name, path: plan_path }
99
81
  end
100
82
 
101
83
  def self.segment_plan_name(plan_name)
@@ -4,14 +4,34 @@ require 'fiber'
4
4
 
5
5
  module Bolt
6
6
  class PlanFuture
7
- attr_reader :fiber, :id
8
- attr_accessor :value
7
+ attr_reader :fiber, :id, :scope
8
+ attr_accessor :value, :plan_stack
9
9
 
10
- def initialize(fiber, id, name = nil)
10
+ def initialize(fiber, id, plan_id:, name: nil, scope: nil)
11
11
  @fiber = fiber
12
12
  @id = id
13
13
  @name = name
14
14
  @value = nil
15
+
16
+ # Default to Puppet's current global_scope, otherwise things will
17
+ # blow up when the Fiber Executor tries to override the global_scope.
18
+ @scope = scope || Puppet.lookup(:global_scope) { nil }
19
+
20
+ # The plan invocation ID when the Future is created may be
21
+ # different from the plan ID of the Future when we switch to it if a new
22
+ # plan was run inside the Future, so keep track of the plans that a
23
+ # Future is executing in as a stack. When one plan finishes, pop it off
24
+ # since now we're in the calling plan. These IDs are unique to each plan
25
+ # invocation, not just plan names.
26
+ @plan_stack = [plan_id]
27
+ end
28
+
29
+ def original_plan
30
+ @plan_stack.last
31
+ end
32
+
33
+ def current_plan
34
+ @plan_stack.first
15
35
  end
16
36
 
17
37
  def name