bolt 2.33.2 → 2.38.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  5. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  12. data/lib/bolt/analytics.rb +27 -8
  13. data/lib/bolt/apply_result.rb +3 -3
  14. data/lib/bolt/bolt_option_parser.rb +48 -16
  15. data/lib/bolt/cli.rb +154 -249
  16. data/lib/bolt/config.rb +188 -55
  17. data/lib/bolt/config/options.rb +147 -87
  18. data/lib/bolt/config/transport/base.rb +10 -19
  19. data/lib/bolt/config/transport/local.rb +1 -7
  20. data/lib/bolt/config/transport/options.rb +10 -68
  21. data/lib/bolt/config/transport/ssh.rb +8 -14
  22. data/lib/bolt/error.rb +33 -3
  23. data/lib/bolt/executor.rb +92 -6
  24. data/lib/bolt/inventory.rb +25 -0
  25. data/lib/bolt/inventory/group.rb +2 -1
  26. data/lib/bolt/inventory/options.rb +130 -0
  27. data/lib/bolt/inventory/target.rb +10 -11
  28. data/lib/bolt/module_installer.rb +21 -13
  29. data/lib/bolt/module_installer/resolver.rb +1 -1
  30. data/lib/bolt/outputter.rb +19 -5
  31. data/lib/bolt/outputter/human.rb +41 -10
  32. data/lib/bolt/outputter/json.rb +1 -1
  33. data/lib/bolt/outputter/logger.rb +1 -1
  34. data/lib/bolt/outputter/rainbow.rb +13 -2
  35. data/lib/bolt/pal.rb +19 -7
  36. data/lib/bolt/pal/yaml_plan.rb +7 -0
  37. data/lib/bolt/plan_creator.rb +160 -0
  38. data/lib/bolt/plugin.rb +42 -13
  39. data/lib/bolt/plugin/cache.rb +76 -0
  40. data/lib/bolt/plugin/module.rb +4 -4
  41. data/lib/bolt/plugin/puppetdb.rb +1 -1
  42. data/lib/bolt/project.rb +59 -40
  43. data/lib/bolt/project_manager.rb +201 -0
  44. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +51 -5
  45. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
  46. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  47. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +5 -3
  48. data/lib/bolt/puppetdb/client.rb +11 -2
  49. data/lib/bolt/puppetdb/config.rb +9 -8
  50. data/lib/bolt/rerun.rb +1 -5
  51. data/lib/bolt/shell/bash.rb +8 -2
  52. data/lib/bolt/shell/powershell.rb +22 -4
  53. data/lib/bolt/target.rb +4 -0
  54. data/lib/bolt/task/run.rb +1 -1
  55. data/lib/bolt/transport/local.rb +13 -0
  56. data/lib/bolt/transport/orch.rb +0 -5
  57. data/lib/bolt/transport/orch/connection.rb +10 -3
  58. data/lib/bolt/transport/remote.rb +1 -1
  59. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  60. data/lib/bolt/util.rb +41 -7
  61. data/lib/bolt/validator.rb +226 -0
  62. data/lib/bolt/version.rb +1 -1
  63. data/lib/bolt/yarn.rb +23 -0
  64. data/lib/bolt_server/base_config.rb +3 -1
  65. data/lib/bolt_server/config.rb +3 -1
  66. data/lib/bolt_server/file_cache.rb +2 -0
  67. data/lib/bolt_server/plugin.rb +13 -0
  68. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  69. data/lib/bolt_server/schemas/connect-data.json +22 -0
  70. data/lib/bolt_server/schemas/partials/task.json +2 -2
  71. data/lib/bolt_server/transport_app.rb +82 -23
  72. data/lib/bolt_spec/plans/mock_executor.rb +4 -1
  73. data/libexec/apply_catalog.rb +1 -1
  74. data/libexec/custom_facts.rb +1 -1
  75. data/libexec/query_resources.rb +1 -1
  76. metadata +22 -13
  77. data/lib/bolt/project_migrator.rb +0 -80
@@ -241,10 +241,11 @@ module Bolt
241
241
  end
242
242
 
243
243
  if input.key?('nodes')
244
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
244
245
  msg = <<~MSG.chomp
245
246
  Found 'nodes' key in group #{@name}. This looks like a v1 inventory file, which is
