bolt 3.14.1 → 3.17.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 (42) 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/guides/debugging.yaml +27 -0
  5. data/guides/inventory.yaml +23 -0
  6. data/guides/links.yaml +12 -0
  7. data/guides/logging.yaml +17 -0
  8. data/guides/module.yaml +18 -0
  9. data/guides/modulepath.yaml +24 -0
  10. data/guides/project.yaml +21 -0
  11. data/guides/targets.yaml +28 -0
  12. data/guides/transports.yaml +22 -0
  13. data/lib/bolt/analytics.rb +2 -19
  14. data/lib/bolt/application.rb +634 -0
  15. data/lib/bolt/bolt_option_parser.rb +28 -4
  16. data/lib/bolt/cli.rb +592 -788
  17. data/lib/bolt/fiber_executor.rb +7 -3
  18. data/lib/bolt/inventory/inventory.rb +68 -39
  19. data/lib/bolt/inventory.rb +2 -9
  20. data/lib/bolt/module_installer/puppetfile.rb +24 -10
  21. data/lib/bolt/outputter/human.rb +83 -32
  22. data/lib/bolt/outputter/json.rb +63 -38
  23. data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
  24. data/lib/bolt/pal.rb +31 -11
  25. data/lib/bolt/plan_creator.rb +84 -25
  26. data/lib/bolt/plan_future.rb +11 -6
  27. data/lib/bolt/plan_result.rb +1 -1
  28. data/lib/bolt/plugin/task.rb +1 -1
  29. data/lib/bolt/plugin.rb +11 -17
  30. data/lib/bolt/project.rb +0 -7
  31. data/lib/bolt/result_set.rb +2 -1
  32. data/lib/bolt/transport/local/connection.rb +17 -1
  33. data/lib/bolt/transport/orch/connection.rb +13 -1
  34. data/lib/bolt/version.rb +1 -1
  35. data/lib/bolt_server/file_cache.rb +12 -0
  36. data/lib/bolt_server/schemas/action-apply.json +32 -0
  37. data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
  38. data/lib/bolt_server/transport_app.rb +113 -24
  39. data/lib/bolt_spec/bolt_context.rb +1 -1
  40. data/lib/bolt_spec/run.rb +1 -1
  41. metadata +14 -3
  42. data/lib/bolt/secret.rb +0 -37
@@ -49,7 +49,11 @@ module Bolt
49
49
  alias print_module_list print_table
50
50
  alias print_module_info print_table
51
51
 
52
- 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:)
53
57
  path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
54
58
  module_dir = if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
55
59
  "built-in module"
@@ -59,11 +63,16 @@ module Bolt
59
63
  @stream.puts task.to_h.merge(module_dir: module_dir).to_json
60
64
  end
61
65
 
62
- def print_tasks(tasks, modulepath)
63
- 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)
64
73
  end
65
74
 
66
- def print_plugin_list(plugins, modulepath)
75
+ def print_plugin_list(plugins:, modulepath:)
67
76
  plugins.delete(:validate_resolve_reference)
68
77
  print_table('plugins' => plugins, 'modulepath' => modulepath)
69
78
  end
@@ -78,11 +87,15 @@ module Bolt
78
87
  @stream.puts plan.to_json
79
88
  end
80
89
 
81
- def print_plans(plans, modulepath)
82
- print_table('plans' => plans, 'modulepath' => modulepath)
90
+ def print_plans(**kwargs)
91
+ print_table(**kwargs)
83
92
  end
84
93
 
85
- 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)
86
99
  @stream.puts apply_result.to_json
87
100
  end
88
101
 
@@ -95,10 +108,19 @@ module Bolt
95
108
  @stream.puts result_set.to_json
96
109
  end
97
110
 
98
- def print_topics(topics)
99
- 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)
100
117
  end
101
118
 
119
+ # Print the guide for the specified topic.
120
+ #
121
+ # @param guide [String] The guide.
122
+ # @param topic [String] The topic.
123
+ #
102
124
  def print_guide(**kwargs)
103
125
  @stream.puts(kwargs.to_json)
104
126
  end
