bolt 2.33.1 → 2.37.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 +95 -227
- data/lib/bolt/config.rb +145 -54
- data/lib/bolt/config/options.rb +76 -10
- data/lib/bolt/config/transport/base.rb +10 -19
- data/lib/bolt/config/transport/local.rb +0 -7
- data/lib/bolt/config/transport/options.rb +1 -1
- data/lib/bolt/config/transport/ssh.rb +8 -14
- data/lib/bolt/config/validator.rb +231 -0
- data/lib/bolt/error.rb +33 -3
- data/lib/bolt/executor.rb +92 -6
- data/lib/bolt/inventory/group.rb +2 -1
- data/lib/bolt/module_installer.rb +1 -1
- data/lib/bolt/module_installer/specs/forge_spec.rb +5 -4
- data/lib/bolt/module_installer/specs/git_spec.rb +4 -3
- data/lib/bolt/outputter/human.rb +21 -9
- data/lib/bolt/outputter/rainbow.rb +1 -1
- 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/project.rb +46 -40
- data/lib/bolt/project_manager.rb +199 -0
- data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +43 -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} +3 -3
- data/lib/bolt/puppetdb/client.rb +3 -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 +17 -1
- data/lib/bolt/task/run.rb +1 -1
- 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/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 +72 -13
- 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 +15 -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/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
|
@@ -357,7 +357,7 @@ module Bolt
|
|
357
357
|
description: "The URL of the host used for API requests.",
|
358
358
|
format: "uri",
|
359
359
|
_plugin: true,
|
360
|
-
_example: "https://api.example.com
|
360
|
+
_example: "https://api.example.com:8143"
|
361
361
|
},
|
362
362
|
"shell-command" => {
|
363
363
|
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]
|
@@ -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
|
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,
|