bolt 2.35.0 → 2.36.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.
- checksums.yaml +4 -4
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
- data/lib/bolt/analytics.rb +27 -8
- data/lib/bolt/apply_result.rb +3 -3
- data/lib/bolt/bolt_option_parser.rb +38 -15
- data/lib/bolt/cli.rb +13 -87
- data/lib/bolt/config.rb +131 -52
- data/lib/bolt/config/options.rb +42 -4
- data/lib/bolt/config/transport/base.rb +10 -19
- data/lib/bolt/config/transport/local.rb +0 -7
- data/lib/bolt/config/transport/ssh.rb +8 -14
- data/lib/bolt/config/validator.rb +231 -0
- data/lib/bolt/executor.rb +5 -17
- data/lib/bolt/outputter/rainbow.rb +1 -1
- data/lib/bolt/plugin.rb +0 -7
- data/lib/bolt/project.rb +30 -36
- data/lib/bolt/project_manager.rb +199 -0
- data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +41 -4
- data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +3 -3
- data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
- data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +3 -3
- data/lib/bolt/puppetdb/config.rb +1 -2
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +14 -7
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +3 -1
- data/lib/bolt_server/config.rb +3 -1
- data/lib/bolt_server/schemas/partials/task.json +2 -2
- data/lib/bolt_server/transport_app.rb +5 -5
- data/libexec/apply_catalog.rb +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +8 -13
- data/lib/bolt/project_migrator.rb +0 -80
data/lib/bolt/config/options.rb
CHANGED
@@ -104,6 +104,21 @@ module Bolt
|
|
104
104
|
# files and is not used by Bolt to actually set default values.
|
105
105
|
OPTIONS = {
|
106
106
|
"apply_settings" => {
|
107
|
+
description: "A map of Puppet settings to use when applying Puppet code using the `apply` "\
|
108
|
+
"plan function or the `bolt apply` command.",
|
109
|
+
type: Hash,
|
110
|
+
properties: {
|
111
|
+
"show_diff" => {
|
112
|
+
description: "Whether to log and report a contextual diff.",
|
113
|
+
type: [TrueClass, FalseClass],
|
114
|
+
_example: true,
|
115
|
+
_default: false
|
116
|
+
}
|
117
|
+
},
|
118
|
+
_plugin: false,
|
119
|
+
_deprecation: "This option will be removed in Bolt 3.0. Use `apply-settings` instead."
|
120
|
+
},
|
121
|
+
"apply-settings" => {
|
107
122
|
description: "A map of Puppet settings to use when applying Puppet code using the `apply` "\
|
108
123
|
"plan function or the `bolt apply` command.",
|
109
124
|
type: Hash,
|
@@ -169,6 +184,9 @@ module Bolt
|
|
169
184
|
"files](inventory_file_v2.md).",
|
170
185
|
type: String,
|
171
186
|
_plugin: false,
|
187
|
+
_deprecation: "This option will be removed in Bolt 3.0. Use the `--inventoryfile` command-line option "\
|
188
|
+
"to use a non-default inventory file or move the file contents to `inventory.yaml` in the "\
|
189
|
+
"project directory.",
|
172
190
|
_example: "~/.puppetlabs/bolt/inventory.yaml",
|
173
191
|
_default: "project/inventory.yaml"
|
174
192
|
},
|
@@ -183,7 +201,8 @@ module Bolt
|
|
183
201
|
properties: {
|
184
202
|
"console" => {
|
185
203
|
description: "Configuration for logs output to the console.",
|
186
|
-
type: Hash,
|
204
|
+
type: [String, Hash],
|
205
|
+
enum: ['disable'],
|
187
206
|
properties: {
|
188
207
|
"level" => {
|
189
208
|
description: "The type of information to log.",
|
@@ -276,8 +295,8 @@ module Bolt
|
|
276
295
|
},
|
277
296
|
"name" => {
|
278
297
|
description: "The name of the Bolt project. When this option is configured, the project is considered a "\
|
279
|
-
"[Bolt project](
|
280
|
-
"
|
298
|
+
"[Bolt project](projects.md), allowing Bolt to load content from the project directory "\
|
299
|
+
"as though it were a module.",
|
281
300
|
type: String,
|
282
301
|
_plugin: false,
|
283
302
|
_example: "myproject"
|
@@ -294,6 +313,16 @@ module Bolt
|
|
294
313
|
_example: ["myproject", "myproject::foo", "myproject::bar", "myproject::deploy::*"]
|
295
314
|
},
|
296
315
|
"plugin_hooks" => {
|
316
|
+
description: "A map of [plugin hooks](writing_plugins.md#hooks) and which plugins a hook should use. "\
|
317
|
+
"The only configurable plugin hook is `puppet_library`, which can use two possible plugins: "\
|
318
|
+
"[`puppet_agent`](https://github.com/puppetlabs/puppetlabs-puppet_agent#puppet_agentinstall) "\
|
319
|
+
"and [`task`](using_plugins.md#task).",
|
320
|
+
type: Hash,
|
321
|
+
_plugin: true,
|
322
|
+
_example: { "puppet_library" => { "plugin" => "puppet_agent", "version" => "6.15.0", "_run_as" => "root" } },
|
323
|
+
_deprecation: "This option will be removed in Bolt 3.0. Use `plugin-hooks` instead."
|
324
|
+
},
|
325
|
+
"plugin-hooks" => {
|
297
326
|
description: "A map of [plugin hooks](writing_plugins.md#hooks) and which plugins a hook should use. "\
|
298
327
|
"The only configurable plugin hook is `puppet_library`, which can use two possible plugins: "\
|
299
328
|
"[`puppet_agent`](https://github.com/puppetlabs/puppetlabs-puppet_agent#puppet_agentinstall) "\
|
@@ -307,7 +336,11 @@ module Bolt
|
|
307
336
|
"its value is a map of configuration data. Configurable options are specified by the plugin. "\
|
308
337
|
"Read more about configuring plugins in [Using plugins](using_plugins.md#configuring-plugins).",
|
309
338
|
type: Hash,
|
310
|
-
|
339
|
+
additionalProperties: {
|
340
|
+
type: Hash,
|
341
|
+
_plugin: true
|
342
|
+
},
|
343
|
+
_plugin: false,
|
311
344
|
_example: { "pkcs7" => { "keysize" => 1024 } }
|
312
345
|
},
|
313
346
|
"puppetdb" => {
|
@@ -480,6 +513,7 @@ module Bolt
|
|
480
513
|
|
481
514
|
# Options that are available in a bolt.yaml file
|
482
515
|
BOLT_OPTIONS = %w[
|
516
|
+
apply-settings
|
483
517
|
apply_settings
|
484
518
|
color
|
485
519
|
compile-concurrency
|
@@ -489,6 +523,7 @@ module Bolt
|
|
489
523
|
inventoryfile
|
490
524
|
log
|
491
525
|
modulepath
|
526
|
+
plugin-hooks
|
492
527
|
plugin_hooks
|
493
528
|
plugins
|
494
529
|
puppetdb
|
@@ -505,6 +540,7 @@ module Bolt
|
|
505
540
|
format
|
506
541
|
inventory-config
|
507
542
|
log
|
543
|
+
plugin-hooks
|
508
544
|
plugin_hooks
|
509
545
|
plugins
|
510
546
|
puppetdb
|
@@ -514,6 +550,7 @@ module Bolt
|
|
514
550
|
|
515
551
|
# Options that are available in a bolt-project.yaml file
|
516
552
|
BOLT_PROJECT_OPTIONS = %w[
|
553
|
+
apply-settings
|
517
554
|
apply_settings
|
518
555
|
color
|
519
556
|
compile-concurrency
|
@@ -526,6 +563,7 @@ module Bolt
|
|
526
563
|
modules
|
527
564
|
name
|
528
565
|
plans
|
566
|
+
plugin-hooks
|
529
567
|
plugin_hooks
|
530
568
|
plugins
|
531
569
|
puppetdb
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'bolt/error'
|
4
4
|
require 'bolt/util'
|
5
|
+
require 'bolt/config/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
|
-
|
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::Config::Validator.new.validate(@config.compact, self.class.schema.fetch(:properties))
|
138
129
|
end
|
139
130
|
end
|
140
131
|
end
|
@@ -29,13 +29,6 @@ module Bolt
|
|
29
29
|
if @config['interpreters']
|
30
30
|
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
|
31
31
|
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
32
|
end
|
40
33
|
end
|
41
34
|
end
|
@@ -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
|
|
@@ -114,14 +116,6 @@ module Bolt
|
|
114
116
|
"Unsupported login-shell #{@config['login-shell']}. Supported shells are #{LOGIN_SHELLS.join(', ')}"
|
115
117
|
end
|
116
118
|
|
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
119
|
if @config['login-shell'] == 'powershell'
|
126
120
|
%w[tty run-as].each do |key|
|
127
121
|
if @config[key]
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
|
5
|
+
# This class validates config against a schema, raising an error that includes
|
6
|
+
# details about any invalid configuration.
|
7
|
+
#
|
8
|
+
module Bolt
|
9
|
+
class Config
|
10
|
+
class Validator
|
11
|
+
attr_reader :deprecations, :warnings
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@errors = []
|
15
|
+
@deprecations = []
|
16
|
+
@warnings = []
|
17
|
+
@path = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# This is the entry method for validating data against the schema.
|
21
|
+
# It loops over each key-value pair in the data hash and validates
|
22
|
+
# the value against the relevant schema definition.
|
23
|
+
#
|
24
|
+
def validate(data, schema, location = nil)
|
25
|
+
@location = location
|
26
|
+
|
27
|
+
validate_keys(data.keys, schema.keys)
|
28
|
+
|
29
|
+
data.each_pair do |key, value|
|
30
|
+
next unless schema.key?(key)
|
31
|
+
|
32
|
+
@path.push(key)
|
33
|
+
|
34
|
+
check_deprecated(key, schema[key], location)
|
35
|
+
validate_value(value, schema[key])
|
36
|
+
ensure
|
37
|
+
@path.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
raise_error
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds a warning if the given option is deprecated.
|
44
|
+
#
|
45
|
+
def check_deprecated(key, definition, location)
|
46
|
+
if definition.key?(:_deprecation)
|
47
|
+
message = "Option '#{path}' "
|
48
|
+
message += "at #{location} " if location
|
49
|
+
message += "is deprecated. #{definition[:_deprecation]}"
|
50
|
+
@deprecations << { option: key, message: message }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Raises a ValidationError if there are any errors. All error messages
|
55
|
+
# created during validation are concatenated into a single error
|
56
|
+
# message.
|
57
|
+
#
|
58
|
+
private def raise_error
|
59
|
+
return unless @errors.any?
|
60
|
+
|
61
|
+
message = "Invalid configuration"
|
62
|
+
message += " at #{@location}" if @location
|
63
|
+
message += ":\n"
|
64
|
+
message += @errors.map { |error| "\s\s#{error}" }.join("\n")
|
65
|
+
|
66
|
+
raise Bolt::ValidationError, message
|
67
|
+
end
|
68
|
+
|
69
|
+
# Validate an individual value. This performs validation that is
|
70
|
+
# common to all values, including type validation. After validating
|
71
|
+
# the value's type, the value is passed off to an individual
|
72
|
+
# validation method for the value's type.
|
73
|
+
#
|
74
|
+
private def validate_value(value, definition)
|
75
|
+
return if plugin_reference?(value, definition)
|
76
|
+
return unless valid_type?(value, definition)
|
77
|
+
|
78
|
+
case value
|
79
|
+
when Hash
|
80
|
+
validate_hash(value, definition)
|
81
|
+
when Array
|
82
|
+
validate_array(value, definition)
|
83
|
+
when String
|
84
|
+
validate_string(value, definition)
|
85
|
+
when Numeric
|
86
|
+
validate_number(value, definition)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Validates a hash value, logging errors for any validations that fail.
|
91
|
+
# This will enumerate each key-value pair in the hash and validate each
|
92
|
+
# value individually.
|
93
|
+
#
|
94
|
+
private def validate_hash(value, definition)
|
95
|
+
properties = definition[:properties] ? definition[:properties].keys : []
|
96
|
+
|
97
|
+
if definition[:properties] && definition[:additionalProperties].nil?
|
98
|
+
validate_keys(value.keys, properties)
|
99
|
+
end
|
100
|
+
|
101
|
+
if definition[:required] && (definition[:required] - value.keys).any?
|
102
|
+
missing = definition[:required] - value.keys
|
103
|
+
@errors << "Value at '#{path}' is missing required keys #{missing.join(', ')}"
|
104
|
+
end
|
105
|
+
|
106
|
+
value.each_pair do |key, val|
|
107
|
+
@path.push(key)
|
108
|
+
|
109
|
+
if properties.include?(key)
|
110
|
+
validate_value(val, definition[:properties][key])
|
111
|
+
elsif definition[:additionalProperties]
|
112
|
+
validate_value(val, definition[:additionalProperties])
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
@path.pop
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Validates an array value, logging errors for any validations that fail.
|
120
|
+
# This will enumerate the items in the array and validate each item
|
121
|
+
# individually.
|
122
|
+
#
|
123
|
+
private def validate_array(value, definition)
|
124
|
+
if definition[:uniqueItems] && value.size != value.uniq.size
|
125
|
+
@errors << "Value at '#{path}' must not include duplicate elements"
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
return unless definition.key?(:items)
|
130
|
+
|
131
|
+
value.each_with_index do |item, index|
|
132
|
+
@path.push(index)
|
133
|
+
validate_value(item, definition[:items])
|
134
|
+
ensure
|
135
|
+
@path.pop
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Validates a string value, logging errors for any validations that fail.
|
140
|
+
#
|
141
|
+
private def validate_string(value, definition)
|
142
|
+
if definition.key?(:enum) && !definition[:enum].include?(value)
|
143
|
+
message = "Value at '#{path}' must be "
|
144
|
+
message += "one of " if definition[:enum].count > 1
|
145
|
+
message += definition[:enum].join(', ')
|
146
|
+
multitype_error(message, value, definition)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Validates a numeric value, logging errors for any validations that fail.
|
151
|
+
#
|
152
|
+
private def validate_number(value, definition)
|
153
|
+
if definition.key?(:minimum) && value < definition[:minimum]
|
154
|
+
@errors << "Value at '#{path}' must be a minimum of #{definition[:minimum]}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Adds warnings for unknown config options.
|
159
|
+
#
|
160
|
+
private def validate_keys(keys, known_keys)
|
161
|
+
(keys - known_keys).each do |key|
|
162
|
+
message = "Unknown option '#{key}'"
|
163
|
+
message += " at '#{path}'" if @path.any?
|
164
|
+
message += " at #{@location}" if @location
|
165
|
+
message += "."
|
166
|
+
@warnings << message
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns true if a value is a plugin reference. This also validates whether
|
171
|
+
# a value can be a plugin reference in the first place. If the value is a
|
172
|
+
# plugin reference but cannot be one according to the schema, then this will
|
173
|
+
# log an error.
|
174
|
+
#
|
175
|
+
private def plugin_reference?(value, definition)
|
176
|
+
if value.is_a?(Hash) && value.key?('_plugin')
|
177
|
+
unless definition[:_plugin]
|
178
|
+
@errors << "Value at '#{path}' is a plugin reference, which is unsupported at "\
|
179
|
+
"this location"
|
180
|
+
end
|
181
|
+
|
182
|
+
true
|
183
|
+
else
|
184
|
+
false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Asserts the type for each option against the type specified in the schema
|
189
|
+
# definition. The schema definition can specify multiple valid types, so the
|
190
|
+
# value needs to only match one of the types to be valid. Returns early if
|
191
|
+
# there is no type in the definition (in practice this shouldn't happen, but
|
192
|
+
# this will safeguard against any dev mistakes).
|
193
|
+
#
|
194
|
+
private def valid_type?(value, definition)
|
195
|
+
return unless definition.key?(:type)
|
196
|
+
|
197
|
+
types = Array(definition[:type])
|
198
|
+
|
199
|
+
if types.include?(value.class)
|
200
|
+
true
|
201
|
+
else
|
202
|
+
if types.include?(TrueClass) || types.include?(FalseClass)
|
203
|
+
types = types - [TrueClass, FalseClass] + ['Boolean']
|
204
|
+
end
|
205
|
+
|
206
|
+
@errors << "Value at '#{path}' must be of type #{types.join(' or ')}"
|
207
|
+
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Adds an error that includes additional helpful information for values
|
213
|
+
# that accept multiple types.
|
214
|
+
#
|
215
|
+
private def multitype_error(message, value, definition)
|
216
|
+
if Array(definition[:type]).count > 1
|
217
|
+
types = Array(definition[:type]) - [value.class]
|
218
|
+
message += " or must be of type #{types.join(' or ')}"
|
219
|
+
end
|
220
|
+
|
221
|
+
@errors << message
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns the formatted path for the key.
|
225
|
+
#
|
226
|
+
private def path
|
227
|
+
@path.join('.')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|