@@ -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)
@@ -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}("
data/lib/bolt/pal.rb CHANGED
@@ -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
@@ -7,7 +7,7 @@ require 'bolt/util'
7
7
 
8
8
  module Bolt
9
9
  module PlanCreator
10
- def self.validate_input(project, plan_name)
10
+ def self.validate_plan_name(project, plan_name)
11
11
  if project.name.nil?
12
12
  raise Bolt::Error.new(
13
13
  "Project directory '#{project.path}' is not a named project. Unable to create "\
@@ -51,7 +51,15 @@ module Bolt
51
51
  end
52
52
  end
53
53
 
54
- def self.create_plan(plans_path, plan_name, outputter, is_puppet)
54
+ # Create a new plan from the plan templates based on which language the
55
+ # user configured, and whether the plan wraps a script.
56
+ #
57
+ # @param plans_path [string] The path to the new plan
58
+ # @param plan_name [string] The name of the new plan
59
+ # @param is_puppet [boolean] Whether to create a Puppet language plan
60
+ # @param script [string] A reference to a script for the new plan to run
61
+ #
62
+ def self.create_plan(plans_path, plan_name, is_puppet: false, script: nil)
55
63
  _, name_segments, basename = segment_plan_name(plan_name)
56
64
  dir_path = plans_path.join(*name_segments)
57
65
 
@@ -66,8 +74,15 @@ module Bolt
66
74
 
67
75
  type = is_puppet ? 'pp' : 'yaml'
68
76
  plan_path = dir_path + "#{basename}.#{type}"
69
- plan_template = is_puppet ? puppet_plan(plan_name) : yaml_plan(plan_name)
70
-
77
+ plan_template = if is_puppet && script
78
+ puppet_script_plan(plan_name, script)
79
+ elsif is_puppet
80
+ puppet_plan(plan_name)
81
+ elsif script
82
+ yaml_script_plan(script)
83
+ else
84
+ yaml_plan(plan_name)
85
+ end
71
86
  begin
72
87
  File.write(plan_path, plan_template)
73
88
  rescue Errno::EACCES => e
@@ -77,25 +92,7 @@ module Bolt
77
92
  )
78
93
  end
79
94
 
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
95
+ { name: plan_name, path: plan_path }
99
96
  end
100
97
 
101
98
  def self.segment_plan_name(plan_name)
@@ -108,7 +105,11 @@ module Bolt
108
105
  [prefix, name_segments, basename]
109
106
  end
110
107
 
111
- def self.yaml_plan(plan_name)
108
+ # Template for a new simple YAML plan.
109
+ #
110
+ # @param plan_name [string] The name of the new plan
111
+ #
112
+ private_class_method def self.yaml_plan(plan_name)
112
113
  <<~YAML
113
114
  # This is the structure of a simple plan. To learn more about writing
114
115
  # YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
@@ -137,7 +138,42 @@ module Bolt
137
138
  YAML
138
139
  end
139
140
 
140
- def self.puppet_plan(plan_name)
141
+ # Template for a new YAML plan that runs a script.
142
+ #
143
+ # @param script [string] A reference to the script to run.
144
+ #
145
+ private_class_method def self.yaml_script_plan(script)
146
+ <<~YAML
147
+ # This is the structure of a simple plan. To learn more about writing
148
+ # YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
149
+
150
+ # The description sets the description of the plan that will appear
151
+ # in 'bolt plan show' output.
152
+ description: A plan created with bolt plan new
153
+
154
+ # The parameters key defines the parameters that can be passed to
155
+ # the plan.
156
+ parameters:
157
+ targets:
158
+ type: TargetSpec
159
+ description: A list of targets to run actions on
160
+
161
+ # The steps key defines the actions the plan will take in order.
162
+ steps:
163
+ - name: run_script
164
+ script: #{script}
165
+ targets: $targets
166
+
167
+ # The return key sets the return value of the plan.
168
+ return: $run_script
169
+ YAML
170
+ end
171
+
172
+ # Template for a new simple Puppet plan.
173
+ #
174
+ # @param plan_name [string] The name of the new plan
175
+ #
176
+ private_class_method def self.puppet_plan(plan_name)
141
177
  <<~PUPPET
142
178
  # This is the structure of a simple plan. To learn more about writing
143
179
  # Puppet plans, see the documentation: http://pup.pt/bolt-puppet-plans
@@ -156,5 +192,28 @@ module Bolt
156
192
  }
157
193
  PUPPET
158
194
  end
