bolt 2.37.0 → 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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/lib/bolt/cli.rb +60 -23
- data/lib/bolt/config.rb +60 -18
- data/lib/bolt/config/options.rb +71 -77
- data/lib/bolt/config/transport/base.rb +2 -2
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +9 -67
- data/lib/bolt/inventory.rb +25 -0
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/module_installer.rb +21 -13
- data/lib/bolt/module_installer/resolver.rb +1 -1
- data/lib/bolt/outputter.rb +19 -5
- data/lib/bolt/outputter/human.rb +20 -1
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +12 -1
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +20 -7
- data/lib/bolt/project_manager.rb +2 -0
- data/lib/bolt/project_manager/config_migrator.rb +9 -1
- data/lib/bolt/project_manager/module_migrator.rb +2 -0
- data/lib/bolt/puppetdb/client.rb +8 -0
- data/lib/bolt/shell/powershell.rb +5 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/validator.rb +226 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +26 -26
- metadata +10 -3
- data/lib/bolt/config/validator.rb +0 -231
data/lib/bolt/outputter.rb
CHANGED
@@ -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
|
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -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
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -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?
|
@@ -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?
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
data/lib/bolt/project.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'bolt/config'
|
5
|
-
require 'bolt/
|
5
|
+
require 'bolt/validator'
|
6
6
|
require 'bolt/pal'
|
7
7
|
require 'bolt/module'
|
8
8
|
|
@@ -83,11 +83,7 @@ module Bolt
|
|
83
83
|
|
84
84
|
logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
|
85
85
|
|
86
|
-
|
87
|
-
# with all validation errors.
|
88
|
-
schema = Bolt::Config::OPTIONS.slice(*Bolt::Config::BOLT_PROJECT_OPTIONS)
|
89
|
-
|
90
|
-
Bolt::Config::Validator.new.tap do |validator|
|
86
|
+
Bolt::Validator.new.tap do |validator|
|
91
87
|
validator.validate(data, schema, project_file)
|
92
88
|
|
93
89
|
validator.warnings.each { |warning| logs << { warn: warning } }
|
@@ -100,6 +96,16 @@ module Bolt
|
|
100
96
|
new(data, path, type, logs, deprecations)
|
101
97
|
end
|
102
98
|
|
99
|
+
# Builds the schema for bolt-project.yaml used by the validator.
|
100
|
+
#
|
101
|
+
def self.schema
|
102
|
+
{
|
103
|
+
type: Hash,
|
104
|
+
properties: Bolt::Config::BOLT_PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
|
105
|
+
definitions: Bolt::Config::OPTIONS
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
103
109
|
def initialize(raw_data, path, type = 'option', logs = [], deprecations = [])
|
104
110
|
@path = Pathname.new(path).expand_path
|
105
111
|
@project_file = @path + CONFIG_NAME
|
@@ -196,6 +202,10 @@ module Bolt
|
|
196
202
|
@data['plugin-cache']
|
197
203
|
end
|
198
204
|
|
205
|
+
def module_install
|
206
|
+
@data['module-install']
|
207
|
+
end
|
208
|
+
|
199
209
|
def modules
|
200
210
|
@modules ||= @data['modules']&.map do |mod|
|
201
211
|
if mod.is_a?(String)
|
@@ -217,7 +227,10 @@ module Bolt
|
|
217
227
|
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
218
228
|
"with a built-in Bolt module of the same name."
|
219
229
|
end
|
220
|
-
|
230
|
+
elsif name.nil? &&
|
231
|
+
(File.directory?(plans_path) ||
|
232
|
+
File.directory?(@path + 'tasks') ||
|
233
|
+
File.directory?(@path + 'files'))
|
221
234
|
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
222
235
|
@logs << { warn: message }
|
223
236
|
end
|
data/lib/bolt/project_manager.rb
CHANGED
@@ -103,7 +103,9 @@ module Bolt
|
|
103
103
|
# early here and not initialize the project if the modules cannot be
|
104
104
|
# resolved and installed.
|
105
105
|
if modules
|
106
|
+
@outputter.start_spin
|
106
107
|
Bolt::ModuleInstaller.new(@outputter, @pal).install(modules, puppetfile, moduledir)
|
108
|
+
@outputter.stop_spin
|
107
109
|
end
|
108
110
|
|
109
111
|
data = { 'name' => project_name }
|
@@ -72,7 +72,15 @@ module Bolt
|
|
72
72
|
data = Bolt::Util.read_yaml_hash(project_file, 'config')
|
73
73
|
modified = false
|
74
74
|
|
75
|
-
|
75
|
+
# Keys to update. The first element is the old key, while the second is
|
76
|
+
# the key update it to.
|
77
|
+
to_update = [
|
78
|
+
%w[apply_settings apply-settings],
|
79
|
+
%w[puppetfile module-install],
|
80
|
+
%w[plugin_hooks plugin-hooks]
|
81
|
+
]
|
82
|
+
|
83
|
+
to_update.each do |old, new|
|
76
84
|
next unless data.key?(old)
|
77
85
|
|
78
86
|
if data.key?(new)
|
@@ -61,6 +61,7 @@ module Bolt
|
|
61
61
|
# Create specs to resolve from
|
62
62
|
specs = Bolt::ModuleInstaller::Specs.new(modules.map(&:to_hash))
|
63
63
|
|
64
|
+
@outputter.start_spin
|
64
65
|
# Attempt to resolve dependencies
|
65
66
|
begin
|
66
67
|
@outputter.print_message('')
|
@@ -72,6 +73,7 @@ module Bolt
|
|
72
73
|
end
|
73
74
|
|
74
75
|
migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
|
76
|
+
@outputter.stop_spin
|
75
77
|
|
76
78
|
# Move remaining modules to 'modules'
|
77
79
|
consolidate_modules(modulepath)
|
data/lib/bolt/puppetdb/client.rb
CHANGED
@@ -18,6 +18,7 @@ module Bolt
|
|
18
18
|
def query_certnames(query)
|
19
19
|
return [] unless query
|
20
20
|
|
21
|
+
@logger.debug("Querying certnames")
|
21
22
|
results = make_query(query)
|
22
23
|
|
23
24
|
if results&.first && !results.first&.key?('certname')
|
@@ -34,6 +35,8 @@ module Bolt
|
|
34
35
|
certnames.uniq!
|
35
36
|
name_query = certnames.map { |c| ["=", "certname", c] }
|
36
37
|
name_query.insert(0, "or")
|
38
|
+
|
39
|
+
@logger.debug("Querying certnames")
|
37
40
|
result = make_query(name_query, 'inventory')
|
38
41
|
|
39
42
|
result&.each_with_object({}) do |node, coll|
|
@@ -52,6 +55,8 @@ module Bolt
|
|
52
55
|
facts_query.insert(0, "or")
|
53
56
|
|
54
57
|
query = ['and', name_query, facts_query]
|
58
|
+
|
59
|
+
@logger.debug("Querying certnames")
|
55
60
|
result = make_query(query, 'fact-contents')
|
56
61
|
result.map! { |h| h.delete_if { |k, _v| %w[environment name].include?(k) } }
|
57
62
|
result.group_by { |c| c['certname'] }
|
@@ -63,11 +68,13 @@ module Bolt
|
|
63
68
|
url += "/#{path}" if path
|
64
69
|
|
65
70
|
begin
|
71
|
+
@logger.debug("Sending PuppetDB query to #{url}")
|
66
72
|
response = http_client.post(url, body: body, header: headers)
|
67
73
|
rescue StandardError => e
|
68
74
|
raise Bolt::PuppetDBFailoverError, "Failed to query PuppetDB: #{e}"
|
69
75
|
end
|
70
76
|
|
77
|
+
@logger.debug("Got response code #{response.code} from PuppetDB")
|
71
78
|
if response.code != 200
|
72
79
|
msg = "Failed to query PuppetDB: #{response.body}"
|
73
80
|
if response.code == 400
|
@@ -92,6 +99,7 @@ module Bolt
|
|
92
99
|
return @http if @http
|
93
100
|
# lazy-load expensive gem code
|
94
101
|
require 'httpclient'
|
102
|
+
@logger.trace("Creating HTTP Client")
|
95
103
|
@http = HTTPClient.new
|
96
104
|
@http.ssl_config.set_client_cert_file(@config.cert, @config.key) if @config.cert
|
97
105
|
@http.ssl_config.add_trust_ca(@config.cacert)
|
@@ -193,7 +193,8 @@ module Bolt
|
|
193
193
|
def run_command(command, options = {}, position = [])
|
194
194
|
command = [*env_declarations(options[:env_vars]), command].join("\r\n") if options[:env_vars]
|
195
195
|
|
196
|
-
|
196
|
+
wrap_command = conn.is_a?(Bolt::Transport::Local::Connection)
|
197
|
+
output = execute(command, wrap_command)
|
197
198
|
Bolt::Result.for_command(target,
|
198
199
|
output.stdout.string,
|
199
200
|
output.stderr.string,
|
@@ -284,8 +285,9 @@ module Bolt
|
|
284
285
|
end
|
285
286
|
end
|
286
287
|
|
287
|
-
def execute(command)
|
288
|
-
if conn.max_command_length && command.length > conn.max_command_length
|
288
|
+
def execute(command, wrap_command = false)
|
289
|
+
if (conn.max_command_length && command.length > conn.max_command_length) ||
|
290
|
+
wrap_command
|
289
291
|
return with_tmpdir do |dir|
|
290
292
|
command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
|
291
293
|
script_file = File.join(dir, "#{SecureRandom.uuid}_wrapper.ps1")
|
data/lib/bolt/target.rb
CHANGED
data/lib/bolt/transport/local.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/logger'
|
3
4
|
require 'bolt/transport/simple'
|
4
5
|
|
5
6
|
module Bolt
|
@@ -10,6 +11,18 @@ module Bolt
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def with_connection(target)
|
14
|
+
if target.transport_config['bundled-ruby'] || target.name == 'localhost'
|
15
|
+
target.set_local_defaults
|
16
|
+
end
|
17
|
+
|
18
|
+
if target.name != 'localhost' &&
|
19
|
+
!target.transport_config.key?('bundled-ruby')
|
20
|
+
msg = "The local transport will default to using Bolt's Ruby interpreter and "\
|
21
|
+
"setting the 'puppet-agent' feature in Bolt 3.0. Enable or disable these "\
|
22
|
+
"defaults by setting 'bundled-ruby' in the local transport config."
|
23
|
+
Bolt::Logger.warn_once('local default config', msg)
|
24
|
+
end
|
25
|
+
|
13
26
|
yield Connection.new(target)
|
14
27
|
end
|
15
28
|
end
|
@@ -0,0 +1,226 @@
|
|
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 Validator
|
10
|
+
attr_reader :deprecations, :warnings
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@errors = []
|
14
|
+
@deprecations = []
|
15
|
+
@warnings = []
|
16
|
+
@path = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# This is the entry method for validating data against the schema.
|
20
|
+
#
|
21
|
+
def validate(data, schema, location = nil)
|
22
|
+
@schema = schema
|
23
|
+
@location = location
|
24
|
+
|
25
|
+
validate_value(data, schema)
|
26
|
+
|
27
|
+
raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raises a ValidationError if there are any errors. All error messages
|
31
|
+
# created during validation are concatenated into a single error
|
32
|
+
# message.
|
33
|
+
#
|
34
|
+
private def raise_error
|
35
|
+
return unless @errors.any?
|
36
|
+
|
37
|
+
message = "Invalid configuration"
|
38
|
+
message += " at #{@location}" if @location
|
39
|
+
message += ":\n"
|
40
|
+
message += @errors.map { |error| "\s\s#{error}" }.join("\n")
|
41
|
+
|
42
|
+
raise Bolt::ValidationError, message
|
43
|
+
end
|
44
|
+
|
45
|
+
# Validate an individual value. This performs validation that is
|
46
|
+
# common to all values, including type validation. After validating
|
47
|
+
# the value's type, the value is passed off to an individual
|
48
|
+
# validation method for the value's type.
|
49
|
+
#
|
50
|
+
private def validate_value(value, definition)
|
51
|
+
definition = @schema.dig(:definitions, definition[:_ref]) if definition[:_ref]
|
52
|
+
|
53
|
+
return if plugin_reference?(value, definition)
|
54
|
+
return unless valid_type?(value, definition)
|
55
|
+
|
56
|
+
case value
|
57
|
+
when Hash
|
58
|
+
validate_hash(value, definition)
|
59
|
+
when Array
|
60
|
+
validate_array(value, definition)
|
61
|
+
when String
|
62
|
+
validate_string(value, definition)
|
63
|
+
when Numeric
|
64
|
+
validate_number(value, definition)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Validates a hash value, logging errors for any validations that fail.
|
69
|
+
# This will enumerate each key-value pair in the hash and validate each
|
70
|
+
# value individually.
|
71
|
+
#
|
72
|
+
private def validate_hash(value, definition)
|
73
|
+
properties = definition[:properties] ? definition[:properties].keys : []
|
74
|
+
|
75
|
+
if definition[:properties] && definition[:additionalProperties].nil?
|
76
|
+
validate_keys(value.keys, properties)
|
77
|
+
end
|
78
|
+
|
79
|
+
if definition[:required] && (definition[:required] - value.keys).any?
|
80
|
+
missing = definition[:required] - value.keys
|
81
|
+
@errors << "Value at '#{path}' is missing required keys #{missing.join(', ')}"
|
82
|
+
end
|
83
|
+
|
84
|
+
value.each_pair do |key, val|
|
85
|
+
@path.push(key)
|
86
|
+
|
87
|
+
if properties.include?(key)
|
88
|
+
check_deprecated(key, definition[:properties][key])
|
89
|
+
validate_value(val, definition[:properties][key])
|
90
|
+
elsif definition[:additionalProperties]
|
91
|
+
validate_value(val, definition[:additionalProperties])
|
92
|
+
end
|
93
|
+
ensure
|
94
|
+
@path.pop
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Validates an array value, logging errors for any validations that fail.
|
99
|
+
# This will enumerate the items in the array and validate each item
|
100
|
+
# individually.
|
101
|
+
#
|
102
|
+
private def validate_array(value, definition)
|
103
|
+
if definition[:uniqueItems] && value.size != value.uniq.size
|
104
|
+
@errors << "Value at '#{path}' must not include duplicate elements"
|
105
|
+
return
|
106
|
+
end
|
107
|
+
|
108
|
+
return unless definition.key?(:items)
|
109
|
+
|
110
|
+
value.each_with_index do |item, index|
|
111
|
+
@path.push(index)
|
112
|
+
validate_value(item, definition[:items])
|
113
|
+
ensure
|
114
|
+
@path.pop
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Validates a string value, logging errors for any validations that fail.
|
119
|
+
#
|
120
|
+
private def validate_string(value, definition)
|
121
|
+
if definition.key?(:enum) && !definition[:enum].include?(value)
|
122
|
+
message = "Value at '#{path}' must be "
|
123
|
+
message += "one of " if definition[:enum].count > 1
|
124
|
+
message += definition[:enum].join(', ')
|
125
|
+
multitype_error(message, value, definition)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Validates a numeric value, logging errors for any validations that fail.
|
130
|
+
#
|
131
|
+
private def validate_number(value, definition)
|
132
|
+
if definition.key?(:minimum) && value < definition[:minimum]
|
133
|
+
@errors << "Value at '#{path}' must be a minimum of #{definition[:minimum]}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if definition.key?(:maximum) && value > definition[:maximum]
|
137
|
+
@errors << "Value at '#{path}' must be a maximum of #{definition[:maximum]}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Adds warnings for unknown config options.
|
142
|
+
#
|
143
|
+
private def validate_keys(keys, known_keys)
|
144
|
+
(keys - known_keys).each do |key|
|
145
|
+
message = "Unknown option '#{key}'"
|
146
|
+
message += " at '#{path}'" if @path.any?
|
147
|
+
message += " at #{@location}" if @location
|
148
|
+
message += "."
|
149
|
+
@warnings << message
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Adds a warning if the given option is deprecated.
|
154
|
+
#
|
155
|
+
private def check_deprecated(key, definition)
|
156
|
+
definition = @schema.dig(:definitions, definition[:_ref]) if definition[:_ref]
|
157
|
+
|
158
|
+
if definition.key?(:_deprecation)
|
159
|
+
message = "Option '#{path}' "
|
160
|
+
message += "at #{@location} " if @location
|
161
|
+
message += "is deprecated. #{definition[:_deprecation]}"
|
162
|
+
@deprecations << { option: key, message: message }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns true if a value is a plugin reference. This also validates whether
|
167
|
+
# a value can be a plugin reference in the first place. If the value is a
|
168
|
+
# plugin reference but cannot be one according to the schema, then this will
|
169
|
+
# log an error.
|
170
|
+
#
|
171
|
+
private def plugin_reference?(value, definition)
|
172
|
+
if value.is_a?(Hash) && value.key?('_plugin')
|
173
|
+
unless definition[:_plugin]
|
174
|
+
@errors << "Value at '#{path}' is a plugin reference, which is unsupported at "\
|
175
|
+
"this location"
|
176
|
+
end
|
177
|
+
|
178
|
+
true
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Asserts the type for each option against the type specified in the schema
|
185
|
+
# definition. The schema definition can specify multiple valid types, so the
|
186
|
+
# value needs to only match one of the types to be valid. Returns early if
|
187
|
+
# there is no type in the definition (in practice this shouldn't happen, but
|
188
|
+
# this will safeguard against any dev mistakes).
|
189
|
+
#
|
190
|
+
private def valid_type?(value, definition)
|
191
|
+
return unless definition.key?(:type)
|
192
|
+
|
193
|
+
types = Array(definition[:type])
|
194
|
+
|
195
|
+
if types.include?(value.class)
|
196
|
+
true
|
197
|
+
else
|
198
|
+
if types.include?(TrueClass) || types.include?(FalseClass)
|
199
|
+
types = types - [TrueClass, FalseClass] + ['Boolean']
|
200
|
+
end
|
201
|
+
|
202
|
+
@errors << "Value at '#{path}' must be of type #{types.join(' or ')}"
|
203
|
+
|
204
|
+
false
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Adds an error that includes additional helpful information for values
|
209
|
+
# that accept multiple types.
|
210
|
+
#
|
211
|
+
private def multitype_error(message, value, definition)
|
212
|
+
if Array(definition[:type]).count > 1
|
213
|
+
types = Array(definition[:type]) - [value.class]
|
214
|
+
message += " or must be of type #{types.join(' or ')}"
|
215
|
+
end
|
216
|
+
|
217
|
+
@errors << message
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns the formatted path for the key.
|
221
|
+
#
|
222
|
+
private def path
|
223
|
+
@path.join('.')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|