bolt 3.13.0 → 3.14.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/functions/background.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +5 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +13 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +47 -7
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +4 -2
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +4 -2
- data/lib/bolt/analytics.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +4 -1
- data/lib/bolt/cli.rb +21 -6
- data/lib/bolt/config/transport/options.rb +12 -0
- data/lib/bolt/config/transport/ssh.rb +7 -0
- data/lib/bolt/executor.rb +12 -4
- data/lib/bolt/fiber_executor.rb +57 -12
- data/lib/bolt/outputter/human.rb +117 -12
- data/lib/bolt/outputter/json.rb +3 -5
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/pal.rb +36 -3
- data/lib/bolt/pal/yaml_plan/step.rb +2 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +0 -8
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/plan_future.rb +21 -6
- data/lib/bolt/transport/ssh/exec_connection.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/schemas/partials/target-ssh.json +4 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +4 -0
- data/lib/bolt_server/transport_app.rb +81 -50
- data/lib/bolt_spec/plans/mock_executor.rb +16 -6
- metadata +11 -14
- data/guides/debugging.txt +0 -28
- data/guides/guide.txt +0 -17
- data/guides/inventory.txt +0 -24
- data/guides/links.txt +0 -13
- data/guides/logging.txt +0 -18
- data/guides/module.txt +0 -19
- data/guides/modulepath.txt +0 -25
- data/guides/project.txt +0 -22
- data/guides/targets.txt +0 -29
- data/guides/transports.txt +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9dbc0b26225fd929d594a6719b55491bad8647650d257b0bbd1bf0eb2424f7c
|
4
|
+
data.tar.gz: ff0df82a3a02edfbf05bdbfe39814d2d5535719dbb6b80659bfff5d28651da3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61a484916431f7da5b3fa8992641bdf382f0d34eaf2f054e41638e27dc48b7118c07f7729a54246a0d6e19e2a74cedc549fdcc513ba26651f84b470f2c7b187e
|
7
|
+
data.tar.gz: d5bf6f496c13253e83dd7d9625058846000a5b339a13fd8d6d922b61ed57b61618125c5de76377f1e01d946e16bd220313659cc066ec793527a2c03013264bc0
|
data/Puppetfile
CHANGED
@@ -35,7 +35,7 @@ mod 'puppetlabs-stdlib', '7.1.0'
|
|
35
35
|
mod 'puppetlabs-aws_inventory', '0.7.0'
|
36
36
|
mod 'puppetlabs-azure_inventory', '0.5.0'
|
37
37
|
mod 'puppetlabs-gcloud_inventory', '0.3.0'
|
38
|
-
mod 'puppetlabs-http_request', '0.
|
38
|
+
mod 'puppetlabs-http_request', '0.3.0'
|
39
39
|
mod 'puppetlabs-pkcs7', '0.1.2'
|
40
40
|
mod 'puppetlabs-secure_env_vars', '0.2.0'
|
41
41
|
mod 'puppetlabs-terraform', '0.6.1'
|
@@ -31,7 +31,8 @@ Puppet::Functions.create_function(:background, Puppet::Functions::InternalFuncti
|
|
31
31
|
executor = Puppet.lookup(:bolt_executor)
|
32
32
|
executor.report_function_call(self.class.name)
|
33
33
|
|
34
|
-
executor.
|
34
|
+
plan_id = executor.get_current_plan_id(fiber: Fiber.current)
|
35
|
+
executor.create_future(scope: scope, name: name, plan_id: plan_id) do |newscope|
|
35
36
|
# Catch 'return' calls inside the block
|
36
37
|
result = catch(:return) do
|
37
38
|
# Execute the block. Individual plan steps in the block will yield
|
@@ -34,7 +34,11 @@ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunct
|
|
34
34
|
executor.report_function_call(self.class.name)
|
35
35
|
|
36
36
|
futures = data.map do |object|
|
37
|
-
|
37
|
+
# We're going to immediately wait for these futures, *and* don't want
|
38
|
+
# their results to be returned as part of `wait()`, so use a 'dummy'
|
39
|
+
# value as the plan_id. This could also be nil, though in general we want
|
40
|
+
# to require Futures to have a plan stack so that they don't get lost.
|
41
|
+
executor.create_future(scope: scope, plan_id: 'parallel') do |newscope|
|
38
42
|
# Catch 'return' calls inside the block
|
39
43
|
result = catch(:return) do
|
40
44
|
# Add the object to the block parameters
|
@@ -125,6 +125,17 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
125
125
|
params = wrap_sensitive_parameters(params, closure.parameters)
|
126
126
|
end
|
127
127
|
|
128
|
+
# This can be anything as long as it's unique
|
129
|
+
plan_instance_id = SecureRandom.uuid
|
130
|
+
|
131
|
+
# Add the plan invocation ID to the plan_stack for the PlanFuture the plan is
|
132
|
+
# running in so that we know the PlanFuture is running in a new plan
|
133
|
+
# invocation. This can be nil in test cases and when `wait()` isn't
|
134
|
+
# supported.
|
135
|
+
current_future = executor.get_current_future(fiber: Fiber.current)
|
136
|
+
# Safe operator to make testing easier
|
137
|
+
current_future&.plan_stack&.unshift(plan_instance_id)
|
138
|
+
|
128
139
|
# wrap plan execution in logging messages
|
129
140
|
executor.log_plan(plan_name) do
|
130
141
|
result = nil
|
@@ -150,6 +161,8 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
150
161
|
raise e
|
151
162
|
end
|
152
163
|
ensure
|
164
|
+
# Pop the plan invocation ID off of the plan_id stack for the Future.
|
165
|
+
current_future&.plan_stack&.shift
|
153
166
|
if run_as
|
154
167
|
executor.run_as = old_run_as
|
155
168
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bolt/logger'
|
4
|
-
require 'bolt/target'
|
5
4
|
|
6
5
|
# Wait for a Future or array of Futures to finish and return results,
|
7
6
|
# optionally with a timeout.
|
8
7
|
#
|
9
8
|
# > **Note:** Not available in apply block
|
10
9
|
Puppet::Functions.create_function(:wait, Puppet::Functions::InternalFunction) do
|
11
|
-
# Wait for
|
10
|
+
# Wait for Futures to finish.
|
12
11
|
# @param futures A Bolt Future object or array of Bolt Futures to wait on.
|
13
12
|
# @param options A hash of additional options.
|
14
13
|
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
@@ -27,7 +26,37 @@ Puppet::Functions.create_function(:wait, Puppet::Functions::InternalFunction) do
|
|
27
26
|
return_type 'Array[Boltlib::PlanResult]'
|
28
27
|
end
|
29
28
|
|
30
|
-
# Wait for
|
29
|
+
# Wait for all Futures in the current plan to finish.
|
30
|
+
# @param options A hash of additional options.
|
31
|
+
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
32
|
+
# @return A Result or Results from the Futures
|
33
|
+
# @example Perform multiple tasks in the background, then wait for all of them to finish
|
34
|
+
# background() || { upload_file("./large_file", "/opt/jfrog/...", $targets) }
|
35
|
+
# background() || { run_task("db::migrate", $targets) }
|
36
|
+
# # Wait for all futures in the plan to finish and return all results
|
37
|
+
# $results = wait()
|
38
|
+
dispatch :wait_for_all do
|
39
|
+
optional_param 'Hash[String[1], Any]', :options
|
40
|
+
return_type 'Array[Boltlib::PlanResult]'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Wait for all Futures in the current plan to finish with a timeout.
|
44
|
+
# @param timeout How long to wait for Futures to finish before raising a Timeout error.
|
45
|
+
# @param options A hash of additional options.
|
46
|
+
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
47
|
+
# @return A Result or Results from the Futures
|
48
|
+
# @example Perform multiple tasks in the background, then wait for all of them to finish with a timeout
|
49
|
+
# background() || { upload_file("./large_file", "/opt/jfrog/...", $targets) }
|
50
|
+
# background() || { run_task("db::migrate", $targets) }
|
51
|
+
# # Wait for all futures in the plan to finish and return all results
|
52
|
+
# $results = wait(30)
|
53
|
+
dispatch :wait_for_all_with_timeout do
|
54
|
+
param 'Variant[Integer[0], Float[0.0]]', :timeout
|
55
|
+
optional_param 'Hash[String[1], Any]', :options
|
56
|
+
return_type 'Array[Boltlib::PlanResult]'
|
57
|
+
end
|
58
|
+
|
59
|
+
# Wait for Futures to finish with timeout.
|
31
60
|
# @param futures A Bolt Future object or array of Bolt Futures to wait on.
|
32
61
|
# @param timeout How long to wait for Futures to finish before raising a Timeout error.
|
33
62
|
# @param options A hash of additional options.
|
@@ -58,14 +87,22 @@ Puppet::Functions.create_function(:wait, Puppet::Functions::InternalFunction) do
|
|
58
87
|
end
|
59
88
|
|
60
89
|
def wait(futures, options = {})
|
61
|
-
inner_wait(futures
|
90
|
+
inner_wait(futures: futures, options: options)
|
91
|
+
end
|
92
|
+
|
93
|
+
def wait_for_all(options = {})
|
94
|
+
inner_wait(options: options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def wait_for_all_with_timeout(timeout, options = {})
|
98
|
+
inner_wait(timeout: timeout, options: options)
|
62
99
|
end
|
63
100
|
|
64
101
|
def wait_with_timeout(futures, timeout, options = {})
|
65
|
-
inner_wait(futures, timeout, options)
|
102
|
+
inner_wait(futures: futures, timeout: timeout, options: options)
|
66
103
|
end
|
67
104
|
|
68
|
-
def inner_wait(futures, timeout
|
105
|
+
def inner_wait(futures: nil, timeout: nil, options: {})
|
69
106
|
unless Puppet[:tasks]
|
70
107
|
raise Puppet::ParseErrorWithIssue
|
71
108
|
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'wait')
|
@@ -85,7 +122,10 @@ Puppet::Functions.create_function(:wait, Puppet::Functions::InternalFunction) do
|
|
85
122
|
executor = Puppet.lookup(:bolt_executor)
|
86
123
|
executor.report_function_call(self.class.name)
|
87
124
|
|
88
|
-
|
125
|
+
# If we get a single Future, make sure it's an array. If we didn't get any
|
126
|
+
# futures pass that on to wait so we can continue collecting any futures
|
127
|
+
# that are created while waiting on existing futures.
|
128
|
+
futures = Array(futures) unless futures.nil?
|
89
129
|
executor.wait(futures, **valid)
|
90
130
|
end
|
91
131
|
end
|
@@ -5,7 +5,9 @@ require 'bolt/util/format'
|
|
5
5
|
# Output a message for the user.
|
6
6
|
#
|
7
7
|
# This will print a message to stdout when using the human output format,
|
8
|
-
# and print to stderr when using the json output format
|
8
|
+
# and print to stderr when using the json output format. Messages are
|
9
|
+
# also logged at the `info` level. For more information about logs, see
|
10
|
+
# [Logs](logs.md).
|
9
11
|
#
|
10
12
|
# > **Note:** Not available in apply block
|
11
13
|
Puppet::Functions.create_function(:'out::message') do
|
@@ -26,7 +28,7 @@ Puppet::Functions.create_function(:'out::message') do
|
|
26
28
|
|
27
29
|
Puppet.lookup(:bolt_executor).tap do |executor|
|
28
30
|
executor.report_function_call(self.class.name)
|
29
|
-
executor.publish_event(type: :message, message: Bolt::Util::Format.stringify(message))
|
31
|
+
executor.publish_event(type: :message, message: Bolt::Util::Format.stringify(message), level: :info)
|
30
32
|
end
|
31
33
|
|
32
34
|
nil
|
@@ -5,7 +5,9 @@ require 'bolt/util/format'
|
|
5
5
|
# Output a message for the user when running in verbose mode.
|
6
6
|
#
|
7
7
|
# This will print a message to stdout when using the human output format,
|
8
|
-
# and print to stderr when using the json output format.
|
8
|
+
# and print to stderr when using the json output format. Messages are
|
9
|
+
# also logged at the `debug` level. For more information about logs, see
|
10
|
+
# [Logs](logs.md).
|
9
11
|
#
|
10
12
|
# > **Note:** Not available in apply block
|
11
13
|
Puppet::Functions.create_function(:'out::verbose') do
|
@@ -25,7 +27,7 @@ Puppet::Functions.create_function(:'out::verbose') do
|
|
25
27
|
|
26
28
|
Puppet.lookup(:bolt_executor).tap do |executor|
|
27
29
|
executor.report_function_call(self.class.name)
|
28
|
-
executor.publish_event(type: :verbose, message: Bolt::Util::Format.stringify(message))
|
30
|
+
executor.publish_event(type: :verbose, message: Bolt::Util::Format.stringify(message), level: :debug)
|
29
31
|
end
|
30
32
|
|
31
33
|
nil
|
data/lib/bolt/analytics.rb
CHANGED
@@ -507,11 +507,14 @@ module Bolt
|
|
507
507
|
show
|
508
508
|
|
509
509
|
#{colorize(:cyan, 'Usage')}
|
510
|
-
bolt module show [options]
|
510
|
+
bolt module show [module name] [options]
|
511
511
|
|
512
512
|
#{colorize(:cyan, 'Description')}
|
513
513
|
List modules available to the Bolt project.
|
514
514
|
|
515
|
+
Providing the name of a module will display detailed documentation for
|
516
|
+
the module.
|
517
|
+
|
515
518
|
#{colorize(:cyan, 'Documentation')}
|
516
519
|
To learn more about Bolt modules, run 'bolt guide module'.
|
517
520
|
HELP
|
data/lib/bolt/cli.rb
CHANGED
@@ -494,7 +494,11 @@ module Bolt
|
|
494
494
|
when 'group'
|
495
495
|
list_groups
|
496
496
|
when 'module'
|
497
|
-
|
497
|
+
if options[:object]
|
498
|
+
show_module(options[:object])
|
499
|
+
else
|
500
|
+
list_modules
|
501
|
+
end
|
498
502
|
when 'plugin'
|
499
503
|
list_plugins
|
500
504
|
end
|
@@ -852,6 +856,10 @@ module Bolt
|
|
852
856
|
outputter.print_module_list(pal.list_modules)
|
853
857
|
end
|
854
858
|
|
859
|
+
def show_module(name)
|
860
|
+
outputter.print_module_info(**pal.show_module(name))
|
861
|
+
end
|
862
|
+
|
855
863
|
def list_plugins
|
856
864
|
outputter.print_plugin_list(plugins.list_plugins, pal.user_modulepath)
|
857
865
|
end
|
@@ -950,8 +958,9 @@ module Bolt
|
|
950
958
|
files = Dir.children(root_path).sort
|
951
959
|
|
952
960
|
files.each_with_object({}) do |file, guides|
|
953
|
-
next if file !~ /\.
|
954
|
-
|
961
|
+
next if file !~ /\.(yaml|yml)\z/
|
962
|
+
# The ".*" here removes any suffix
|
963
|
+
topic = File.basename(file, ".*")
|
955
964
|
guides[topic] = File.join(root_path, file)
|
956
965
|
end
|
957
966
|
rescue SystemCallError => e
|
@@ -961,7 +970,7 @@ module Bolt
|
|
961
970
|
|
962
971
|
# Display the list of available Bolt guides.
|
963
972
|
def list_topics
|
964
|
-
outputter.print_topics(guides.keys
|
973
|
+
outputter.print_topics(guides.keys)
|
965
974
|
0
|
966
975
|
end
|
967
976
|
|
@@ -971,12 +980,18 @@ module Bolt
|
|
971
980
|
analytics.event('Guide', 'known_topic', label: topic)
|
972
981
|
|
973
982
|
begin
|
974
|
-
guide =
|
983
|
+
guide = Bolt::Util.read_yaml_hash(guides[topic], 'guide')
|
975
984
|
rescue SystemCallError => e
|
976
985
|
raise Bolt::FileError("#{e.message}: unable to load guide page", filepath)
|
977
986
|
end
|
978
987
|
|
979
|
-
|
988
|
+
# Make sure both topic and guide keys are defined
|
989
|
+
unless (%w[topic guide] - guide.keys).empty?
|
990
|
+
msg = "Guide file #{guides[topic]} must have a 'topic' key and 'guide' key, but has #{guide.keys} keys."
|
991
|
+
raise Bolt::Error.new(msg, 'bolt/invalid-guide')
|
992
|
+
end
|
993
|
+
|
994
|
+
outputter.print_guide(**Bolt::Util.symbolize_top_level_keys(guide))
|
980
995
|
else
|
981
996
|
analytics.event('Guide', 'unknown_topic', label: topic)
|
982
997
|
outputter.print_message("Did not find guide for topic '#{topic}'.\n\n")
|
@@ -16,6 +16,18 @@ module Bolt
|
|
16
16
|
_default: false,
|
17
17
|
_example: true
|
18
18
|
},
|
19
|
+
"batch-mode" => {
|
20
|
+
type: [TrueClass, FalseClass],
|
21
|
+
description: "Whether to disable password querying. When set to `false`, SSH will fall back to "\
|
22
|
+
"prompting for a password if key authentication fails. This might cause Bolt to hang. "\
|
23
|
+
"To prevent Bolt from hanging, you can configure `ssh-command` to use an SSH utility "\
|
24
|
+
"such as sshpass that supports providing a password non-interactively. For more "\
|
25
|
+
"information, see [Providing a password non-interactively using "\
|
26
|
+
"`native-ssh`](troubleshooting.md#providing-a-password-non-interactively-using-native-ssh).",
|
27
|
+
_plugin: true,
|
28
|
+
_default: true,
|
29
|
+
_example: false
|
30
|
+
},
|
19
31
|
"bundled-ruby" => {
|
20
32
|
description: "Whether to use the Ruby bundled with Bolt packages for local targets.",
|
21
33
|
type: [TrueClass, FalseClass],
|
@@ -34,6 +34,7 @@ module Bolt
|
|
34
34
|
|
35
35
|
# Options available when using the native ssh transport
|
36
36
|
NATIVE_OPTIONS = %w[
|
37
|
+
batch-mode
|
37
38
|
cleanup
|
38
39
|
copy-command
|
39
40
|
host
|
@@ -49,6 +50,7 @@ module Bolt
|
|
49
50
|
].concat(RUN_AS_OPTIONS).sort.freeze
|
50
51
|
|
51
52
|
DEFAULTS = {
|
53
|
+
"batch-mode" => true,
|
52
54
|
"cleanup" => true,
|
53
55
|
"connect-timeout" => 10,
|
54
56
|
"disconnect-timeout" => 5,
|
@@ -124,6 +126,11 @@ module Bolt
|
|
124
126
|
msg = 'Cannot use native SSH transport with load-config set to false'
|
125
127
|
raise Bolt::ValidationError, msg
|
126
128
|
end
|
129
|
+
|
130
|
+
if !@config['batch-mode'] && !@config['ssh-command']
|
131
|
+
raise Bolt::ValidationError,
|
132
|
+
'Must set ssh-command when batch-mode is set to false'
|
133
|
+
end
|
127
134
|
end
|
128
135
|
end
|
129
136
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -381,8 +381,16 @@ module Bolt
|
|
381
381
|
# overloaded while also minimizing the Puppet lookups needed from plan
|
382
382
|
# functions
|
383
383
|
#
|
384
|
-
def create_future(scope: nil, name: nil, &block)
|
385
|
-
@fiber_executor.create_future(scope: scope, name: name, &block)
|
384
|
+
def create_future(plan_id:, scope: nil, name: nil, &block)
|
385
|
+
@fiber_executor.create_future(scope: scope, name: name, plan_id: plan_id, &block)
|
386
|
+
end
|
387
|
+
|
388
|
+
def get_current_future(fiber:)
|
389
|
+
@fiber_executor.get_current_future(fiber: fiber)
|
390
|
+
end
|
391
|
+
|
392
|
+
def get_current_plan_id(fiber:)
|
393
|
+
@fiber_executor.get_current_plan_id(fiber: fiber)
|
386
394
|
end
|
387
395
|
|
388
396
|
def plan_complete?
|
@@ -401,8 +409,8 @@ module Bolt
|
|
401
409
|
@fiber_executor.wait(futures, **opts)
|
402
410
|
end
|
403
411
|
|
404
|
-
def
|
405
|
-
@fiber_executor.
|
412
|
+
def get_futures_for_plan(plan_id:)
|
413
|
+
@fiber_executor.get_futures_for_plan(plan_id: plan_id)
|
406
414
|
end
|
407
415
|
|
408
416
|
# Execute a plan function concurrently. This function accepts the executor
|
data/lib/bolt/fiber_executor.rb
CHANGED
@@ -5,18 +5,19 @@ require 'bolt/plan_future'
|
|
5
5
|
|
6
6
|
module Bolt
|
7
7
|
class FiberExecutor
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :active_futures, :finished_futures
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@logger = Bolt::Logger.logger(self)
|
12
12
|
@id = 0
|
13
|
-
@
|
13
|
+
@active_futures = []
|
14
|
+
@finished_futures = []
|
14
15
|
end
|
15
16
|
|
16
17
|
# Whether there is more than one fiber running in parallel.
|
17
18
|
#
|
18
19
|
def in_parallel?
|
19
|
-
|
20
|
+
active_futures.length > 1
|
20
21
|
end
|
21
22
|
|
22
23
|
# Creates a new Puppet scope from the current Plan scope so that variables
|
@@ -24,7 +25,7 @@ module Bolt
|
|
24
25
|
# Then creates a new Fiber to execute the block, wraps the Fiber in a
|
25
26
|
# Bolt::PlanFuture, and returns the Bolt::PlanFuture.
|
26
27
|
#
|
27
|
-
def create_future(scope: nil, name: nil)
|
28
|
+
def create_future(plan_id:, scope: nil, name: nil)
|
28
29
|
newscope = nil
|
29
30
|
if scope
|
30
31
|
# Save existing variables to the new scope before starting the future
|
@@ -46,13 +47,16 @@ module Bolt
|
|
46
47
|
end
|
47
48
|
|
48
49
|
# PlanFutures are assigned an ID, which is just a global incrementing
|
49
|
-
# integer. The main plan should always have ID 0.
|
50
|
+
# integer. The main plan should always have ID 0. They also have a
|
51
|
+
# plan_id, which identifies which plan spawned them. This is used for
|
52
|
+
# tracking which Futures to wait on when `wait()` is called without
|
53
|
+
# arguments.
|
50
54
|
@id += 1
|
51
|
-
future = Bolt::PlanFuture.new(future, @id, name)
|
55
|
+
future = Bolt::PlanFuture.new(future, @id, name: name, plan_id: plan_id)
|
52
56
|
@logger.trace("Created future #{future.name}")
|
53
57
|
|
54
58
|
# Register the PlanFuture with the FiberExecutor to be executed
|
55
|
-
|
59
|
+
active_futures << future
|
56
60
|
future
|
57
61
|
end
|
58
62
|
|
@@ -63,7 +67,7 @@ module Bolt
|
|
63
67
|
# the PlanFuture and remove the PlanFuture from the FiberExecutor.
|
64
68
|
#
|
65
69
|
def round_robin
|
66
|
-
|
70
|
+
active_futures.each do |future|
|
67
71
|
# If the Fiber is still running and can be resumed, then resume it
|
68
72
|
@logger.trace("Checking future '#{future.name}'")
|
69
73
|
if future.alive?
|
@@ -78,19 +82,19 @@ module Bolt
|
|
78
82
|
|
79
83
|
# If the future errored and the main plan has already exited, log the
|
80
84
|
# error at warn level.
|
81
|
-
unless
|
85
|
+
unless active_futures.map(&:id).include?(0) || future.state == "done"
|
82
86
|
Bolt::Logger.warn('errored_futures', "Error in future '#{future.name}': #{future.value}")
|
83
87
|
end
|
84
88
|
|
85
89
|
# Remove the PlanFuture from the FiberExecutor.
|
86
|
-
|
90
|
+
finished_futures.push(active_futures.delete(future))
|
87
91
|
end
|
88
92
|
|
89
93
|
# If the Fiber immediately returned or if the Fiber is blocking on a
|
90
94
|
# `wait` call, Bolt should pause for long enough that something can
|
91
95
|
# execute before checking again. This mitigates CPU
|
92
96
|
# thrashing.
|
93
|
-
return unless
|
97
|
+
return unless active_futures.all? { |f| %i[returned_immediately unfinished].include?(f.value) }
|
94
98
|
@logger.trace("Nothing can be resumed. Rechecking in 0.5 seconds.")
|
95
99
|
|
96
100
|
sleep(0.5)
|
@@ -101,12 +105,53 @@ module Bolt
|
|
101
105
|
# Bolt can exit.
|
102
106
|
#
|
103
107
|
def plan_complete?
|
104
|
-
|
108
|
+
active_futures.empty?
|
109
|
+
end
|
110
|
+
|
111
|
+
def all_futures
|
112
|
+
active_futures + finished_futures
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get the PlanFuture object that is currently executing
|
116
|
+
#
|
117
|
+
def get_current_future(fiber:)
|
118
|
+
all_futures.select { |f| f.fiber == fiber }.first
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get the plan invocation ID for the PlanFuture that is currently executing
|
122
|
+
#
|
123
|
+
def get_current_plan_id(fiber:)
|
124
|
+
get_current_future(fiber: fiber).current_plan
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get the Future objects associated with a particular plan invocation.
|
128
|
+
#
|
129
|
+
def get_futures_for_plan(plan_id:)
|
130
|
+
all_futures.select { |f| f.original_plan == plan_id }
|
105
131
|
end
|
106
132
|
|
107
133
|
# Block until the provided PlanFuture objects have finished, or the timeout is reached.
|
108
134
|
#
|
109
135
|
def wait(futures, timeout: nil, catch_errors: false, **_kwargs)
|
136
|
+
if futures.nil?
|
137
|
+
results = []
|
138
|
+
plan_id = get_current_plan_id(fiber: Fiber.current)
|
139
|
+
# Recollect the futures for this plan until all of the futures have
|
140
|
+
# finished. This ensures that we include futures created inside of
|
141
|
+
# futures being waited on.
|
142
|
+
until (futures = get_futures_for_plan(plan_id: plan_id)).map(&:alive?).none?
|
143
|
+
if futures.map(&:fiber).include?(Fiber.current)
|
144
|
+
msg = "The wait() function cannot be called with no arguments inside a "\
|
145
|
+
"background block in the same plan."
|
146
|
+
raise Bolt::Error.new(msg, 'bolt/infinite-wait')
|
147
|
+
end
|
148
|
+
# Wait for all the futures we know about so far before recollecting
|
149
|
+
# Futures for the plan and waiting again
|
150
|
+
results = wait(futures, timeout: timeout, catch_errors: catch_errors)
|
151
|
+
end
|
152
|
+
return results
|
153
|
+
end
|
154
|
+
|
110
155
|
if timeout.nil?
|
111
156
|
Fiber.yield(:unfinished) until futures.map(&:alive?).none?
|
112
157
|
else
|