246
247
  no longer supported by Bolt. Migrate to a v2 inventory file automatically using
247
- 'bolt project migrate'.
248
+ '#{command}'.
248
249
  MSG
249
250
  raise ValidationError.new(msg, nil)
250
251
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/config/options'
4
+
5
+ module Bolt
6
+ class Inventory
7
+ module Options
8
+ # Top-level options available in the inventory.
9
+ OPTIONS = %w[
10
+ config
11
+ facts
12
+ features
13
+ groups
14
+ targets
15
+ vars
16
+ ].freeze
17
+
18
+ # Definitions used to validate the data.
19
+ # https://github.com/puppetlabs/bolt/blob/main/schemas/README.md
20
+ DEFINITIONS = {
21
+ "alias" => {
22
+ description: "A unique alias to refer to the target. Aliases cannot conflict "\
23
+ "with the name of a group, the name of a target, or another alias.",
24
+ type: [String, Array],
25
+ uniqueItems: true,
26
+ items: {
27
+ type: String,
28
+ _plugin: true
29
+ },
30
+ _plugin: true
31
+ },
32
+ "config" => {
33
+ description: "A map of configuration options.",
34
+ type: Hash,
35
+ # These properties are populated as part of Bolt::Inventory.schema
36
+ properties: {},
37
+ _plugin: true
38
+ },
39
+ "facts" => {
40
+ description: "A map of system information, also known as facts, for the target.",
41
+ type: Hash,
42
+ _plugin: true
43
+ },
44
+ "features" => {
45
+ description: "A list of available features for the target.",
46
+ type: Array,
47
+ uniqueItems: true,
48
+ items: {
49
+ type: String,
50
+ _plugin: true
51
+ },
52
+ _plugin: true
53
+ },
54
+ "groups" => {
55
+ description: "A list of groups and their associated configuration.",
56
+ type: Array,
57
+ items: {
58
+ type: Hash,
59
+ required: ["name"],
60
+ properties: {
61
+ "config" => { _ref: "config" },
62
+ "facts" => { _ref: "facts" },
63
+ "features" => { _ref: "features" },
64
+ "groups" => { _ref: "groups" },
65
+ "name" => { _ref: "name" },
66
+ "plugin_hooks" => { _ref: "plugin_hooks" },
67
+ "targets" => { _ref: "targets" },
68
+ "vars" => { _ref: "vars" }
69
+ },
70
+ _plugin: true
71
+ },
72
+ _plugin: true
73
+ },
74
+ "name" => {
75
+ description: "A human-readable name to refer to the group or target. Names "\
76
+ "cannot conflict with the name of a group, the name of a target, "\
77
+ "or the alias of a target. A name is required for a group and is "\
78
+ "required for a target unless the uri option is set.",
79
+ type: String,
80
+ _plugin: true
81
+ },
82
+ "plugin_hooks" => {
83
+ description: "Configuration for the Puppet library plugin used to install the "\
84
+ "Puppet agent on the target. For more information, see "\
85
+ "https://pup.pt/bolt-plugin-hooks",
86
+ type: Hash,
87
+ properties: {
88
+ "puppet_library" => {
89
+ description: "Configuration for the Puppet library plugin.",
90
+ type: Hash,
91
+ _plugin: true
92
+ }
93
+ },
94
+ _plugin: true
95
+ },
96
+ "targets" => {
97
+ description: "A list of targets and their associated configuration.",
98
+ type: Array,
99
+ items: {
100
+ type: [String, Hash],
101
+ properties: {
102
+ "alias" => { _ref: "alias" },
103
+ "config" => { _ref: "config" },
104
+ "facts" => { _ref: "facts" },
105
+ "features" => { _ref: "features" },
106
+ "name" => { _ref: "name" },
107
+ "plugin_hooks" => { _ref: "plugin_hooks" },
108
+ "uri" => { _ref: "uri" },
109
+ "vars" => { _ref: "vars" }
110
+ },
111
+ _plugin: true
112
+ },
113
+ _plugin: true
114
+ },
115
+ "uri" => {
116
+ description: "The URI of the target. This option is required unless the name "\
117
+ "option is set.",
118
+ type: String,
119
+ format: "uri",
120
+ _plugin: true
121
+ },
122
+ "vars" => {
123
+ description: "A map of variables for the group or target.",
124
+ type: Hash,
125
+ _plugin: true
126
+ }
127
+ }.freeze
128
+ end
129
+ end
130
+ end
@@ -31,7 +31,8 @@ module Bolt
31
31
  end