195
+
196
+ # Template for a new Puppet plan that only runs a script.
197
+ #
198
+ # @param plan_name [string] The name of the new plan
199
+ # @param script [string] A reference to the script to run
200
+ #
201
+ private_class_method def self.puppet_script_plan(plan_name, script)
202
+ <<~PUPPET
203
+ # This is the structure of a simple plan. To learn more about writing
204
+ # Puppet plans, see the documentation: http://pup.pt/bolt-puppet-plans
205
+
206
+ # The summary sets the description of the plan that will appear
207
+ # in 'bolt plan show' output. Bolt uses puppet-strings to parse the
208
+ # summary and parameters from the plan.
209
+ # @summary A plan created with bolt plan new.
210
+ # @param targets The targets to run on.
211
+ plan #{plan_name} (
212
+ TargetSpec $targets
213
+ ) {
214
+ return run_script('#{script}', $targets)
215
+ }
216
+ PUPPET
217
+ end
159
218
  end
160
219
  end
@@ -4,14 +4,19 @@ require 'fiber'
4
4
 
5
5
  module Bolt
6
6
  class PlanFuture
7
- attr_reader :fiber, :id
7
+ attr_reader :fiber, :id, :scope
8
8
  attr_accessor :value, :plan_stack
9
9
 
10
- def initialize(fiber, id, plan_id:, name: nil)
11
- @fiber = fiber
12
- @id = id
13
- @name = name
14
- @value = nil
10
+ def initialize(fiber, id, plan_id:, name: nil, scope: nil)
11
+ @fiber = fiber
12
+ @id = id
13
+ @name = name
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
+
15
20
  # The plan invocation ID when the Future is created may be
16
21
  # different from the plan ID of the Future when we switch to it if a new
17
22
  # plan was run inside the Future, so keep track of the plans that a
@@ -6,7 +6,7 @@ require 'bolt/util'
6
6
 
7
7
  module Bolt
8
8
  class PlanResult
9
- attr_accessor :value, :status
9
+ attr_accessor :status, :value
10
10
 
11
11
  # This must be called from inside a compiler
12
12
  def self.from_pcore(result, status)
@@ -59,7 +59,7 @@ module Bolt
59
59
  run_opts = {}
60
60
  run_opts[:run_as] = opts['_run_as'] if opts['_run_as']
61
61
  begin
62
- task = apply_prep.get_task(opts['task'], params)
62
+ task = @context.get_validated_task(opts['task'], params)
63
63
  rescue Bolt::Error => e
64
64
  raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, name, 'puppet_library')
65
65
  end
data/lib/bolt/plugin.rb CHANGED
@@ -127,29 +127,15 @@ module Bolt
127
127
  end
128
128
  end
129
129
 
130
- def self.setup(config, pal, analytics = Bolt::Analytics::NoopClient.new, **opts)
131
- plugins = new(config, pal, analytics, **opts)
132
-
133
- config.plugins.each_key do |plugin|
134
- plugins.by_name(plugin)
135
- end
136
-
137
- plugins.plugin_hooks.merge!(plugins.resolve_references(config.plugin_hooks))
138
-
139
- plugins
140
- end
141
-
142
130
  RUBY_PLUGINS = %w[task prompt env_var puppetdb puppet_connect_data].freeze
143
131
  BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
144
132
  yaml env_var gcloud_inventory].freeze
145
133
  DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
146
134
 
147
135
  attr_reader :pal, :plugin_context
148
- attr_accessor :plugin_hooks
136
+ attr_writer :plugin_hooks
149
137
 
150
- private_class_method :new
151
-
152
- def initialize(config, pal, analytics, load_plugins: true)
138
+ def initialize(config, pal, analytics = Bolt::Analytics::NoopClient.new, load_plugins: true)
153
139
  @config = config
154
140
  @analytics = analytics
155
141
  @plugin_context = PluginContext.new(config, pal, self)
@@ -166,7 +152,15 @@ module Bolt
166
152
  raise Bolt::Error.new(msg, 'bolt/plugin-error')
167
153
  end
168
154
  @unresolved_plugin_configs['puppetdb'] = config.puppetdb if config.puppetdb
169
- @plugin_hooks = DEFAULT_PLUGIN_HOOKS.dup
155
+ end
156
+
157
+ # Returns a map of configured plugin hooks. Any unresolved plugin references
158
+ # are resolved.
159
+ #
160
+ # @return [Hash[String, Hash]]
161
+ #
162
+ def plugin_hooks
163
+ @plugin_hooks ||= DEFAULT_PLUGIN_HOOKS.merge(resolve_references(@config.plugin_hooks))
170
164
  end
171
165
 
172
166
  def modules