bolt 2.34.0 → 2.40.1

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 (74) 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 +45 -18
  15. data/lib/bolt/cli.rb +98 -116
  16. data/lib/bolt/config.rb +184 -80
  17. data/lib/bolt/config/options.rb +148 -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 +12 -69
  21. data/lib/bolt/config/transport/ssh.rb +8 -19
  22. data/lib/bolt/error.rb +24 -0
  23. data/lib/bolt/executor.rb +92 -18
  24. data/lib/bolt/inventory.rb +25 -0
  25. data/lib/bolt/inventory/group.rb +0 -8
  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 +22 -3
  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 +18 -6
  36. data/lib/bolt/pal/yaml_plan.rb +7 -0
  37. data/lib/bolt/plugin.rb +41 -12
  38. data/lib/bolt/plugin/cache.rb +76 -0
  39. data/lib/bolt/plugin/module.rb +4 -4
  40. data/lib/bolt/plugin/puppetdb.rb +1 -1
  41. data/lib/bolt/project.rb +59 -40
  42. data/lib/bolt/project_manager.rb +201 -0
  43. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +49 -4
  44. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +3 -3
  45. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  46. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +5 -3
  47. data/lib/bolt/puppetdb/client.rb +11 -2
  48. data/lib/bolt/puppetdb/config.rb +4 -3
  49. data/lib/bolt/rerun.rb +1 -5
  50. data/lib/bolt/shell/bash.rb +8 -2
  51. data/lib/bolt/shell/powershell.rb +21 -3
  52. data/lib/bolt/target.rb +4 -0
  53. data/lib/bolt/task/run.rb +1 -1
  54. data/lib/bolt/transport/local.rb +13 -0
  55. data/lib/bolt/transport/orch.rb +0 -5
  56. data/lib/bolt/transport/orch/connection.rb +10 -3
  57. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  58. data/lib/bolt/util.rb +36 -7
  59. data/lib/bolt/validator.rb +227 -0
  60. data/lib/bolt/version.rb +1 -1
  61. data/lib/bolt/yarn.rb +23 -0
  62. data/lib/bolt_server/base_config.rb +3 -1
  63. data/lib/bolt_server/config.rb +3 -1
  64. data/lib/bolt_server/plugin.rb +13 -0
  65. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  66. data/lib/bolt_server/schemas/connect-data.json +22 -0
  67. data/lib/bolt_server/schemas/partials/task.json +2 -2
  68. data/lib/bolt_server/transport_app.rb +82 -23
  69. data/lib/bolt_spec/plans/mock_executor.rb +4 -1
  70. data/libexec/apply_catalog.rb +1 -1
  71. data/libexec/custom_facts.rb +1 -1
  72. data/libexec/query_resources.rb +1 -1
  73. metadata +22 -14
  74. data/lib/bolt/project_migrator.rb +0 -80
@@ -254,14 +254,6 @@ module Bolt
254
254
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
255
255
  @logger.warn(msg)
256
256
  end
257
-
258
- Bolt::Util.walk_keys(input) do |key|
259
- if @plugins.reference?(key)
260
- raise ValidationError.new("Group keys cannot be specified as _plugin references", @name)
261
- else
262
- key
263
- end
264
- end
265
257
  end
266
258
 
267
259
  def validate(used_group_names = Set.new, used_target_names = Set.new, used_aliases = {})
@@ -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
@@ -215,7 +234,7 @@ module Bolt
215
234
 
216
235
  def print_tasks(tasks, modulepath)
217
236
  command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
218
- print_table(tasks)
237
+ tasks.any? ? print_table(tasks) : print_message('No available tasks')
219
238
  print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
220
239
  "\nUse '#{command}' to view "\
221
240
  "details and parameters for a specific task.")
@@ -299,7 +318,7 @@ module Bolt
299
318
 
300
319
  def print_plans(plans, modulepath)
301
320
  command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
302
- print_table(plans)
321
+ plans.any? ? print_table(plans) : print_message('No available plans')
303
322
  print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
304
323
  "\nUse '#{command}' to view "\
305
324
  "details and parameters for a specific plan.")
@@ -3,7 +3,7 @@
3
3
  module Bolt
4
4
  class Outputter
5
5
  class JSON < Bolt::Outputter
6
- def initialize(color, verbose, trace, stream = $stdout)
6
+ def initialize(color, verbose, trace, spin, stream = $stdout)
7
7
  super
8
8
  @items_open = false
9
9
  @object_open = false
@@ -6,7 +6,7 @@ module Bolt
6
6
  class Outputter
7
7
  class Logger < Bolt::Outputter
8
8
  def initialize(verbose, trace)
9
- super(false, verbose, trace)
9
+ super(false, verbose, trace, false)
10
10
  @logger = Bolt::Logger.logger(self)
11
11
  end
12
12
 
@@ -5,7 +5,7 @@ require 'bolt/pal'
5
5
  module Bolt
6
6
  class Outputter
7
7
  class Rainbow < Bolt::Outputter::Human
8
- def initialize(color, verbose, trace, stream = $stdout)
8
+ def initialize(color, verbose, trace, spin, stream = $stdout)
9
9
  begin
10
10
  require 'paint'
11
11
  if Bolt::Util.windows?
@@ -53,7 +53,7 @@ module Bolt
53
53
  @state = :normal if c == 'm'
54
54
  end
55
55
  end
56
- a.join('')
56
+ a.join
57
57
  else
58
58
  "\033[#{COLORS[color]}m#{string}\033[0m"
59
59
  end
@@ -62,6 +62,17 @@ module Bolt
62
62
  end
63
63
  end
64
64
 
65
+ def start_spin
66
+ return unless @spin
67
+ @spin = true
68
+ @spin_thread = Thread.new do
69
+ loop do
70
+ @stream.print(colorize(:rainbow, @pinwheel.rotate!.first + "\b"))
71
+ sleep(0.1)
72
+ end
73
+ end
74
+ end
75
+
65
76
  def print_summary(results, elapsed_time = nil)
66
77
  ok_set = results.ok_set
67
78
  unless ok_set.empty?