32
32
 
33
33
  if @name == 'localhost'
34
- target_data = localhost_defaults(target_data)
34
+ default = { 'config' => { 'transport' => 'local' } }
35
+ target_data = Bolt::Util.deep_merge(default, target_data)
35
36
  end
36
37
 
37
38
  @config = target_data['config'] || {}
@@ -49,18 +50,16 @@ module Bolt
49
50
  validate
50
51
  end
51
52
 
52
- def localhost_defaults(data)
53
+ def set_local_defaults
54
+ return if @set_local_default
53
55
  defaults = {
54
- 'config' => {
55
- 'transport' => 'local',
56
- 'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
57
- },
58
- 'features' => ['puppet-agent']
56
+ 'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
59
57
  }
60
- data = Bolt::Util.deep_merge(defaults, data)
61
- # If features is an empty array deep_merge won't add the puppet-agent
62
- data['features'] += ['puppet-agent'] if data['features'].empty?
63
- data
58
+ old_config = @config
59
+ @config = Bolt::Util.deep_merge(defaults, @config)
60
+ invalidate_config_cache! if old_config != @config
61
+ set_feature('puppet-agent')
62
+ @set_local_default = true
64
63
  end
65
64
 
66
65
  # rubocop:disable Naming/AccessorMethodName
@@ -17,14 +17,14 @@ module Bolt
17
17
 
18
18
  # Adds a single module to the project.
19
19
  #
20
- def add(name, specs, puppetfile_path, moduledir, config_path)
20
+ def add(name, specs, puppetfile_path, moduledir, project_file, config)
21
21
  project_specs = Specs.new(specs)
22
22
 
23
23
  # Exit early if project config already includes a spec with this name.
24
24
  if project_specs.include?(name)
25
25
  @outputter.print_message(
26
- "Project configuration file #{config_path} already includes specification with name "\
27
- "#{name}. Nothing to do."
26
+ "Project configuration file #{project_file} already includes specification "\
27
+ "with name #{name}. Nothing to do."
28
28
  )
29
29
  return true
30
30
  end
@@ -47,30 +47,32 @@ module Bolt
47
47
  # a version conflict.
48
48
  @outputter.print_action_step("Resolving module dependencies, this may take a moment")
49
49
 
50
+ @outputter.start_spin
50
51
  begin
51
52
  resolve_specs.add_specs('name' => name)
52
- puppetfile = Resolver.new.resolve(resolve_specs)
53
+ puppetfile = Resolver.new.resolve(resolve_specs, config)
53
54
  rescue Bolt::Error
54
55
  project_specs.add_specs('name' => name)
55
- puppetfile = Resolver.new.resolve(project_specs)
56
+ puppetfile = Resolver.new.resolve(project_specs, config)
56
57
  end
58
+ @outputter.stop_spin
57
59
 
58
60
  # Display the diff between the existing Puppetfile and the new Puppetfile.
59
61
  print_puppetfile_diff(existing_puppetfile, puppetfile)
60
62
 
61
63
  # Add the module to the project configuration.
62
- @outputter.print_action_step("Updating project configuration file at #{config_path}")
64
+ @outputter.print_action_step("Updating project configuration file at #{project_file}")
63
65
 
64
- data = Bolt::Util.read_yaml_hash(config_path, 'project')
66
+ data = Bolt::Util.read_yaml_hash(project_file, 'project')
65
67
  data['modules'] ||= []
66
68
  data['modules'] << name.tr('-', '/')
67
69
 
68
70
  begin
69
- File.write(config_path, data.to_yaml)
71
+ File.write(project_file, data.to_yaml)
70
72
  rescue SystemCallError => e
71
73
  raise Bolt::FileError.new(
72
74
  "Unable to update project configuration file: #{e.message}",
73
- config
75
+ project_file
74
76
  )
75
77
  end
76
78
 
@@ -79,7 +81,7 @@ module Bolt
79
81
  puppetfile.write(puppetfile_path, moduledir)
80
82
 
81
83
  # Install the modules.
82
- install_puppetfile(puppetfile_path, moduledir)
84
+ install_puppetfile(puppetfile_path, moduledir, config)
83
85
  end
