bolt 2.33.2 → 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/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 +48 -16
- data/lib/bolt/cli.rb +154 -249
- data/lib/bolt/config.rb +188 -55
- data/lib/bolt/config/options.rb +147 -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 +10 -68
- data/lib/bolt/config/transport/ssh.rb +8 -14
- data/lib/bolt/error.rb +33 -3
- data/lib/bolt/executor.rb +92 -6
- data/lib/bolt/inventory.rb +25 -0
- data/lib/bolt/inventory/group.rb +2 -1
- 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 +41 -10
- 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 +19 -7
- data/lib/bolt/pal/yaml_plan.rb +7 -0
- data/lib/bolt/plan_creator.rb +160 -0
- data/lib/bolt/plugin.rb +42 -13
- 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} +51 -5
- data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
- 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 +9 -8
- data/lib/bolt/rerun.rb +1 -5
- data/lib/bolt/shell/bash.rb +8 -2
- data/lib/bolt/shell/powershell.rb +22 -4
- 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/remote.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
- data/lib/bolt/util.rb +41 -7
- data/lib/bolt/validator.rb +226 -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/file_cache.rb +2 -0
- 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 -13
- 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
|
-
|
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
|
-
#
|
10
|
-
#
|
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.",
|
@@ -357,7 +299,7 @@ module Bolt
|
|
357
299
|
description: "The URL of the host used for API requests.",
|
358
300
|
format: "uri",
|
359
301
|
_plugin: true,
|
360
|
-
_example: "https://api.example.com
|
302
|
+
_example: "https://api.example.com:8143"
|
361
303
|
},
|
362
304
|
"shell-command" => {
|
363
305
|
type: String,
|
@@ -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]
|
data/lib/bolt/error.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/util'
|
4
|
+
|
3
5
|
module Bolt
|
4
6
|
class Error < RuntimeError
|
5
7
|
attr_reader :kind, :details, :issue_code, :error_code
|
@@ -37,13 +39,17 @@ module Bolt
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def self.unknown_task(task)
|
40
|
-
|
41
|
-
|
42
|
+
command = Bolt::Util.powershell? ? "Get-BoltTask" : "bolt task show"
|
43
|
+
new(
|
44
|
+
"Could not find a task named '#{task}'. For a list of available tasks, run '#{command}'.",
|
45
|
+
'bolt/unknown-task'
|
46
|
+
)
|
42
47
|
end
|
43
48
|
|
44
49
|
def self.unknown_plan(plan)
|
50
|
+
command = Bolt::Util.powershell? ? "Get-BoltPlan" : "bolt plan show"
|
45
51
|
new(
|
46
|
-
"Could not find a plan named
|
52
|
+
"Could not find a plan named '#{plan}'. For a list of available plans, run '#{command}'.",
|
47
53
|
'bolt/unknown-plan'
|
48
54
|
)
|
49
55
|
end
|
@@ -84,6 +90,20 @@ module Bolt
|
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
93
|
+
class ParallelFailure < Bolt::Error
|
94
|
+
def initialize(results, failed_indices)
|
95
|
+
details = {
|
96
|
+
'action' => 'parallelize',
|
97
|
+
'failed_indices' => failed_indices,
|
98
|
+
'results' => results
|
99
|
+
}
|
100
|
+
message = "Plan aborted: parallel block failed on #{failed_indices.length} target"
|
101
|
+
message += "s" unless failed_indices.length == 1
|
102
|
+
super(message, 'bolt/parallel-failure', details)
|
103
|
+
@error_code = 2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
87
107
|
class PlanFailure < Error
|
88
108
|
def initialize(*args)
|
89
109
|
super(*args)
|
@@ -125,6 +145,16 @@ module Bolt
|
|
125
145
|
end
|
126
146
|
end
|
127
147
|
|
148
|
+
class InvalidParallelResult < Error
|
149
|
+
def initialize(result_str, file, line)
|
150
|
+
super("Parallel block returned an invalid result: #{result_str}",
|
151
|
+
'bolt/invalid-plan-result',
|
152
|
+
{ 'file' => file,
|
153
|
+
'line' => line,
|
154
|
+
'result_string' => result_str })
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
128
158
|
class ValidationError < Bolt::Error
|
129
159
|
def initialize(msg)
|
130
160
|
super(msg, 'bolt/validation-error')
|
data/lib/bolt/executor.rb
CHANGED
@@ -17,6 +17,7 @@ require 'bolt/transport/orch'
|
|
17
17
|
require 'bolt/transport/local'
|
18
18
|
require 'bolt/transport/docker'
|
19
19
|
require 'bolt/transport/remote'
|
20
|
+
require 'bolt/yarn'
|
20
21
|
|
21
22
|
module Bolt
|
22
23
|
TRANSPORTS = {
|
@@ -29,7 +30,7 @@ module Bolt
|
|
29
30
|
}.freeze
|
30
31
|
|
31
32
|
class Executor
|
32
|
-
attr_reader :noop, :transports
|
33
|
+
attr_reader :noop, :transports, :in_parallel
|
33
34
|
attr_accessor :run_as
|
34
35
|
|
35
36
|
def initialize(concurrency = 1,
|
@@ -60,6 +61,7 @@ module Bolt
|
|
60
61
|
|
61
62
|
@noop = noop
|
62
63
|
@run_as = nil
|
64
|
+
@in_parallel = false
|
63
65
|
@pool = if concurrency > 0
|
64
66
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
65
67
|
else
|
@@ -84,6 +86,14 @@ module Bolt
|
|
84
86
|
self
|
85
87
|
end
|
86
88
|
|
89
|
+
def unsubscribe(subscriber, types = nil)
|
90
|
+
if types.nil? || types.sort == @subscribers[subscriber]&.sort
|
91
|
+
@subscribers.delete(subscriber)
|
92
|
+
elsif @subscribers[subscriber].is_a?(Array)
|
93
|
+
@subscribers[subscriber] = @subscribers[subscriber] - types
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
87
97
|
def publish_event(event)
|
88
98
|
@subscribers.each do |subscriber, types|
|
89
99
|
# If types isn't set or if the subscriber is subscribed to
|
@@ -246,10 +256,10 @@ module Bolt
|
|
246
256
|
@logger.trace { "Failed to submit analytics event: #{e.message}" }
|
247
257
|
end
|
248
258
|
|
249
|
-
def with_node_logging(description, batch)
|
250
|
-
@logger.
|
259
|
+
def with_node_logging(description, batch, log_level = :info)
|
260
|
+
@logger.send(log_level, "#{description} on #{batch.map(&:safe_name)}")
|
251
261
|
result = yield
|
252
|
-
@logger.
|
262
|
+
@logger.send(log_level, result.to_json)
|
253
263
|
result
|
254
264
|
end
|
255
265
|
|
@@ -279,14 +289,14 @@ module Bolt
|
|
279
289
|
end
|
280
290
|
end
|
281
291
|
|
282
|
-
def run_task(targets, task, arguments, options = {}, position = [])
|
292
|
+
def run_task(targets, task, arguments, options = {}, position = [], log_level = :info)
|
283
293
|
description = options.fetch(:description, "task #{task.name}")
|
284
294
|
log_action(description, targets) do
|
285
295
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
286
296
|
arguments['_task'] = task.name
|
287
297
|
|
288
298
|
batch_execute(targets) do |transport, batch|
|
289
|
-
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
|
290
300
|
transport.batch_task(batch, task, arguments, options, position, &method(:publish_event))
|
291
301
|
end
|
292
302
|
end
|
@@ -347,6 +357,82 @@ module Bolt
|
|
347
357
|
plan.call_by_name_with_scope(scope, params, true)
|
348
358
|
end
|
349
359
|
|
360
|
+
def create_yarn(scope, block, object, index)
|
361
|
+
fiber = Fiber.new do
|
362
|
+
# Create the new scope
|
363
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
364
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
365
|
+
|
366
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
367
|
+
current_scope = scope.effective_symtable(true)
|
368
|
+
until current_scope.nil?
|
369
|
+
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
370
|
+
current_scope = current_scope.parent
|
371
|
+
end
|
372
|
+
newscope.push_ephemerals([local])
|
373
|
+
|
374
|
+
begin
|
375
|
+
result = catch(:return) do
|
376
|
+
args = { block.parameters[0][1].to_s => object }
|
377
|
+
block.closure.call_by_name_with_scope(newscope, args, true)
|
378
|
+
end
|
379
|
+
|
380
|
+
# If we got a return from the block, get it's value
|
381
|
+
# Otherwise the result is the last line from the block
|
382
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
383
|
+
|
384
|
+
# Validate the result is a PlanResult
|
385
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
386
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
387
|
+
end
|
388
|
+
|
389
|
+
result
|
390
|
+
rescue Puppet::PreformattedError => e
|
391
|
+
if e.cause.is_a?(Bolt::Error)
|
392
|
+
e.cause
|
393
|
+
else
|
394
|
+
raise e
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
Bolt::Yarn.new(fiber, index)
|
400
|
+
end
|
401
|
+
|
402
|
+
def handle_event(event)
|
403
|
+
case event[:type]
|
404
|
+
when :node_result
|
405
|
+
@thread_completed = true
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def round_robin(skein)
|
410
|
+
subscribe(self, [:node_result])
|
411
|
+
results = Array.new(skein.length)
|
412
|
+
@in_parallel = true
|
413
|
+
|
414
|
+
until skein.empty?
|
415
|
+
@thread_completed = false
|
416
|
+
r = nil
|
417
|
+
|
418
|
+
skein.each do |yarn|
|
419
|
+
if yarn.alive?
|
420
|
+
r = yarn.resume
|
421
|
+
else
|
422
|
+
results[yarn.index] = yarn.value
|
423
|
+
skein.delete(yarn)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
next unless r == 'unfinished'
|
428
|
+
sleep(0.1) until @thread_completed || skein.empty?
|
429
|
+
end
|
430
|
+
|
431
|
+
@in_parallel = false
|
432
|
+
unsubscribe(self, [:node_result])
|
433
|
+
results
|
434
|
+
end
|
435
|
+
|
350
436
|
class TimeoutError < RuntimeError; end
|
351
437
|
|
352
438
|
def wait_until_available(targets,
|
data/lib/bolt/inventory.rb
CHANGED
@@ -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
|