bolt 3.8.1 → 3.9.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/future.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +61 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +5 -9
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +28 -13
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +5 -15
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +5 -17
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +5 -17
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +5 -15
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +5 -17
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +91 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/guides/inventory.txt +5 -0
- data/lib/bolt/applicator.rb +3 -2
- data/lib/bolt/config/transport/docker.rb +5 -1
- data/lib/bolt/config/transport/lxd.rb +1 -1
- data/lib/bolt/config/transport/podman.rb +5 -1
- data/lib/bolt/error.rb +11 -1
- data/lib/bolt/executor.rb +51 -72
- data/lib/bolt/fiber_executor.rb +141 -0
- data/lib/bolt/outputter/human.rb +1 -0
- data/lib/bolt/pal.rb +19 -3
- data/lib/bolt/plan_future.rb +66 -0
- data/lib/bolt/transport/docker/connection.rb +5 -2
- data/lib/bolt/transport/lxd/connection.rb +4 -0
- data/lib/bolt/transport/podman/connection.rb +4 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +42 -45
- metadata +7 -3
- data/lib/bolt/yarn.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c6c3fbb5bdf8e8b53493a029b46560616f2ea310092823f863de14ad1fc836b
|
4
|
+
data.tar.gz: f4a3193539da70d75ff36f39a126d6f56710c91e2ff54c465a1477685b6fb19a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6c8837f2d6ef2e67ac2be71d150637bcb90af95207812ec22c81557841279a5f148f2348ca6e0d137e42bfd4058009c59439996a208dc6c5c72568a8842e498
|
7
|
+
data.tar.gz: 712b2be84c5ff24749ef1e010d439e05057c48d648394f7d321611d7576a006277d476adb38c6892fa73b380a0288726023643c8b2849a00f9ac164e06fe805f
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The [`background()` plan function](plan_functions.md#background) returns a
|
4
|
+
# `Future` object, which can be passed to the [`wait()` plan
|
5
|
+
# function](plan_functions.md#wait) to block on the result of the backgrounded
|
6
|
+
# code block.
|
7
|
+
#
|
8
|
+
# @!method state
|
9
|
+
# Either 'running' if the Future is still executing, 'done' if the Future
|
10
|
+
# finished successfully, or 'error' if the Future finished with an error.
|
11
|
+
#
|
12
|
+
Puppet::DataTypes.create_type('Future') do
|
13
|
+
interface <<-PUPPET
|
14
|
+
attributes => {},
|
15
|
+
functions => {
|
16
|
+
state => Callable[[], Enum['running', 'done', 'error']],
|
17
|
+
}
|
18
|
+
PUPPET
|
19
|
+
|
20
|
+
load_file('bolt/plan_future')
|
21
|
+
|
22
|
+
# Needed for Puppet to recognize Bolt::Result as a Puppet object when deserializing
|
23
|
+
Bolt::PlanFuture.include(Puppet::Pops::Types::PuppetObject)
|
24
|
+
implementation_class Bolt::PlanFuture
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Starts a block of code in parallel with the main plan without blocking.
|
4
|
+
# Returns a Future object.
|
5
|
+
#
|
6
|
+
# > **Note:** Not available in apply block
|
7
|
+
Puppet::Functions.create_function(:background, Puppet::Functions::InternalFunction) do
|
8
|
+
# Starts a block of code in parallel with the main plan without blocking.
|
9
|
+
# Returns a Future object.
|
10
|
+
# @param name An optional name for legible logs.
|
11
|
+
# @param block The code block to run in the background.
|
12
|
+
# @return A Bolt Future object
|
13
|
+
# @example Start a long-running process
|
14
|
+
# background() || {
|
15
|
+
# run_task('superlong::task', $targets)
|
16
|
+
# }
|
17
|
+
# run_command("echo 'Continue immediately'", $targets)
|
18
|
+
dispatch :background do
|
19
|
+
scope_param
|
20
|
+
optional_param 'String[1]', :name
|
21
|
+
block_param 'Callable[0, 0]', :block
|
22
|
+
return_type 'Future'
|
23
|
+
end
|
24
|
+
|
25
|
+
def background(scope, name = nil, &block)
|
26
|
+
unless Puppet[:tasks]
|
27
|
+
raise Puppet::ParseErrorWithIssue
|
28
|
+
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'background')
|
29
|
+
end
|
30
|
+
|
31
|
+
executor = Puppet.lookup(:bolt_executor)
|
32
|
+
executor.report_function_call(self.class.name)
|
33
|
+
|
34
|
+
executor.create_future(scope: scope, name: name) do |newscope|
|
35
|
+
# Catch 'return' calls inside the block
|
36
|
+
result = catch(:return) do
|
37
|
+
# Execute the block. Individual plan steps in the block will yield
|
38
|
+
# the Fiber if they haven't finished, so all this needs to do is run
|
39
|
+
# the block.
|
40
|
+
block.closure.call_by_name_with_scope(newscope, {}, true)
|
41
|
+
end
|
42
|
+
|
43
|
+
# If we got a return from the block, get its value
|
44
|
+
# Otherwise the result is the last line from the block
|
45
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
46
|
+
|
47
|
+
# Validate the result is a PlanResult
|
48
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
49
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
50
|
+
end
|
51
|
+
|
52
|
+
result
|
53
|
+
rescue Puppet::PreformattedError => e
|
54
|
+
if e.cause.is_a?(Bolt::Error)
|
55
|
+
e.cause
|
56
|
+
else
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -112,17 +112,13 @@ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFun
|
|
112
112
|
call_function('debug', "Simulating file download of '#{source}' - no targets given - no action taken")
|
113
113
|
Bolt::ResultSet.new([])
|
114
114
|
else
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
|
115
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
116
|
+
r = if executor.in_parallel?
|
117
|
+
executor.run_in_thread do
|
118
|
+
executor.download_file(targets, source, destination, options, file_line)
|
120
119
|
end
|
121
|
-
|
122
|
-
Fiber.yield('unfinished') while future.incomplete?
|
123
|
-
future.value || future.reason
|
124
120
|
else
|
125
|
-
executor.download_file(targets, source, destination, options,
|
121
|
+
executor.download_file(targets, source, destination, options, file_line)
|
126
122
|
end
|
127
123
|
|
128
124
|
if !r.ok && !options[:catch_errors]
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'bolt/yarn'
|
4
|
-
|
5
3
|
# Map a code block onto an array, where each array element executes in parallel.
|
6
4
|
# This function is experimental.
|
7
5
|
#
|
@@ -35,21 +33,38 @@ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunct
|
|
35
33
|
executor = Puppet.lookup(:bolt_executor)
|
36
34
|
executor.report_function_call(self.class.name)
|
37
35
|
|
38
|
-
|
39
|
-
executor.
|
40
|
-
|
36
|
+
futures = data.map do |object|
|
37
|
+
executor.create_future(scope: scope) do |newscope|
|
38
|
+
# Catch 'return' calls inside the block
|
39
|
+
result = catch(:return) do
|
40
|
+
# Add the object to the block parameters
|
41
|
+
args = { block.parameters[0][1].to_s => object }
|
42
|
+
# Execute the block. Individual plan steps in the block will yield
|
43
|
+
# the Fiber if they haven't finished, so all this needs to do is run
|
44
|
+
# the block.
|
45
|
+
block.closure.call_by_name_with_scope(newscope, args, true)
|
46
|
+
end
|
41
47
|
|
42
|
-
|
48
|
+
# If we got a return from the block, get its value
|
49
|
+
# Otherwise the result is the last line from the block
|
50
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
43
51
|
|
44
|
-
|
45
|
-
|
46
|
-
|
52
|
+
# Validate the result is a PlanResult
|
53
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
54
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
55
|
+
end
|
47
56
|
|
48
|
-
|
49
|
-
|
50
|
-
|
57
|
+
result
|
58
|
+
rescue Puppet::PreformattedError => e
|
59
|
+
if e.cause.is_a?(Bolt::Error)
|
60
|
+
e.cause
|
61
|
+
else
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
end
|
51
65
|
end
|
52
66
|
|
53
|
-
|
67
|
+
# We may eventually want parallelize to accept a timeout
|
68
|
+
executor.wait(futures)
|
54
69
|
end
|
55
70
|
end
|
@@ -87,23 +87,13 @@ Puppet::Functions.create_function(:run_command) do
|
|
87
87
|
call_function('debug', "Simulating run_command('#{command}') - no targets given - no action taken")
|
88
88
|
Bolt::ResultSet.new([])
|
89
89
|
else
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
executor.run_command(targets,
|
95
|
-
command,
|
96
|
-
options,
|
97
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
90
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
91
|
+
r = if executor.in_parallel?
|
92
|
+
executor.run_in_thread do
|
93
|
+
executor.run_command(targets, command, options, file_line)
|
98
94
|
end
|
99
|
-
|
100
|
-
Fiber.yield('unfinished') while future.incomplete?
|
101
|
-
future.value || future.reason
|
102
95
|
else
|
103
|
-
executor.run_command(targets,
|
104
|
-
command,
|
105
|
-
options,
|
106
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
96
|
+
executor.run_command(targets, command, options, file_line)
|
107
97
|
end
|
108
98
|
|
109
99
|
if !r.ok && !options[:catch_errors]
|
@@ -130,25 +130,13 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
|
|
130
130
|
if targets.empty?
|
131
131
|
Bolt::ResultSet.new([])
|
132
132
|
else
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
executor.run_script(targets,
|
138
|
-
found,
|
139
|
-
arguments,
|
140
|
-
options,
|
141
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
133
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
134
|
+
r = if executor.in_parallel?
|
135
|
+
executor.run_in_thread do
|
136
|
+
executor.run_script(targets, found, arguments, options, file_line)
|
142
137
|
end
|
143
|
-
|
144
|
-
Fiber.yield('unfinished') while future.incomplete?
|
145
|
-
future.value || future.reason
|
146
138
|
else
|
147
|
-
executor.run_script(targets,
|
148
|
-
found,
|
149
|
-
arguments,
|
150
|
-
options,
|
151
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
139
|
+
executor.run_script(targets, found, arguments, options, file_line)
|
152
140
|
end
|
153
141
|
|
154
142
|
if !r.ok && !options[:catch_errors]
|
@@ -133,25 +133,13 @@ Puppet::Functions.create_function(:run_task) do
|
|
133
133
|
if targets.empty?
|
134
134
|
Bolt::ResultSet.new([])
|
135
135
|
else
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
executor.run_task(targets,
|
141
|
-
task,
|
142
|
-
params,
|
143
|
-
options,
|
144
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
136
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
137
|
+
result = if executor.in_parallel?
|
138
|
+
executor.run_in_thread do
|
139
|
+
executor.run_task(targets, task, params, options, file_line)
|
145
140
|
end
|
146
|
-
|
147
|
-
Fiber.yield('unfinished') while future.incomplete?
|
148
|
-
future.value || future.reason
|
149
141
|
else
|
150
|
-
executor.run_task(targets,
|
151
|
-
task,
|
152
|
-
params,
|
153
|
-
options,
|
154
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
142
|
+
executor.run_task(targets, task, params, options, file_line)
|
155
143
|
end
|
156
144
|
|
157
145
|
if !result.ok && !options[:catch_errors]
|
@@ -180,23 +180,13 @@ Puppet::Functions.create_function(:run_task_with) do
|
|
180
180
|
else
|
181
181
|
# Combine the results from the task run with any failing results that were
|
182
182
|
# generated earlier when creating the target mapping
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
executor.run_task_with(target_mapping,
|
188
|
-
task,
|
189
|
-
options,
|
190
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
183
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
184
|
+
task_result = if executor.in_parallel?
|
185
|
+
executor.run_in_thread do
|
186
|
+
executor.run_task_with(target_mapping, task, options, file_line)
|
191
187
|
end
|
192
|
-
|
193
|
-
Fiber.yield('unfinished') while future.incomplete?
|
194
|
-
future.value || future.reason
|
195
188
|
else
|
196
|
-
executor.run_task_with(target_mapping,
|
197
|
-
task,
|
198
|
-
options,
|
199
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
189
|
+
executor.run_task_with(target_mapping, task, options, file_line)
|
200
190
|
end
|
201
191
|
result = Bolt::ResultSet.new(task_result.results + error_set)
|
202
192
|
|
@@ -87,25 +87,13 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
|
|
87
87
|
call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
|
88
88
|
Bolt::ResultSet.new([])
|
89
89
|
else
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
executor.upload_file(targets,
|
95
|
-
found,
|
96
|
-
destination,
|
97
|
-
options,
|
98
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
90
|
+
file_line = Puppet::Pops::PuppetStack.top_of_stack
|
91
|
+
r = if executor.in_parallel?
|
92
|
+
executor.run_in_thread do
|
93
|
+
executor.upload_file(targets, found, destination, options, file_line)
|
99
94
|
end
|
100
|
-
|
101
|
-
Fiber.yield('unfinished') while future.incomplete?
|
102
|
-
future.value || future.reason
|
103
95
|
else
|
104
|
-
executor.upload_file(targets,
|
105
|
-
found,
|
106
|
-
destination,
|
107
|
-
options,
|
108
|
-
Puppet::Pops::PuppetStack.top_of_stack)
|
96
|
+
executor.upload_file(targets, found, destination, options, file_line)
|
109
97
|
end
|
110
98
|
if !r.ok && !options[:catch_errors]
|
111
99
|
raise Bolt::RunFailure.new(r, 'upload_file', source)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/logger'
|
4
|
+
require 'bolt/target'
|
5
|
+
|
6
|
+
# Wait for a Future or array of Futures to finish and return results,
|
7
|
+
# optionally with a timeout.
|
8
|
+
#
|
9
|
+
# > **Note:** Not available in apply block
|
10
|
+
Puppet::Functions.create_function(:wait, Puppet::Functions::InternalFunction) do
|
11
|
+
# Wait for Future(s) to finish
|
12
|
+
# @param futures A Bolt Future object or array of Bolt Futures to wait on.
|
13
|
+
# @param options A hash of additional options.
|
14
|
+
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
15
|
+
# @return A Result or Results from the Futures
|
16
|
+
# @example Upload a large file in the background, then wait until it's loaded
|
17
|
+
# $futures = background() || {
|
18
|
+
# upload_file("./very_large_file", "/opt/jfrog/artifactory/var/etc/artifactory", $targets)
|
19
|
+
# }
|
20
|
+
# # Run an unrelated task
|
21
|
+
# run_task("deploy", $targets)
|
22
|
+
# # Wait for the file upload to finish
|
23
|
+
# $results = wait($futures)
|
24
|
+
dispatch :wait do
|
25
|
+
param 'Variant[Future, Array[Future]]', :futures
|
26
|
+
optional_param 'Hash[String[1], Any]', :options
|
27
|
+
return_type 'Array[Boltlib::PlanResult]'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Wait for Future(s) to finish with timeout
|
31
|
+
# @param futures A Bolt Future object or array of Bolt Futures to wait on.
|
32
|
+
# @param timeout How long to wait for Futures to finish before raising a Timeout error.
|
33
|
+
# @param options A hash of additional options.
|
34
|
+
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
35
|
+
# @return A Result or Results from the Futures
|
36
|
+
# @example Upload a large file in the background with a 30 second timeout.
|
37
|
+
# $futures = background() || {
|
38
|
+
# upload_file("./very_large_file", "/opt/jfrog/artifactory/var/etc/artifactory", $targets)
|
39
|
+
# }
|
40
|
+
# # Run an unrelated task
|
41
|
+
# run_task("deploy", $targets)
|
42
|
+
# # Wait for the file upload to finish
|
43
|
+
# $results = wait($futures, 30)
|
44
|
+
#
|
45
|
+
# @example Upload a large file in the background with a 30 second timeout, catching any errors.
|
46
|
+
# $futures = background() || {
|
47
|
+
# upload_file("./very_large_file", "/opt/jfrog/artifactory/var/etc/artifactory", $targets)
|
48
|
+
# }
|
49
|
+
# # Run an unrelated task
|
50
|
+
# run_task("deploy", $targets)
|
51
|
+
# # Wait for the file upload to finish
|
52
|
+
# $results = wait($futures, 30, '_catch_errors' => true)
|
53
|
+
dispatch :wait_with_timeout do
|
54
|
+
param 'Variant[Future, Array[Future]]', :futures
|
55
|
+
param 'Variant[Integer[0], Float[0.0]]', :timeout
|
56
|
+
optional_param 'Hash[String[1], Any]', :options
|
57
|
+
return_type 'Array[Boltlib::PlanResult]'
|
58
|
+
end
|
59
|
+
|
60
|
+
def wait(futures, options = {})
|
61
|
+
inner_wait(futures, nil, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def wait_with_timeout(futures, timeout, options = {})
|
65
|
+
inner_wait(futures, timeout, options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def inner_wait(futures, timeout = nil, options = {})
|
69
|
+
unless Puppet[:tasks]
|
70
|
+
raise Puppet::ParseErrorWithIssue
|
71
|
+
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'wait')
|
72
|
+
end
|
73
|
+
|
74
|
+
valid, unknown = options.partition { |k, _v| %w[_catch_errors].include?(k) }.map(&:to_h)
|
75
|
+
if unknown.any?
|
76
|
+
file, line = Puppet::Pops::PuppetStack.top_of_stack
|
77
|
+
msg = "The wait() function call in #{file}#L#{line} received unknown options "\
|
78
|
+
"#{unknown.keys}. Removing unknown options and continuing..."
|
79
|
+
Bolt::Logger.warn("plan_function_options", msg)
|
80
|
+
end
|
81
|
+
|
82
|
+
valid = valid.transform_keys { |k| k.sub(/^_/, '').to_sym }
|
83
|
+
valid[:timeout] = timeout if timeout
|
84
|
+
|
85
|
+
executor = Puppet.lookup(:bolt_executor)
|
86
|
+
executor.report_function_call(self.class.name)
|
87
|
+
|
88
|
+
futures = Array(futures)
|
89
|
+
executor.wait(futures, **valid)
|
90
|
+
end
|
91
|
+
end
|
data/guides/inventory.txt
CHANGED
@@ -14,6 +14,11 @@ DESCRIPTION
|
|
14
14
|
project configuration file named 'bolt-project.yaml' alongside the inventory
|
15
15
|
file.
|
16
16
|
|
17
|
+
When Bolt loads inventory, it loads the entire inventory, not just the
|
18
|
+
groups and targets specified on the command line. If you've defined a
|
19
|
+
target in multiple groups, this might result in target configuration that
|
20
|
+
is different than expected.
|
21
|
+
|
17
22
|
DOCUMENTATION
|
18
23
|
https://pup.pt/bolt-inventory
|
19
24
|
https://pup.pt/bolt-inventory-reference
|
data/lib/bolt/applicator.rb
CHANGED
@@ -306,11 +306,12 @@ module Bolt
|
|
306
306
|
Puppet.lookup(:current_environment).override_with(modulepath: @plugin_dirs).modules.each do |mod|
|
307
307
|
search_dirs = yield mod
|
308
308
|
|
309
|
-
|
309
|
+
tar_dir = Pathname.new(mod.name) # goes great with fish
|
310
|
+
mod_dir = Pathname.new(mod.path)
|
310
311
|
files = Find.find(*search_dirs).select { |file| File.file?(file) }
|
311
312
|
|
312
313
|
files.each do |file|
|
313
|
-
tar_path = Pathname.new(file).relative_path_from(
|
314
|
+
tar_path = tar_dir + Pathname.new(file).relative_path_from(mod_dir)
|
314
315
|
@logger.trace("Packing plugin #{file} to #{tar_path}")
|
315
316
|
stat = File.stat(file)
|
316
317
|
content = File.binread(file)
|
@@ -15,7 +15,7 @@ module Bolt
|
|
15
15
|
shell-command
|
16
16
|
tmpdir
|
17
17
|
tty
|
18
|
-
].freeze
|
18
|
+
].concat(RUN_AS_OPTIONS).sort.freeze
|
19
19
|
|
20
20
|
DEFAULTS = {
|
21
21
|
'cleanup' => true
|
@@ -27,6 +27,10 @@ module Bolt
|
|
27
27
|
if @config['interpreters']
|
28
28
|
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
|
29
29
|
end
|
30
|
+
|
31
|
+
if Bolt::Util.windows? && @config['run-as']
|
32
|
+
raise Bolt::ValidationError, "run-as is not supported when using PowerShell"
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -14,7 +14,7 @@ module Bolt
|
|
14
14
|
shell-command
|
15
15
|
tmpdir
|
16
16
|
tty
|
17
|
-
].freeze
|
17
|
+
].concat(RUN_AS_OPTIONS).sort.freeze
|
18
18
|
|
19
19
|
DEFAULTS = {
|
20
20
|
'cleanup' => true
|
@@ -26,6 +26,10 @@ module Bolt
|
|
26
26
|
if @config['interpreters']
|
27
27
|
@config['interpreters'] = normalize_interpreters(@config['interpreters'])
|
28
28
|
end
|
29
|
+
|
30
|
+
if Bolt::Util.windows? && @config['run-as']
|
31
|
+
raise Bolt::ValidationError, "run-as is not supported when using PowerShell"
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
data/lib/bolt/error.rb
CHANGED
@@ -105,6 +105,16 @@ module Bolt
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
|
+
class FutureTimeoutError < Bolt::Error
|
109
|
+
def initialize(name, timeout)
|
110
|
+
details = {
|
111
|
+
'future' => name
|
112
|
+
}
|
113
|
+
message = "Future '#{name}' timed out after #{timeout} seconds."
|
114
|
+
super(message, 'bolt/future-timeout-error', details)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
108
118
|
class ParallelFailure < Bolt::Error
|
109
119
|
def initialize(results, failed_indices)
|
110
120
|
details = {
|
@@ -162,7 +172,7 @@ module Bolt
|
|
162
172
|
|
163
173
|
class InvalidParallelResult < Error
|
164
174
|
def initialize(result_str, file, line)
|
165
|
-
super("
|
175
|
+
super("Background block returned an invalid result: #{result_str}",
|
166
176
|
'bolt/invalid-plan-result',
|
167
177
|
{ 'file' => file,
|
168
178
|
'line' => line,
|
data/lib/bolt/executor.rb
CHANGED
@@ -7,10 +7,11 @@ require 'logging'
|
|
7
7
|
require 'pathname'
|
8
8
|
require 'set'
|
9
9
|
require 'bolt/analytics'
|
10
|
-
require 'bolt/result'
|
11
10
|
require 'bolt/config'
|
12
|
-
require 'bolt/
|
11
|
+
require 'bolt/fiber_executor'
|
13
12
|
require 'bolt/puppetdb'
|
13
|
+
require 'bolt/result'
|
14
|
+
require 'bolt/result_set'
|
14
15
|
# Load transports
|
15
16
|
require 'bolt/transport/docker'
|
16
17
|
require 'bolt/transport/local'
|
@@ -20,7 +21,6 @@ require 'bolt/transport/podman'
|
|
20
21
|
require 'bolt/transport/remote'
|
21
22
|
require 'bolt/transport/ssh'
|
22
23
|
require 'bolt/transport/winrm'
|
23
|
-
require 'bolt/yarn'
|
24
24
|
|
25
25
|
module Bolt
|
26
26
|
TRANSPORTS = {
|
@@ -35,7 +35,7 @@ module Bolt
|
|
35
35
|
}.freeze
|
36
36
|
|
37
37
|
class Executor
|
38
|
-
attr_reader :noop, :transports, :
|
38
|
+
attr_reader :noop, :transports, :future
|
39
39
|
attr_accessor :run_as
|
40
40
|
|
41
41
|
def initialize(concurrency = 1,
|
@@ -66,7 +66,6 @@ module Bolt
|
|
66
66
|
|
67
67
|
@noop = noop
|
68
68
|
@run_as = nil
|
69
|
-
@in_parallel = false
|
70
69
|
@future = future
|
71
70
|
@pool = if concurrency > 0
|
72
71
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
@@ -77,6 +76,7 @@ module Bolt
|
|
77
76
|
|
78
77
|
@concurrency = concurrency
|
79
78
|
@warn_concurrency = modified_concurrency
|
79
|
+
@fiber_executor = Bolt::FiberExecutor.new
|
80
80
|
end
|
81
81
|
|
82
82
|
def transport(transport)
|
@@ -373,83 +373,62 @@ module Bolt
|
|
373
373
|
plan.call_by_name_with_scope(scope, params, true)
|
374
374
|
end
|
375
375
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
current_scope = scope.effective_symtable(true)
|
384
|
-
until current_scope.nil?
|
385
|
-
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
386
|
-
current_scope = current_scope.parent
|
387
|
-
end
|
388
|
-
newscope.push_ephemerals([local])
|
389
|
-
|
390
|
-
begin
|
391
|
-
result = catch(:return) do
|
392
|
-
args = { block.parameters[0][1].to_s => object }
|
393
|
-
block.closure.call_by_name_with_scope(newscope, args, true)
|
394
|
-
end
|
395
|
-
|
396
|
-
# If we got a return from the block, get it's value
|
397
|
-
# Otherwise the result is the last line from the block
|
398
|
-
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
376
|
+
# Call into FiberExecutor to avoid this class getting
|
377
|
+
# overloaded while also minimizing the Puppet lookups needed from plan
|
378
|
+
# functions
|
379
|
+
#
|
380
|
+
def create_future(scope: nil, name: nil, &block)
|
381
|
+
@fiber_executor.create_future(scope: scope, name: name, &block)
|
382
|
+
end
|
399
383
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
end
|
384
|
+
def plan_complete?
|
385
|
+
@fiber_executor.plan_complete?
|
386
|
+
end
|
404
387
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
e.cause
|
409
|
-
else
|
410
|
-
raise e
|
411
|
-
end
|
412
|
-
end
|
413
|
-
end
|
388
|
+
def round_robin
|
389
|
+
@fiber_executor.round_robin
|
390
|
+
end
|
414
391
|
|
415
|
-
|
392
|
+
def in_parallel?
|
393
|
+
@fiber_executor.in_parallel?
|
416
394
|
end
|
417
395
|
|
418
|
-
def
|
419
|
-
|
420
|
-
when :node_result
|
421
|
-
@thread_completed = true
|
422
|
-
end
|
396
|
+
def wait(futures, **opts)
|
397
|
+
@fiber_executor.wait(futures, **opts)
|
423
398
|
end
|
424
399
|
|
425
|
-
def
|
426
|
-
|
427
|
-
|
428
|
-
@in_parallel = true
|
429
|
-
publish_event(type: :stop_spin)
|
400
|
+
def plan_futures
|
401
|
+
@fiber_executor.plan_futures
|
402
|
+
end
|
430
403
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
skein.delete(yarn)
|
442
|
-
end
|
443
|
-
end
|
404
|
+
# Execute a plan function concurrently. This function accepts the executor
|
405
|
+
# function to be run and the parameters to pass to it, and returns the
|
406
|
+
# result of running the executor function.
|
407
|
+
#
|
408
|
+
def run_in_thread
|
409
|
+
require 'concurrent'
|
410
|
+
require 'fiber'
|
411
|
+
future = Concurrent::Future.execute do
|
412
|
+
yield
|
413
|
+
end
|
444
414
|
|
445
|
-
|
446
|
-
|
415
|
+
# Used to track how often we resume the same executor function
|
416
|
+
still_running = 0
|
417
|
+
# While the thread is still running
|
418
|
+
while future.incomplete?
|
419
|
+
# If the Fiber gets resumed, increment the resume tracker. This means
|
420
|
+
# the tracker starts at 1 since it needs to increment before yielding,
|
421
|
+
# since it can't yield then increment.
|
422
|
+
still_running += 1
|
423
|
+
# If the Fiber has been resumed before, still_running will be 2 or
|
424
|
+
# more. Yield different values for when the same Fiber is resumed
|
425
|
+
# multiple times and when it's resumed the first time in order to know
|
426
|
+
# if progress was made in the plan.
|
427
|
+
Fiber.yield(still_running < 2 ? :something_happened : :returned_immediately)
|
447
428
|
end
|
448
429
|
|
449
|
-
|
450
|
-
|
451
|
-
unsubscribe(self, [:node_result])
|
452
|
-
results
|
430
|
+
# Once the thread completes, return the result.
|
431
|
+
future.value || future.reason
|
453
432
|
end
|
454
433
|
|
455
434
|
class TimeoutError < RuntimeError; end
|
@@ -519,7 +498,7 @@ module Bolt
|
|
519
498
|
# coupled with the orchestrator transport since the transport behaves
|
520
499
|
# differently when a plan is running. In order to limit how much this
|
521
500
|
# pollutes the transport API we only handle the orchestrator transport here.
|
522
|
-
# Since we
|
501
|
+
# Since we call this function without resolving targets this will result
|
523
502
|
# in the orchestrator transport always being initialized during plan runs.
|
524
503
|
# For now that's ok.
|
525
504
|
#
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/logger'
|
4
|
+
require 'bolt/plan_future'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class FiberExecutor
|
8
|
+
attr_reader :plan_futures
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@logger = Bolt::Logger.logger(self)
|
12
|
+
@id = 0
|
13
|
+
@plan_futures = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Whether there is more than one fiber running in parallel.
|
17
|
+
#
|
18
|
+
def in_parallel?
|
19
|
+
plan_futures.length > 1
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a new Puppet scope from the current Plan scope so that variables
|
23
|
+
# can be used inside the block and won't interact with the outer scope.
|
24
|
+
# Then creates a new Fiber to execute the block, wraps the Fiber in a
|
25
|
+
# Bolt::PlanFuture, and returns the Bolt::PlanFuture.
|
26
|
+
#
|
27
|
+
def create_future(scope: nil, name: nil)
|
28
|
+
newscope = nil
|
29
|
+
if scope
|
30
|
+
# Save existing variables to the new scope before starting the future
|
31
|
+
# itself so that if the plan returns before the backgrounded block
|
32
|
+
# starts, we still have the variables.
|
33
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
34
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
35
|
+
|
36
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
37
|
+
scope.to_hash.each_pair { |k, v| local[k] = v }
|
38
|
+
newscope.push_ephemerals([local])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a new Fiber that will execute the provided block.
|
42
|
+
future = Fiber.new do
|
43
|
+
# Yield the new scope - this should be ignored by the block if
|
44
|
+
# `newscope` is nil.
|
45
|
+
yield newscope
|
46
|
+
end
|
47
|
+
|
48
|
+
# PlanFutures are assigned an ID, which is just a global incrementing
|
49
|
+
# integer. The main plan should always have ID 0.
|
50
|
+
@id += 1
|
51
|
+
future = Bolt::PlanFuture.new(future, @id, name)
|
52
|
+
@logger.trace("Created future #{future.name}")
|
53
|
+
|
54
|
+
# Register the PlanFuture with the FiberExecutor to be executed
|
55
|
+
plan_futures << future
|
56
|
+
future
|
57
|
+
end
|
58
|
+
|
59
|
+
# Visit each PlanFuture registered with the FiberExecutor and resume it.
|
60
|
+
# Fibers will yield themselves back, either if they kicked off a
|
61
|
+
# long-running process or if the current long-running process hasn't
|
62
|
+
# completed. If the Fiber finishes after being resumed, store the result in
|
63
|
+
# the PlanFuture and remove the PlanFuture from the FiberExecutor.
|
64
|
+
#
|
65
|
+
def round_robin
|
66
|
+
plan_futures.each do |future|
|
67
|
+
# If the Fiber is still running and can be resumed, then resume it
|
68
|
+
@logger.trace("Checking future '#{future.name}'")
|
69
|
+
if future.alive?
|
70
|
+
@logger.trace("Resuming future '#{future.name}'")
|
71
|
+
future.resume
|
72
|
+
end
|
73
|
+
|
74
|
+
# Once we've restarted the Fiber, check to see if it's finished again
|
75
|
+
# and cleanup if it has.
|
76
|
+
next if future.alive?
|
77
|
+
@logger.trace("Cleaning up future '#{future.name}'")
|
78
|
+
|
79
|
+
# If the future errored and the main plan has already exited, log the
|
80
|
+
# error at warn level.
|
81
|
+
unless plan_futures.map(&:id).include?(0) || future.state == "done"
|
82
|
+
Bolt::Logger.warn('errored_futures', "Error in future '#{future.name}': #{future.value}")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Remove the PlanFuture from the FiberExecutor.
|
86
|
+
plan_futures.delete(future)
|
87
|
+
end
|
88
|
+
|
89
|
+
# If the Fiber immediately returned or if the Fiber is blocking on a
|
90
|
+
# `wait` call, Bolt should pause for long enough that something can
|
91
|
+
# execute before checking again. This mitigates CPU
|
92
|
+
# thrashing.
|
93
|
+
return unless plan_futures.all? { |f| %i[returned_immediately unfinished].include?(f.value) }
|
94
|
+
@logger.trace("Nothing can be resumed. Rechecking in 0.5 seconds.")
|
95
|
+
|
96
|
+
sleep(0.5)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Whether all PlanFutures have finished executing, indicating that the
|
100
|
+
# entire plan (main plan and any PlanFutures it spawned) has finished and
|
101
|
+
# Bolt can exit.
|
102
|
+
#
|
103
|
+
def plan_complete?
|
104
|
+
plan_futures.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Block until the provided PlanFuture objects have finished, or the timeout is reached.
|
108
|
+
#
|
109
|
+
def wait(futures, timeout: nil, catch_errors: false, **_kwargs)
|
110
|
+
if timeout.nil?
|
111
|
+
Fiber.yield(:unfinished) until futures.map(&:alive?).none?
|
112
|
+
else
|
113
|
+
start = Time.now
|
114
|
+
Fiber.yield(:unfinished) until (Time.now - start > timeout) || futures.map(&:alive?).none?
|
115
|
+
# Raise an error for any futures that are still alive
|
116
|
+
futures.each do |f|
|
117
|
+
if f.alive?
|
118
|
+
f.raise(Bolt::FutureTimeoutError.new(f.name, timeout))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
results = futures.map(&:value)
|
124
|
+
|
125
|
+
failed_indices = results.each_index.select do |i|
|
126
|
+
results[i].is_a?(Bolt::Error)
|
127
|
+
end
|
128
|
+
|
129
|
+
if failed_indices.any?
|
130
|
+
if catch_errors
|
131
|
+
failed_indices.each { |i| results[i] = results[i].to_puppet_error }
|
132
|
+
else
|
133
|
+
# Do this after handling errors for simplicity and pretty printing
|
134
|
+
raise Bolt::ParallelFailure.new(results, failed_indices)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
results
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/bolt/outputter/human.rb
CHANGED
data/lib/bolt/pal.rb
CHANGED
@@ -607,10 +607,26 @@ module Bolt
|
|
607
607
|
end
|
608
608
|
end
|
609
609
|
|
610
|
-
def run_plan(plan_name, params, executor
|
610
|
+
def run_plan(plan_name, params, executor, inventory = nil, pdb_client = nil, applicator = nil)
|
611
|
+
# Start the round robin inside the plan compiler, so that
|
612
|
+
# backgrounded tasks can finish once the main plan exits
|
611
613
|
in_plan_compiler(executor, inventory, pdb_client, applicator) do |compiler|
|
612
|
-
|
613
|
-
|
614
|
+
# Create a Fiber for the main plan. This will be run along with any
|
615
|
+
# other Fibers created during the plan run in the round_robin, with the
|
616
|
+
# main plan always taking precedence in being resumed.
|
617
|
+
future = executor.create_future(name: plan_name) do |_scope|
|
618
|
+
r = compiler.call_function('run_plan', plan_name, params.merge('_bolt_api_call' => true))
|
619
|
+
Bolt::PlanResult.from_pcore(r, 'success')
|
620
|
+
rescue Bolt::Error => e
|
621
|
+
Bolt::PlanResult.new(e, 'failure')
|
622
|
+
end
|
623
|
+
|
624
|
+
# Round robin until all Fibers, including the main plan, have finished.
|
625
|
+
# This will stay alive until backgrounded tasks have finished.
|
626
|
+
executor.round_robin until executor.plan_complete?
|
627
|
+
|
628
|
+
# Return the result from the main plan
|
629
|
+
future.value
|
614
630
|
end
|
615
631
|
rescue Bolt::Error => e
|
616
632
|
Bolt::PlanResult.new(e, 'failure')
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class PlanFuture
|
7
|
+
attr_reader :fiber, :id
|
8
|
+
attr_accessor :value
|
9
|
+
|
10
|
+
def initialize(fiber, id, name = nil)
|
11
|
+
@fiber = fiber
|
12
|
+
@id = id
|
13
|
+
@name = name
|
14
|
+
@value = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name || @id
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Future '#{name}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def alive?
|
26
|
+
fiber.alive?
|
27
|
+
end
|
28
|
+
|
29
|
+
def raise(exception)
|
30
|
+
# Make sure the value gets set
|
31
|
+
@value = exception
|
32
|
+
# This was introduced in Ruby 2.7
|
33
|
+
begin
|
34
|
+
# Raise an exception to kill the Fiber. If the Fiber has not been
|
35
|
+
# resumed yet, or is already terminated this will raise a FiberError.
|
36
|
+
# We don't especially care about the FiberError, as long as the Fiber
|
37
|
+
# doesn't report itself as alive.
|
38
|
+
fiber.raise(exception)
|
39
|
+
rescue FiberError
|
40
|
+
# If the Fiber is still alive, resume it with a block to raise the
|
41
|
+
# exception which will terminate it.
|
42
|
+
if fiber.alive?
|
43
|
+
fiber.resume { raise(exception) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def resume
|
49
|
+
if fiber.alive?
|
50
|
+
@value = fiber.resume
|
51
|
+
else
|
52
|
+
@value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def state
|
57
|
+
if fiber.alive?
|
58
|
+
"running"
|
59
|
+
elsif value.is_a?(Exception)
|
60
|
+
"error"
|
61
|
+
else
|
62
|
+
"done"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -27,6 +27,10 @@ module Bolt
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def reset_cwd?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
30
34
|
# The full ID of the target container
|
31
35
|
#
|
32
36
|
# @return [String] The full ID of the target container
|
@@ -74,7 +78,6 @@ module Bolt
|
|
74
78
|
# CODEREVIEW: Is it always safe to pass --interactive?
|
75
79
|
args += %w[--interactive]
|
76
80
|
args += %w[--tty] if target.options['tty']
|
77
|
-
args += %W[--env DOCKER_HOST=#{@docker_host}] if @docker_host
|
78
81
|
args += @env_vars if @env_vars
|
79
82
|
|
80
83
|
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
@@ -86,7 +89,7 @@ module Bolt
|
|
86
89
|
docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
|
87
90
|
@logger.trace { "Executing: #{docker_command.join(' ')}" }
|
88
91
|
|
89
|
-
Open3.popen3(*docker_command)
|
92
|
+
Open3.popen3(env_hash, *docker_command)
|
90
93
|
rescue StandardError
|
91
94
|
@logger.trace { "Command aborted" }
|
92
95
|
raise
|
data/lib/bolt/version.rb
CHANGED
@@ -17,13 +17,12 @@ module BoltSpec
|
|
17
17
|
|
18
18
|
# Nothing on the executor is 'public'
|
19
19
|
class MockExecutor
|
20
|
-
attr_reader :noop, :error_message, :
|
20
|
+
attr_reader :noop, :error_message, :transports, :future
|
21
21
|
attr_accessor :run_as, :transport_features, :execute_any_plan
|
22
22
|
|
23
23
|
def initialize(modulepath)
|
24
24
|
@noop = false
|
25
25
|
@run_as = nil
|
26
|
-
@in_parallel = false
|
27
26
|
@future = {}
|
28
27
|
@error_message = nil
|
29
28
|
@allow_apply = false
|
@@ -38,6 +37,7 @@ module BoltSpec
|
|
38
37
|
@execute_any_plan = true
|
39
38
|
# plans that are allowed to be executed by the @executor_real
|
40
39
|
@allowed_exec_plans = {}
|
40
|
+
@id = 0
|
41
41
|
end
|
42
42
|
|
43
43
|
def module_file_id(file)
|
@@ -257,58 +257,53 @@ module BoltSpec
|
|
257
257
|
end
|
258
258
|
# End apply_prep mocking
|
259
259
|
|
260
|
-
#
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
# executed for each object sequentially, and this function returns the
|
265
|
-
# result itself.
|
266
|
-
#
|
267
|
-
def create_yarn(scope, block, object, _index)
|
268
|
-
# Create the new scope
|
269
|
-
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
270
|
-
local = Puppet::Parser::Scope::LocalScope.new
|
271
|
-
|
272
|
-
# Compress the current scopes into a single vars hash to add to the new scope
|
273
|
-
current_scope = scope.effective_symtable(true)
|
274
|
-
until current_scope.nil?
|
275
|
-
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
276
|
-
current_scope = current_scope.parent
|
277
|
-
end
|
278
|
-
newscope.push_ephemerals([local])
|
279
|
-
|
280
|
-
begin
|
281
|
-
result = catch(:return) do
|
282
|
-
args = { block.parameters[0][1].to_s => object }
|
283
|
-
block.closure.call_by_name_with_scope(newscope, args, true)
|
284
|
-
end
|
285
|
-
|
286
|
-
# If we got a return from the block, get it's value
|
287
|
-
# Otherwise the result is the last line from the block
|
288
|
-
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
260
|
+
# Parallel function mocking
|
261
|
+
def run_in_thread
|
262
|
+
yield
|
263
|
+
end
|
289
264
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
265
|
+
def in_parallel?
|
266
|
+
false
|
267
|
+
end
|
294
268
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
269
|
+
def create_future(scope: nil, name: nil)
|
270
|
+
newscope = nil
|
271
|
+
if scope
|
272
|
+
# Create the new scope
|
273
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
274
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
275
|
+
|
276
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
277
|
+
current_scope = scope.effective_symtable(true)
|
278
|
+
until current_scope.nil?
|
279
|
+
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
280
|
+
current_scope = current_scope.parent
|
301
281
|
end
|
282
|
+
newscope.push_ephemerals([local])
|
302
283
|
end
|
284
|
+
|
285
|
+
# Execute "futures" serially when running in BoltSpec
|
286
|
+
result = yield newscope
|
287
|
+
@id += 1
|
288
|
+
future = Bolt::PlanFuture.new(nil, @id, name: name)
|
289
|
+
future.value = result
|
290
|
+
future
|
303
291
|
end
|
304
292
|
|
305
|
-
|
306
|
-
# passed to the function, so these results can be returned as-is.
|
307
|
-
#
|
308
|
-
def round_robin(results)
|
293
|
+
def wait(results, _timeout, **_kwargs)
|
309
294
|
results
|
310
295
|
end
|
311
296
|
|
297
|
+
# Since Futures are executed immediately once created, this will always
|
298
|
+
# be true by the time it's called.
|
299
|
+
def plan_complete?
|
300
|
+
true
|
301
|
+
end
|
302
|
+
|
303
|
+
def plan_futures
|
304
|
+
[]
|
305
|
+
end
|
306
|
+
|
312
307
|
# Public methods on Bolt::Executor that need to be mocked so there aren't
|
313
308
|
# "undefined method" errors.
|
314
309
|
|
@@ -337,6 +332,8 @@ module BoltSpec
|
|
337
332
|
def subscribe(_subscriber, _types = nil); end
|
338
333
|
|
339
334
|
def unsubscribe(_subscriber, _types = nil); end
|
335
|
+
|
336
|
+
def round_robin; end
|
340
337
|
end
|
341
338
|
end
|
342
339
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -399,6 +399,7 @@ files:
|
|
399
399
|
- Puppetfile
|
400
400
|
- bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb
|
401
401
|
- bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb
|
402
|
+
- bolt-modules/boltlib/lib/puppet/datatypes/future.rb
|
402
403
|
- bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb
|
403
404
|
- bolt-modules/boltlib/lib/puppet/datatypes/result.rb
|
404
405
|
- bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb
|
@@ -406,6 +407,7 @@ files:
|
|
406
407
|
- bolt-modules/boltlib/lib/puppet/functions/add_facts.rb
|
407
408
|
- bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb
|
408
409
|
- bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb
|
410
|
+
- bolt-modules/boltlib/lib/puppet/functions/background.rb
|
409
411
|
- bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb
|
410
412
|
- bolt-modules/boltlib/lib/puppet/functions/download_file.rb
|
411
413
|
- bolt-modules/boltlib/lib/puppet/functions/facts.rb
|
@@ -432,6 +434,7 @@ files:
|
|
432
434
|
- bolt-modules/boltlib/lib/puppet/functions/set_var.rb
|
433
435
|
- bolt-modules/boltlib/lib/puppet/functions/upload_file.rb
|
434
436
|
- bolt-modules/boltlib/lib/puppet/functions/vars.rb
|
437
|
+
- bolt-modules/boltlib/lib/puppet/functions/wait.rb
|
435
438
|
- bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb
|
436
439
|
- bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb
|
437
440
|
- bolt-modules/boltlib/lib/puppet/functions/write_file.rb
|
@@ -485,6 +488,7 @@ files:
|
|
485
488
|
- lib/bolt/container_result.rb
|
486
489
|
- lib/bolt/error.rb
|
487
490
|
- lib/bolt/executor.rb
|
491
|
+
- lib/bolt/fiber_executor.rb
|
488
492
|
- lib/bolt/inventory.rb
|
489
493
|
- lib/bolt/inventory/group.rb
|
490
494
|
- lib/bolt/inventory/inventory.rb
|
@@ -528,6 +532,7 @@ files:
|
|
528
532
|
- lib/bolt/pal/yaml_plan/step/upload.rb
|
529
533
|
- lib/bolt/pal/yaml_plan/transpiler.rb
|
530
534
|
- lib/bolt/plan_creator.rb
|
535
|
+
- lib/bolt/plan_future.rb
|
531
536
|
- lib/bolt/plan_result.rb
|
532
537
|
- lib/bolt/plugin.rb
|
533
538
|
- lib/bolt/plugin/cache.rb
|
@@ -583,7 +588,6 @@ files:
|
|
583
588
|
- lib/bolt/util/puppet_log_level.rb
|
584
589
|
- lib/bolt/validator.rb
|
585
590
|
- lib/bolt/version.rb
|
586
|
-
- lib/bolt/yarn.rb
|
587
591
|
- lib/bolt_server/acl.rb
|
588
592
|
- lib/bolt_server/base_config.rb
|
589
593
|
- lib/bolt_server/config.rb
|
data/lib/bolt/yarn.rb
DELETED
@@ -1,23 +0,0 @@
|
|
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
|