84
86
 
85
87
  # Outputs a diff of an old Puppetfile and a new Puppetfile.
@@ -145,7 +147,7 @@ module Bolt
145
147
 
146
148
  # Installs a project's module dependencies.
147
149
  #
148
- def install(specs, path, moduledir, force: false, resolve: true)
150
+ def install(specs, path, moduledir, config = {}, force: false, resolve: true)
149
151
  @outputter.print_message("Installing project modules\n\n")
150
152
 
151
153
  if resolve != false
@@ -155,7 +157,11 @@ module Bolt
155
157
  # and write a Puppetfile.
156
158
  if force || !path.exist?
157
159
  @outputter.print_action_step("Resolving module dependencies, this may take a moment")
158
- puppetfile = Resolver.new.resolve(specs)
160
+
161
+ # This doesn't use the block as it's more testable to just mock *_spin
162
+ @outputter.start_spin
163
+ puppetfile = Resolver.new.resolve(specs, config)
164
+ @outputter.stop_spin
159
165
 
160
166
  # We get here either through 'bolt module install' which uses the
161
167
  # managed modulepath (which isn't configurable) or through bolt
@@ -177,14 +183,16 @@ module Bolt
177
183
  end
178
184
 
179
185
  # Install the modules.
180
- install_puppetfile(path, moduledir)
186
+ install_puppetfile(path, moduledir, config)
181
187
  end
182
188
 
183
189
  # Installs the Puppetfile and generates types.
184
190
  #
185
191
  def install_puppetfile(path, moduledir, config = {})
186
192
  @outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
193
+ @outputter.start_spin
187
194
  ok = Installer.new(config).install(path, moduledir)
195
+ @outputter.stop_spin
188
196
 
189
197
  # Automatically generate types after installing modules
190
198
  @outputter.print_action_step("Generating type references")
@@ -9,7 +9,7 @@ module Bolt
9
9
  class Resolver
10
10
  # Resolves module specs and returns a Puppetfile object.
11
11
  #
12
- def resolve(specs)
12
+ def resolve(specs, _config = {})
13
13
  require 'puppetfile-resolver'
14
14
 
15
15
  # Build the document model from the specs.
@@ -2,24 +2,25 @@
2
2
 
3
3
  module Bolt
4
4
  class Outputter
5
- def self.for_format(format, color, verbose, trace)
5
+ def self.for_format(format, color, verbose, trace, spin)
6
6
  case format
7
7
  when 'human'
8
- Bolt::Outputter::Human.new(color, verbose, trace)
8
+ Bolt::Outputter::Human.new(color, verbose, trace, spin)
9
9
  when 'json'
10
- Bolt::Outputter::JSON.new(color, verbose, trace)
10
+ Bolt::Outputter::JSON.new(color, verbose, trace, false)
11
11
  when 'rainbow'
12
- Bolt::Outputter::Rainbow.new(color, verbose, trace)
12
+ Bolt::Outputter::Rainbow.new(color, verbose, trace, spin)
13
13
  when nil
14
14
  raise "Cannot use outputter before parsing."
15
15
  end
16
16
  end
17
17
 
18
- def initialize(color, verbose, trace, stream = $stdout)
18
+ def initialize(color, verbose, trace, spin, stream = $stdout)
19
19
  @color = color
20
20
  @verbose = verbose
21
21
  @trace = trace
22
22
  @stream = stream
23
+ @spin = spin
23
24
  end
24
25
 
25
26
  def indent(indent, string)
@@ -34,6 +35,19 @@ module Bolt
34
35
  def print_error
35
36
  raise NotImplementedError, "print_error() must be implemented by the outputter class"
36
37
  end
38
+
39
+ def start_spin; end
40
+
41
+ def stop_spin; end
42
+
43
+ def spin
44
+ start_spin
45
+ begin
46
+ yield
47
+ ensure
48
+ stop_spin
49
+ end
50
+ end
37
51
  end
38
52
  end
39
53
 
@@ -14,12 +14,13 @@ module Bolt
14
14
 
15
15
  def print_head; end
16
16
 
17
- def initialize(color, verbose, trace, stream = $stdout)
17
+ def initialize(color, verbose, trace, spin, stream = $stdout)
18
18
  super
