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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
- data/lib/bolt/analytics.rb +27 -8
- data/lib/bolt/apply_result.rb +3 -3
- data/lib/bolt/bolt_option_parser.rb +45 -18
- data/lib/bolt/cli.rb +98 -116
- data/lib/bolt/config.rb +184 -80
- data/lib/bolt/config/options.rb +148 -87
- data/lib/bolt/config/transport/base.rb +10 -19
- data/lib/bolt/config/transport/local.rb +1 -7
- data/lib/bolt/config/transport/options.rb +12 -69
- data/lib/bolt/config/transport/ssh.rb +8 -19
- data/lib/bolt/error.rb +24 -0
- data/lib/bolt/executor.rb +92 -18
- data/lib/bolt/inventory.rb +25 -0
- data/lib/bolt/inventory/group.rb +0 -8
- 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 +22 -3
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +13 -2
- data/lib/bolt/pal.rb +18 -6
- data/lib/bolt/pal/yaml_plan.rb +7 -0
- data/lib/bolt/plugin.rb +41 -12
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/module.rb +4 -4
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +59 -40
- data/lib/bolt/project_manager.rb +201 -0
- data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +49 -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} +5 -3
- data/lib/bolt/puppetdb/client.rb +11 -2
- data/lib/bolt/puppetdb/config.rb +4 -3
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +8 -2
- data/lib/bolt/shell/powershell.rb +21 -3
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/transport/orch.rb +0 -5
- data/lib/bolt/transport/orch/connection.rb +10 -3
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +36 -7
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt/yarn.rb +23 -0
- data/lib/bolt_server/base_config.rb +3 -1
- data/lib/bolt_server/config.rb +3 -1
- data/lib/bolt_server/plugin.rb +13 -0
- data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
- data/lib/bolt_server/schemas/connect-data.json +22 -0
- data/lib/bolt_server/schemas/partials/task.json +2 -2
- data/lib/bolt_server/transport_app.rb +82 -23
- data/lib/bolt_spec/plans/mock_executor.rb +4 -1
- data/libexec/apply_catalog.rb +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +22 -14
- data/lib/bolt/project_migrator.rb +0 -80
@@ -14,6 +14,22 @@ module Bolt
|
|
14
14
|
extensions = [target.options['extensions'] || []].flatten.map { |ext| ext[0] == '.' ? ext : '.' + ext }
|
15
15
|
extensions += target.options['interpreters'].keys if target.options['interpreters']
|
16
16
|
@extensions = DEFAULT_EXTENSIONS + extensions
|
17
|
+
validate_ps_version
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_ps_version
|
21
|
+
version = execute("$PSVersionTable.PSVersion.Major").stdout.string.chomp
|
22
|
+
if !version.empty? && version.to_i < 3
|
23
|
+
# This lets us know how many targets have Powershell 2, and lets the
|
24
|
+
# user know how many targets they have with PS2
|
25
|
+
msg = "Detected PowerShell 2 on one or more targets.\nPowerShell 2 "\
|
26
|
+
"is deprecated, and support will be removed in Bolt 3.0. See "\
|
27
|
+
"bolt-debug.log or run with '--log-level debug' to see the full "\
|
28
|
+
"list of targets with PowerShell 2."
|
29
|
+
|
30
|
+
Bolt::Logger.deprecation_warning("PowerShell 2", msg)
|
31
|
+
@logger.debug("Detected PowerShell 2 on #{target}.")
|
32
|
+
end
|
17
33
|
end
|
18
34
|
|
19
35
|
def provided_features
|
@@ -177,7 +193,8 @@ module Bolt
|
|
177
193
|
def run_command(command, options = {}, position = [])
|
178
194
|
command = [*env_declarations(options[:env_vars]), command].join("\r\n") if options[:env_vars]
|
179
195
|
|
180
|
-
|
196
|
+
wrap_command = conn.is_a?(Bolt::Transport::Local::Connection)
|
197
|
+
output = execute(command, wrap_command)
|
181
198
|
Bolt::Result.for_command(target,
|
182
199
|
output.stdout.string,
|
183
200
|
output.stderr.string,
|
@@ -268,8 +285,9 @@ module Bolt
|
|
268
285
|
end
|
269
286
|
end
|
270
287
|
|
271
|
-
def execute(command)
|
272
|
-
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
|
273
291
|
return with_tmpdir do |dir|
|
274
292
|
command += "\r\nif (!$?) { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
|
275
293
|
script_file = File.join(dir, "#{SecureRandom.uuid}_wrapper.ps1")
|
data/lib/bolt/target.rb
CHANGED
data/lib/bolt/task/run.rb
CHANGED
@@ -42,7 +42,7 @@ module Bolt
|
|
42
42
|
if targets.empty?
|
43
43
|
Bolt::ResultSet.new([])
|
44
44
|
else
|
45
|
-
result = executor.
|
45
|
+
result = executor.run_task(targets, task, params, options, [], :trace)
|
46
46
|
|
47
47
|
if !result.ok && !options[:catch_errors]
|
48
48
|
raise Bolt::RunFailure.new(result, 'run_task', task.name)
|
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
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -10,11 +10,6 @@ require 'bolt/transport/orch/connection'
|
|
10
10
|
module Bolt
|
11
11
|
module Transport
|
12
12
|
class Orch < Base
|
13
|
-
CONF_FILE = if ENV['HOME'].nil?
|
14
|
-
'/etc/puppetlabs/client-tools/orchestrator.conf'
|
15
|
-
else
|
16
|
-
File.expand_path('~/.puppetlabs/client-tools/orchestrator.conf')
|
17
|
-
end
|
18
13
|
BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
|
19
14
|
BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
|
20
15
|
BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
|
@@ -17,13 +17,20 @@ module Bolt
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def initialize(opts, plan_context, logger)
|
20
|
+
require 'addressable/uri'
|
21
|
+
|
20
22
|
@logger = logger
|
21
23
|
@key = self.class.get_key(opts)
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
client_opts = opts.slice('token-file', 'cacert', 'job-poll-interval', 'job-poll-timeout')
|
25
|
+
|
26
|
+
if opts['service-url']
|
27
|
+
uri = Addressable::URI.parse(opts['service-url'])
|
28
|
+
uri&.port ||= 8143
|
29
|
+
client_opts['service-url'] = uri.to_s
|
25
30
|
end
|
31
|
+
|
26
32
|
client_opts['User-Agent'] = "Bolt/#{VERSION}"
|
33
|
+
|
27
34
|
%w[token-file cacert].each do |f|
|
28
35
|
client_opts[f] = File.expand_path(client_opts[f]) if client_opts[f]
|
29
36
|
end
|
@@ -12,8 +12,12 @@ module Bolt
|
|
12
12
|
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
|
13
13
|
|
14
14
|
@target = target
|
15
|
-
|
16
|
-
|
15
|
+
begin
|
16
|
+
ssh_config = Net::SSH::Config.for(target.host)
|
17
|
+
@user = @target.user || ssh_config[:user] || Etc.getlogin
|
18
|
+
rescue StandardError
|
19
|
+
@user = @target.user || Etc.getlogin
|
20
|
+
end
|
17
21
|
@logger = Bolt::Logger.logger(self)
|
18
22
|
end
|
19
23
|
|
data/lib/bolt/util.rb
CHANGED
@@ -22,6 +22,28 @@ module Bolt
|
|
22
22
|
raise Bolt::FileError.new("Error attempting to read #{file}: #{e}", file)
|
23
23
|
end
|
24
24
|
|
25
|
+
def read_json_file(path, filename)
|
26
|
+
require 'json'
|
27
|
+
|
28
|
+
logger = Bolt::Logger.logger(self)
|
29
|
+
path = File.expand_path(path)
|
30
|
+
content = JSON.parse(File.read(path))
|
31
|
+
logger.trace("Loaded #{filename} from #{path}")
|
32
|
+
content
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
raise Bolt::FileError.new("Could not read #{filename} file at #{path}", path)
|
35
|
+
rescue JSON::ParserError => e
|
36
|
+
msg = "Unable to parse #{filename} file at #{path} as JSON: #{e.message}"
|
37
|
+
raise Bolt::FileError.new(msg, path)
|
38
|
+
rescue IOError, SystemCallError => e
|
39
|
+
raise Bolt::FileError.new("Could not read #{filename} file at #{path}\n#{e.message}",
|
40
|
+
path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_optional_json_file(path, file_name)
|
44
|
+
File.exist?(path) && !File.zero?(path) ? read_yaml_hash(path, file_name) : {}
|
45
|
+
end
|
46
|
+
|
25
47
|
def read_yaml_hash(path, file_name)
|
26
48
|
require 'yaml'
|
27
49
|
|
@@ -29,19 +51,26 @@ module Bolt
|
|
29
51
|
path = File.expand_path(path)
|
30
52
|
content = File.open(path, "r:UTF-8") { |f| YAML.safe_load(f.read) } || {}
|
31
53
|
unless content.is_a?(Hash)
|
32
|
-
|
33
|
-
|
54
|
+
raise Bolt::FileError.new(
|
55
|
+
"Invalid content for #{file_name} file at #{path}\nContent should be a Hash or empty, "\
|
56
|
+
"not #{content.class}",
|
57
|
+
path
|
58
|
+
)
|
34
59
|
end
|
35
60
|
logger.trace("Loaded #{file_name} from #{path}")
|
36
61
|
content
|
37
62
|
rescue Errno::ENOENT
|
38
|
-
raise Bolt::FileError.new("Could not read #{file_name} file
|
63
|
+
raise Bolt::FileError.new("Could not read #{file_name} file at #{path}", path)
|
64
|
+
rescue Psych::SyntaxError => e
|
65
|
+
raise Bolt::FileError.new("Could not parse #{file_name} file at #{path}, line #{e.line}, "\
|
66
|
+
"column #{e.column}\n#{e.problem}",
|
67
|
+
path)
|
39
68
|
rescue Psych::Exception => e
|
40
|
-
raise Bolt::FileError.new("Could not parse #{file_name} file
|
41
|
-
|
69
|
+
raise Bolt::FileError.new("Could not parse #{file_name} file at #{path}\n#{e.message}",
|
70
|
+
path)
|
42
71
|
rescue IOError, SystemCallError => e
|
43
|
-
raise Bolt::FileError.new("Could not read #{file_name} file
|
44
|
-
|
72
|
+
raise Bolt::FileError.new("Could not read #{file_name} file at #{path}\n#{e.message}",
|
73
|
+
path)
|
45
74
|
end
|
46
75
|
|
47
76
|
def read_optional_yaml_hash(path, file_name)
|
@@ -0,0 +1,227 @@
|
|
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, plugin_supported = false)
|
51
|
+
definition = @schema.dig(:definitions, definition[:_ref]) if definition[:_ref]
|
52
|
+
plugin_supported = definition[:_plugin] if definition.key?(:_plugin)
|
53
|
+
|
54
|
+
return if plugin_reference?(value, plugin_supported)
|
55
|
+
return unless valid_type?(value, definition)
|
56
|
+
|
57
|
+
case value
|
58
|
+
when Hash
|
59
|
+
validate_hash(value, definition, plugin_supported)
|
60
|
+
when Array
|
61
|
+
validate_array(value, definition, plugin_supported)
|
62
|
+
when String
|
63
|
+
validate_string(value, definition)
|
64
|
+
when Numeric
|
65
|
+
validate_number(value, definition)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Validates a hash value, logging errors for any validations that fail.
|
70
|
+
# This will enumerate each key-value pair in the hash and validate each
|
71
|
+
# value individually.
|
72
|
+
#
|
73
|
+
private def validate_hash(value, definition, plugin_supported)
|
74
|
+
properties = definition[:properties] ? definition[:properties].keys : []
|
75
|
+
|
76
|
+
if definition[:properties] && definition[:additionalProperties].nil?
|
77
|
+
validate_keys(value.keys, properties)
|
78
|
+
end
|
79
|
+
|
80
|
+
if definition[:required] && (definition[:required] - value.keys).any?
|
81
|
+
missing = definition[:required] - value.keys
|
82
|
+
@errors << "Value at '#{path}' is missing required keys #{missing.join(', ')}"
|
83
|
+
end
|
84
|
+
|
85
|
+
value.each_pair do |key, val|
|
86
|
+
@path.push(key)
|
87
|
+
|
88
|
+
if properties.include?(key)
|
89
|
+
check_deprecated(key, definition[:properties][key])
|
90
|
+
validate_value(val, definition[:properties][key], plugin_supported)
|
91
|
+
elsif definition[:additionalProperties].is_a?(Hash)
|
92
|
+
validate_value(val, definition[:additionalProperties], plugin_supported)
|
93
|
+
end
|
94
|
+
ensure
|
95
|
+
@path.pop
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Validates an array value, logging errors for any validations that fail.
|
100
|
+
# This will enumerate the items in the array and validate each item
|
101
|
+
# individually.
|
102
|
+
#
|
103
|
+
private def validate_array(value, definition, plugin_supported)
|
104
|
+
if definition[:uniqueItems] && value.size != value.uniq.size
|
105
|
+
@errors << "Value at '#{path}' must not include duplicate elements"
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
return unless definition.key?(:items)
|
110
|
+
|
111
|
+
value.each_with_index do |item, index|
|
112
|
+
@path.push(index)
|
113
|
+
validate_value(item, definition[:items], plugin_supported)
|
114
|
+
ensure
|
115
|
+
@path.pop
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Validates a string value, logging errors for any validations that fail.
|
120
|
+
#
|
121
|
+
private def validate_string(value, definition)
|
122
|
+
if definition.key?(:enum) && !definition[:enum].include?(value)
|
123
|
+
message = "Value at '#{path}' must be "
|
124
|
+
message += "one of " if definition[:enum].count > 1
|
125
|
+
message += definition[:enum].join(', ')
|
126
|
+
multitype_error(message, value, definition)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Validates a numeric value, logging errors for any validations that fail.
|
131
|
+
#
|
132
|
+
private def validate_number(value, definition)
|
133
|
+
if definition.key?(:minimum) && value < definition[:minimum]
|
134
|
+
@errors << "Value at '#{path}' must be a minimum of #{definition[:minimum]}"
|
135
|
+
end
|
136
|
+
|
137
|
+
if definition.key?(:maximum) && value > definition[:maximum]
|
138
|
+
@errors << "Value at '#{path}' must be a maximum of #{definition[:maximum]}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Adds warnings for unknown config options.
|
143
|
+
#
|
144
|
+
private def validate_keys(keys, known_keys)
|
145
|
+
(keys - known_keys).each do |key|
|
146
|
+
message = "Unknown option '#{key}'"
|
147
|
+
message += " at '#{path}'" if @path.any?
|
148
|
+
message += " at #{@location}" if @location
|
149
|
+
message += "."
|
150
|
+
@warnings << message
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Adds a warning if the given option is deprecated.
|
155
|
+
#
|
156
|
+
private def check_deprecated(key, definition)
|
157
|
+
definition = @schema.dig(:definitions, definition[:_ref]) if definition[:_ref]
|
158
|
+
|
159
|
+
if definition.key?(:_deprecation)
|
160
|
+
message = "Option '#{path}' "
|
161
|
+
message += "at #{@location} " if @location
|
162
|
+
message += "is deprecated. #{definition[:_deprecation]}"
|
163
|
+
@deprecations << { option: key, message: message }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns true if a value is a plugin reference. This also validates whether
|
168
|
+
# a value can be a plugin reference in the first place. If the value is a
|
169
|
+
# plugin reference but cannot be one according to the schema, then this will
|
170
|
+
# log an error.
|
171
|
+
#
|
172
|
+
private def plugin_reference?(value, plugin_supported)
|
173
|
+
if value.is_a?(Hash) && value.key?('_plugin')
|
174
|
+
unless plugin_supported
|
175
|
+
@errors << "Value at '#{path}' is a plugin reference, which is unsupported at "\
|
176
|
+
"this location"
|
177
|
+
end
|
178
|
+
|
179
|
+
true
|
180
|
+
else
|
181
|
+
false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Asserts the type for each option against the type specified in the schema
|
186
|
+
# definition. The schema definition can specify multiple valid types, so the
|
187
|
+
# value needs to only match one of the types to be valid. Returns early if
|
188
|
+
# there is no type in the definition (in practice this shouldn't happen, but
|
189
|
+
# this will safeguard against any dev mistakes).
|
190
|
+
#
|
191
|
+
private def valid_type?(value, definition)
|
192
|
+
return unless definition.key?(:type)
|
193
|
+
|
194
|
+
types = Array(definition[:type])
|
195
|
+
|
196
|
+
if types.include?(value.class)
|
197
|
+
true
|
198
|
+
else
|
199
|
+
if types.include?(TrueClass) || types.include?(FalseClass)
|
200
|
+
types = types - [TrueClass, FalseClass] + ['Boolean']
|
201
|
+
end
|
202
|
+
|
203
|
+
@errors << "Value at '#{path}' must be of type #{types.join(' or ')}"
|
204
|
+
|
205
|
+
false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Adds an error that includes additional helpful information for values
|
210
|
+
# that accept multiple types.
|
211
|
+
#
|
212
|
+
private def multitype_error(message, value, definition)
|
213
|
+
if Array(definition[:type]).count > 1
|
214
|
+
types = Array(definition[:type]) - [value.class]
|
215
|
+
message += " or must be of type #{types.join(' or ')}"
|
216
|
+
end
|
217
|
+
|
218
|
+
@errors << message
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns the formatted path for the key.
|
222
|
+
#
|
223
|
+
private def path
|
224
|
+
@path.join('.')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt/yarn.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Yarn
|
7
|
+
attr_reader :fiber, :value, :index
|
8
|
+
|
9
|
+
def initialize(fiber, index)
|
10
|
+
@fiber = fiber
|
11
|
+
@index = index
|
12
|
+
@value = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def alive?
|
16
|
+
fiber.alive?
|
17
|
+
end
|
18
|
+
|
19
|
+
def resume
|
20
|
+
@value = fiber.resume
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|