bolt 2.35.0 → 2.40.2

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 (59) 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/lib/bolt/analytics.rb +27 -8
  5. data/lib/bolt/apply_result.rb +3 -3
  6. data/lib/bolt/bolt_option_parser.rb +45 -18
  7. data/lib/bolt/cli.rb +92 -110
  8. data/lib/bolt/config.rb +184 -80
  9. data/lib/bolt/config/options.rb +144 -83
  10. data/lib/bolt/config/transport/base.rb +10 -19
  11. data/lib/bolt/config/transport/local.rb +1 -7
  12. data/lib/bolt/config/transport/options.rb +11 -68
  13. data/lib/bolt/config/transport/ssh.rb +8 -19
  14. data/lib/bolt/executor.rb +5 -17
  15. data/lib/bolt/inventory.rb +25 -0
  16. data/lib/bolt/inventory/group.rb +0 -8
  17. data/lib/bolt/inventory/options.rb +130 -0
  18. data/lib/bolt/inventory/target.rb +10 -11
  19. data/lib/bolt/module_installer.rb +21 -13
  20. data/lib/bolt/module_installer/resolver.rb +1 -1
  21. data/lib/bolt/outputter.rb +19 -5
  22. data/lib/bolt/outputter/human.rb +20 -1
  23. data/lib/bolt/outputter/json.rb +1 -1
  24. data/lib/bolt/outputter/logger.rb +1 -1
  25. data/lib/bolt/outputter/rainbow.rb +13 -2
  26. data/lib/bolt/plugin.rb +41 -12
  27. data/lib/bolt/plugin/cache.rb +76 -0
  28. data/lib/bolt/plugin/module.rb +4 -4
  29. data/lib/bolt/plugin/puppetdb.rb +1 -1
  30. data/lib/bolt/project.rb +59 -40
  31. data/lib/bolt/project_manager.rb +201 -0
  32. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +49 -4
  33. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +3 -3
  34. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  35. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +5 -3
  36. data/lib/bolt/puppetdb/client.rb +8 -0
  37. data/lib/bolt/puppetdb/config.rb +1 -2
  38. data/lib/bolt/rerun.rb +1 -5
  39. data/lib/bolt/shell/bash.rb +8 -2
  40. data/lib/bolt/shell/powershell.rb +21 -3
  41. data/lib/bolt/target.rb +4 -0
  42. data/lib/bolt/task/run.rb +1 -1
  43. data/lib/bolt/transport/local.rb +13 -0
  44. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  45. data/lib/bolt/util.rb +36 -7
  46. data/lib/bolt/validator.rb +227 -0
  47. data/lib/bolt/version.rb +1 -1
  48. data/lib/bolt_server/base_config.rb +3 -1
  49. data/lib/bolt_server/config.rb +3 -1
  50. data/lib/bolt_server/plugin.rb +13 -0
  51. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  52. data/lib/bolt_server/schemas/connect-data.json +22 -0
  53. data/lib/bolt_server/schemas/partials/task.json +3 -3
  54. data/lib/bolt_server/transport_app.rb +68 -40
  55. data/libexec/apply_catalog.rb +1 -1
  56. data/libexec/custom_facts.rb +1 -1
  57. data/libexec/query_resources.rb +1 -1
  58. metadata +23 -17
  59. data/lib/bolt/project_migrator.rb +0 -80
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bolt/error'
4
4
  require 'bolt/util'
5
+ require 'bolt/validator'
5
6
  require 'bolt/config/transport/options'
6
7
 
7
8
  module Bolt
@@ -90,6 +91,14 @@ module Bolt
90
91
  self::OPTIONS
91
92
  end
92
93
 
94
+ def self.schema
95
+ {
96
+ type: Hash,
97
+ properties: self::TRANSPORT_OPTIONS.slice(*self::OPTIONS),
98
+ _plugin: true
99
+ }
100
+ end
101
+
93
102
  private def defaults
94
103
  unless defined? self.class::DEFAULTS
95
104
  raise NotImplementedError,
@@ -116,25 +125,7 @@ module Bolt
116
125
 
117
126
  # Validation defaults to just asserting the option types
118
127
  private def validate