19
19
  # Plans and without_default_logging() calls can both be nested, so we
20
20
  # track each of them with a "stack" consisting of an integer.
21
21
  @plan_depth = 0
22
22
  @disable_depth = 0
23
+ @pinwheel = %w[- \\ | /]
23
24
  end
24
25
 
25
26
  def colorize(color, string)
@@ -30,6 +31,24 @@ module Bolt
30
31
  end
31
32
  end
32
33
 
34
+ def start_spin
35
+ return unless @spin
36
+ @spin = true
37
+ @spin_thread = Thread.new do
38
+ loop do
39
+ sleep(0.1)
40
+ @stream.print(colorize(:cyan, @pinwheel.rotate!.first + "\b"))
41
+ end
42
+ end
43
+ end
44
+
45
+ def stop_spin
46
+ return unless @spin
47
+ @spin_thread.terminate
48
+ @spin = false
49
+ @stream.print("\b")
50
+ end
51
+
33
52
  def remove_trail(string)
34
53
  string.sub(/\s\z/, '')
35
54
  end
@@ -214,9 +233,10 @@ module Bolt
214
233
  end
215
234
 
216
235
  def print_tasks(tasks, modulepath)
217
- print_table(tasks)
236
+ command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
237
+ tasks.any? ? print_table(tasks) : print_message('No available tasks')
218
238
  print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
219
- "\nUse `bolt task show <task-name>` to view "\
239
+ "\nUse '#{command}' to view "\
220
240
  "details and parameters for a specific task.")
221
241
  end
222
242
 
@@ -225,20 +245,26 @@ module Bolt
225
245
  # Building lots of strings...
226
246
  pretty_params = +""
227
247
  task_info = +""
228
- usage = +"bolt task run --targets <node-name> #{task.name}"
248
+ usage = if Bolt::Util.powershell?
249
+ +"Invoke-BoltTask -Name #{task.name} -Targets <targets>"
250
+ else
251
+ +"bolt task run #{task.name} --targets <targets>"
252
+ end
229
253
 
230
254
  task.parameters&.each do |k, v|
231
255
  pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
232
256
  pretty_params << " Default: #{v['default'].inspect}\n" if v.key?('default')
233
257
  pretty_params << " #{v['description']}\n" if v['description']
234
- usage << if v['type'].is_a?(Puppet::Pops::Types::POptionalType)
258
+ usage << if v['type'].start_with?("Optional")
235
259
  " [#{k}=<value>]"
236
260
  else
237
261
  " #{k}=<value>"
238
262
  end
239
263
  end
240
264
 
241
- usage << " [--noop]" if task.supports_noop
265
+ if task.supports_noop
266
+ usage << Bolt::Util.powershell? ? '[-Noop]' : '[--noop]'
267
+ end
242
268
 
243
269
  task_info << "\n#{task.name}"
244
270
  task_info << " - #{task.description}" if task.description
@@ -261,7 +287,11 @@ module Bolt
261
287
  # Building lots of strings...
262
288
  pretty_params = +""
263
289
  plan_info = +""
264
- usage = +"bolt plan run #{plan['name']}"
290
+ usage = if Bolt::Util.powershell?
291
+ +"Invoke-BoltPlan -Name #{plan['name']}"
292
+ else
293
+ +"bolt plan run #{plan['name']}"
294
+ end
265
295
 
266
296
  plan['parameters'].each do |name, p|
267
297
  pretty_params << "- #{name}: #{p['type']}\n"
@@ -287,16 +317,17 @@ module Bolt
287
317
  end
288
318
 
289
319
  def print_plans(plans, modulepath)
290
- print_table(plans)
320
+ command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
321
+ plans.any? ? print_table(plans) : print_message('No available plans')
291
322
  print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
292
- "\nUse `bolt plan show <plan-name>` to view "\
323
+ "\nUse '#{command}' to view "\
293
324
  "details and parameters for a specific plan.")
294
325
  end
295
326
 
296
327
  def print_topics(topics)
297
328
  print_message("Available topics are:")
298
329
  print_message(topics.join("\n"))
299
- print_message("\nUse `bolt guide <topic>` to view a specific guide.")
330
+ print_message("\nUse 'bolt guide <TOPIC>' to view a specific guide.")
300
331
  end
301
332
 
302
333
  def print_guide(guide, _topic)