119
- assert_type
120
- end
121
-
122
- # Validates that each option is the correct type. Types are loaded from the TRANSPORT_OPTIONS hash.
123
- private def assert_type
124
- @config.each_pair do |opt, val|
125
- types = Array(TRANSPORT_OPTIONS.dig(opt, :type)).compact
126
-
127
- next if val.nil? || types.empty? || types.include?(val.class)
128
-
129
- # Ruby doesn't have a Boolean class, so add it to the types list if TrueClass or FalseClass
130
- # are present.
131
- if types.include?(TrueClass) || types.include?(FalseClass)
132
- types = types - [TrueClass, FalseClass] + ['Boolean']
133
- end
134
-
135
- raise Bolt::ValidationError,
136
- "#{opt} must be of type #{types.join(', ')}; received #{val.class} #{val.inspect} "
137
- end
128
+ Bolt::Validator.new.validate(@config.compact, self.class.schema)
138
129
  end
139
130
  end
140
131
  end
@@ -8,6 +8,7 @@ module Bolt
8
8
  module Transport
9
9
  class Local < Base
10
10
  WINDOWS_OPTIONS = %w[
11
+ bundled-ruby
11
12
  cleanup
12
13
  interpreters
13
14
  tmpdir
@@ -29,13 +30,6 @@ module Bolt
29
30
  if @config['interpreters']
30
31
  @config['interpreters'] = normalize_interpreters(@config['interpreters'])
31
32
  end
32
-
33
- if (run_as_cmd = @config['run-as-command'])
34
- unless run_as_cmd.all? { |n| n.is_a?(String) }
35
- raise Bolt::ValidationError,
36
- "run-as-command must be an Array of Strings, received #{run_as_cmd.class} #{run_as_cmd.inspect}"
37
- end
38
- end
39
33
  end
40
34
  end
41
35
  end
@@ -6,73 +6,8 @@ module Bolt
6
6
  module Options
7
7
  LOGIN_SHELLS = %w[sh bash zsh dash ksh powershell].freeze
8
8
 
9
- # The following constants define the various configuration options available to Bolt's
10
- # transports. Each constant is a hash where keys are the configuration option and values
11
- # are the option's definition. These options are used in multiple locations:
12
- #
13
- # - Automatic type validation when loading and setting configuration
14
- # - Generating reference documentation for configuration files
15
- # - Generating JSON schemas for configuration files
16
- #
17
- # Data includes keys defined by JSON Schema Draft 07 as well as some metadata used
18
- # by Bolt to generate documentation. The following keys are used:
19
- #
20
- # :description String A detailed description of the option and what it does. This
21
- # field is used in both documentation and the JSON schemas,
22
- # and should provide as much detail as possible, including
23
- # links to relevant documentation.
24
- #
25
- # :type Class The expected type of a value. These should be Ruby classes,
26
- # as this field is used to perform automatic type validation.
27
- # If an option can accept more than one type, this should be
28
- # an array of types. Boolean values should set :type to
29
- # [TrueClass, FalseClass], as Ruby does not have a single
30
- # Boolean class.
31
- #
32
- # :items Hash A definition hash for items in an array. Similar to values
33
- # for top-level options, items can have a :description, :type,
34
- # or any other key in this list.
35
- #
36
- # :uniqueItems Boolean Whether or not an array should contain only unique items.
37
- #
38
- # :properties Hash A hash where keys are sub-options and values are definitions
39
- # for the sub-option. Similar to values for top-level options,
40
- # properties can have a :description, :type, or any other key
41
- # in this list.
42
- #
43
- # :additionalProperties A variation of the :properties key, where the hash is a
44
- # Hash definition for any properties not specified in :properties.
45
- # This can be used to permit arbitrary sub-options, such as
46
- # logs for the 'log' option.
47
- #
48
- # :propertyNames Hash A hash that defines the properties that an option's property
49
- # names must adhere to.
50
- #
51
- # :required Array An array of properties that are required for options that
52
- # accept Hash values.
53
- #
54
- # :minimum Integer The minimum integer value for an option.
55
- #
56
- # :enum Array An array of values that the option recognizes.
57
- #
58
- # :pattern String A JSON regex pattern that the option's vaue should match.
59
- #
60
- # :format String Requires that a string value matches a format defined by the
61
- # JSON Schema draft.
62
- #
63
- # :_plugin Boolean Whether the option accepts a plugin reference. This is used
64
- # when generating the JSON schemas to determine whether or not
65
- # to include a reference to the _plugin definition. If :_plugin
66
- # is set to true, the script that generates JSON schemas will
67
- # automatically recurse through the :items and :properties keys
68
- # and add plugin references if applicable.
69
- #
70
- # :_example Any An example value for the option. This is used to generate
71
- # reference documentation for configuration files.
72
- #
73
- # :_default Any The documented default value for the option. This is only
74
- # used to generate reference documentation for configuration
75
- # files and is not used by Bolt to actually set default values.
9
+ # Definitions used to validate config options.
10
+ # https://github.com/puppetlabs/bolt/blob/main/schemas/README.md
76
11
  TRANSPORT_OPTIONS = {
77
12
  "basic-auth-only" => {
78
13
  type: [TrueClass, FalseClass],
@@ -81,6 +16,13 @@ module Bolt
81
16
  _default: false,
82
17
  _example: true
83
18
  },
19
+ "bundled-ruby" => {
20
+ description: "Whether to use the Ruby bundled with Bolt packages for local targets.",
21
+ type: [TrueClass, FalseClass],
22
+ _plugin: false,
23
+ _example: true,
24
+ _default: false
25
+ },
84
26
  "cacert" => {
85
27
  type: String,
86
28
  description: "The path to the CA certificate.",
@@ -201,7 +143,8 @@ module Bolt
201
143
  "`task.py`) and the extension is case sensitive. When a target's name is `localhost`, "\
202
144
  "Ruby tasks run with the Bolt Ruby interpreter by default.",
203
145
  additionalProperties: {
204
- type: String
146
+ type: String,
147
+ _plugin: false
205
148
  },
206
149
  propertyNames: {
207
150
  pattern: "^.?[a-zA-Z0-9]+$"
@@ -73,6 +73,14 @@ module Bolt
73
73
  %w[ssh-command native-ssh].concat(OPTIONS)
74
74
  end
75
75
 
76
+ def self.schema
77
+ {
78
+ type: Hash,
79
+ properties: self::TRANSPORT_OPTIONS.slice(*(self::OPTIONS + self::NATIVE_OPTIONS)),
80
+ _plugin: true
81
+ }
82
+ end
83
+
76
84
  private def filter(unfiltered)
77
85
  # Because we filter before merging config together it's impossible to
78
86
  # know whether both ssh-command *and* native-ssh will be specified
@@ -87,12 +95,6 @@ module Bolt
87
95
  super
88
96
 
89
97
  if (key_opt = @config['private-key'])
90
- unless key_opt.instance_of?(String) || (key_opt.instance_of?(Hash) && key_opt.include?('key-data'))
91
- raise Bolt::ValidationError,
92
- "private-key option must be a path to a private key file or a Hash containing the 'key-data', "\
93
- "received #{key_opt.class} #{key_opt}"
94
- end
95
-
96
98
  if key_opt.instance_of?(String)
97
99
  @config['private-key'] = File.expand_path(key_opt, @project)
98
100
 
@@ -109,19 +111,6 @@ module Bolt
109
111
  @config['interpreters'] = normalize_interpreters(@config['interpreters'])
110
112
  end
111
113
 
112
- if @config['login-shell'] && !LOGIN_SHELLS.include?(@config['login-shell'])
113
- raise Bolt::ValidationError,
114
- "Unsupported login-shell #{@config['login-shell']}. Supported shells are #{LOGIN_SHELLS.join(', ')}"
115
- end
116
-
117
- %w[encryption-algorithms host-key-algorithms kex-algorithms mac-algorithms run-as-command].each do |opt|
118
- next unless @config.key?(opt)
119
- unless @config[opt].all? { |n| n.is_a?(String) }
120
- raise Bolt::ValidationError,
121
- "#{opt} must be an Array of Strings, received #{@config[opt].inspect}"
122
- end
123
- end
124
-
125
114
  if @config['login-shell'] == 'powershell'
126
115
  %w[tty run-as].each do |key|
127
116
  if @config[key]
@@ -256,10 +256,10 @@ module Bolt
256
256
  @logger.trace { "Failed to submit analytics event: #{e.message}" }
257
257
  end
258
258
 
259
- def with_node_logging(description, batch)
260
- @logger.info("#{description} on #{batch.map(&:safe_name)}")
259
+ def with_node_logging(description, batch, log_level = :info)
260
+ @logger.send(log_level, "#{description} on #{batch.map(&:safe_name)}")
261
261
  result = yield
262
- @logger.info(result.to_json)
262
+ @logger.send(log_level, result.to_json)
263
263
  result
264
264
  end
265
265
 
@@ -289,32 +289,20 @@ module Bolt
289
289
  end
290
290
  end
291
291
 
292
- def run_task(targets, task, arguments, options = {}, position = [])
292
+ def run_task(targets, task, arguments, options = {}, position = [], log_level = :info)
293
293
  description = options.fetch(:description, "task #{task.name}")
294
294
  log_action(description, targets) do
295
295
  options[:run_as] = run_as if run_as && !options.key?(:run_as)
296
296
  arguments['_task'] = task.name
297
297
 
298
298
  batch_execute(targets) do |transport, batch|
299
- with_node_logging("Running task #{task.name} with '#{arguments.to_json}'", batch) do
299
+ with_node_logging("Running task #{task.name} with '#{arguments.to_json}'", batch, log_level) do
300
300
  transport.batch_task(batch, task, arguments, options, position, &method(:publish_event))
301
301
  end
302
302
  end
303
303
  end
304
304
  end
305
305
 
306
- def run_task_with_minimal_logging(targets, task, arguments, options = {})
307
- description = options.fetch(:description, "task #{task.name}")
308
- log_action(description, targets) do
309
- options[:run_as] = run_as if run_as && !options.key?(:run_as)
310
- arguments['_task'] = task.name
311
-
312
- batch_execute(targets) do |transport, batch|
313
- transport.batch_task(batch, task, arguments, options, [], &method(:publish_event))
314
- end
315
- end
316
- end
317
-
318
306
  def run_task_with(target_mapping, task, options = {}, position = [])
319
307
  targets = target_mapping.keys
320
308
  description = options.fetch(:description, "task #{task.name}")
@@ -4,13 +4,17 @@ require 'set'
4
4
  require 'bolt/config'
5
5
  require 'bolt/inventory/group'
6
6
  require 'bolt/inventory/inventory'
7
+ require 'bolt/inventory/options'
7
8
  require 'bolt/target'
8
9
  require 'bolt/util'
9
10
  require 'bolt/plugin'
11
+ require 'bolt/validator'
10
12
  require 'yaml'
11
13
 
12
14
  module Bolt
13
15
  class Inventory
16
+ include Bolt::Inventory::Options
17
+
14
18
  ENVIRONMENT_VAR = 'BOLT_INVENTORY'
15
19
 
16
20
  class ValidationError < Bolt::Error
@@ -45,11 +49,25 @@ module Bolt
45
49
  end
46
50
  end
47
51
 
52
+ # Builds the schema used by the validator.
53
+ #
54
+ def self.schema
55
+ schema = {
56
+ type: Hash,
57
+ properties: OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
58
+ definitions: DEFINITIONS
59
+ }
60
+
61
+ schema[:definitions]['config'][:properties] = Bolt::Config.transport_definitions
62
+ schema
63
+ end
64
+
48
65
  def self.from_config(config, plugins)
49
66
  logger = Bolt::Logger.logger(self)
50
67
 
51
68
  if ENV.include?(ENVIRONMENT_VAR)
52
69
  begin
70
+ source = ENVIRONMENT_VAR
53
71
  data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
54
72
  raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
55
73
  logger.debug("Loaded inventory from environment variable #{ENVIRONMENT_VAR}")
@@ -57,9 +75,11 @@ module Bolt
57
75
  raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
58
76
  end
59
77
  elsif config.inventoryfile
78
+ source = config.inventoryfile
60
79
  data = Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
61
80
  logger.debug("Loaded inventory from #{config.inventoryfile}")
62
81
  else
82
+ source = config.default_inventoryfile
63
83
  data = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
64
84
 
65
85
  if config.default_inventoryfile.exist?
@@ -74,6 +94,11 @@ module Bolt
74
94
  t.resolve(plugins) unless t.resolved?
75
95
  end
76
96
 
97
+ Bolt::Validator.new.tap do |validator|
98
+ validator.validate(data, schema, source)
99
+ validator.warnings.each { |warning| logger.warn(warning) }
100
+ end
101
+
77
102
  inventory = create_version(data, config.transport, config.transports, plugins)
78
103
  inventory.validate
79
104
  inventory
@@ -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