bolt 3.8.0 → 3.10.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.

Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  3. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +9 -6
  4. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +61 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +5 -9
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +28 -13
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +5 -15
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +5 -17
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +8 -17
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -15
  11. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +5 -17
  12. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +91 -0
  13. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  14. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -1
  15. data/bolt-modules/file/lib/puppet/functions/file/read.rb +1 -1
  16. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +1 -1
  17. data/guides/debugging.txt +28 -0
  18. data/guides/inventory.txt +5 -0
  19. data/lib/bolt/applicator.rb +3 -2
  20. data/lib/bolt/bolt_option_parser.rb +51 -4
  21. data/lib/bolt/cli.rb +38 -10
  22. data/lib/bolt/config/options.rb +2 -1
  23. data/lib/bolt/config/transport/docker.rb +1 -1
  24. data/lib/bolt/config/transport/lxd.rb +1 -1
  25. data/lib/bolt/config/transport/options.rb +2 -1
  26. data/lib/bolt/config/transport/podman.rb +1 -1
  27. data/lib/bolt/error.rb +11 -1
  28. data/lib/bolt/executor.rb +55 -72
  29. data/lib/bolt/fiber_executor.rb +141 -0
  30. data/lib/bolt/logger.rb +1 -1
  31. data/lib/bolt/module_installer/specs.rb +1 -1
  32. data/lib/bolt/outputter/human.rb +33 -0
  33. data/lib/bolt/outputter/json.rb +9 -0
  34. data/lib/bolt/pal.rb +117 -17
  35. data/lib/bolt/plan_future.rb +66 -0
  36. data/lib/bolt/plugin.rb +38 -0
  37. data/lib/bolt/plugin/env_var.rb +8 -1
  38. data/lib/bolt/plugin/module.rb +1 -1
  39. data/lib/bolt/plugin/prompt.rb +8 -1
  40. data/lib/bolt/plugin/puppet_connect_data.rb +8 -1
  41. data/lib/bolt/plugin/puppetdb.rb +7 -1
  42. data/lib/bolt/plugin/task.rb +9 -1
  43. data/lib/bolt/project.rb +2 -1
  44. data/lib/bolt/task.rb +7 -0
  45. data/lib/bolt/transport/docker/connection.rb +5 -2
  46. data/lib/bolt/transport/lxd/connection.rb +4 -0
  47. data/lib/bolt/transport/podman/connection.rb +4 -0
  48. data/lib/bolt/util.rb +13 -1
  49. data/lib/bolt/version.rb +1 -1
  50. data/lib/bolt_server/config.rb +1 -1
  51. data/lib/bolt_server/request_error.rb +11 -0
  52. data/lib/bolt_server/transport_app.rb +133 -95
  53. data/lib/bolt_spec/plans/mock_executor.rb +40 -45
  54. data/lib/bolt_spec/run.rb +4 -1
  55. data/modules/puppet_connect/plans/test_input_data.pp +8 -3
  56. data/resources/bolt_bash_completion.sh +214 -0
  57. metadata +10 -3
  58. data/lib/bolt/yarn.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f4c856f3322c88dabb51efe39bc7373d96e33f6274785188900e8fbb5488436
4
- data.tar.gz: 5e5da3f150093febfc2b6d7e343e9995ea33b044fb4941ed9229944e4735aa72
3
+ metadata.gz: fa1075046a81e0596ff3f0483dc7b7a5913a073dd6e998f66c70687049531e74
4
+ data.tar.gz: 9cdb2d3503e1bd2d84fa7c0fc846e3f4d2293780f648d9d07d549fd50d2b1a51
5
5
  SHA512:
6
- metadata.gz: 8f88f29b98a7bab28a4bbae5e860bb4c94fca20dd994241b71f2e5327934a92db1370c77fdb03d5f9a033e626b726e379e3eec510f0316b67b0807aa86920b67
7
- data.tar.gz: 8cf9a3bbfde78b7930089a7ada109e03bba5439ea8ef3049d3fa58bc589914881969be783fe667ef5c238613e126c7f79d7dd0db30fcd2f5d9147999086d1df7
6
+ metadata.gz: 4b2f636ec7f5f038b69cd5c4f82e9fd93e59521035e15fa678cef67501a4e00287e29f40ce1672d2f5db0fa4972cbc26d9763f62480612e30ebcfae404d12a1f
7
+ data.tar.gz: f87b3966ea10cf5a5fe46937e9a47b73d4553924bff00ee578f6c659a70fe82864e2cc78b76a685dfb9e3d4a4e5a3fb7bea136730bf6b5160454e665f4f02ea8
@@ -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
@@ -16,6 +16,7 @@ Puppet::Functions.create_function(:apply_prep) do
16
16
  # @param targets A pattern or array of patterns identifying a set of targets.
17
17
  # @param options Options hash.
18
18
  # @option options [Array] _required_modules An array of modules to sync to the target.
19
+ # @option options [String] _run_as User to run as using privilege escalation.
19
20
  # @return [nil]
20
21
  # @example Prepare targets by name.
21
22
  # apply_prep('target1,target2')
@@ -71,7 +72,8 @@ Puppet::Functions.create_function(:apply_prep) do
71
72
  .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'apply_prep')
72
73
  end
73
74
 
74
- options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
75
+ # Unfreeze this
76
+ options = options.slice(*%w[_run_as _required_modules])
75
77
 
76
78
  applicator = Puppet.lookup(:apply_executor)
77
79
 
@@ -79,14 +81,14 @@ Puppet::Functions.create_function(:apply_prep) do
79
81
 
80
82
  targets = inventory.get_targets(target_spec)
81
83
 
82
- required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
83
- if required_modules&.any?
84
+ required_modules = options.delete('_required_modules').to_a
85
+ if required_modules.any?
84
86
  Puppet.debug("Syncing only required modules: #{required_modules.join(',')}.")
85
87
  end
86
88
 
87
89
  # Gather facts, including custom facts
88
90
  plugins = applicator.build_plugin_tarball do |mod|
89
- next unless required_modules.nil? || required_modules.include?(mod.name)
91
+ next unless required_modules.empty? || required_modules.include?(mod.name)
90
92
  search_dirs = []
91
93
  search_dirs << mod.plugins if mod.plugins?
92
94
  search_dirs << mod.pluginfacts if mod.pluginfacts?
@@ -107,8 +109,9 @@ Puppet::Functions.create_function(:apply_prep) do
107
109
  opts = t.plugin_hooks&.fetch('puppet_library').dup
108
110
  plugin_name = opts.delete('plugin')
109
111
  hook = inventory.plugins.get_hook(plugin_name, :puppet_library)
112
+ # Give plan function options precedence over inventory options
110
113
  { 'target' => t,
111
- 'hook_proc' => hook.call(opts, t, self) }
114
+ 'hook_proc' => hook.call(opts.merge(options), t, self) }
112
115
  rescue StandardError => e
113
116
  Bolt::Result.from_exception(t, e)
114
117
  end
@@ -132,7 +135,7 @@ Puppet::Functions.create_function(:apply_prep) do
132
135
 
133
136
  task = applicator.custom_facts_task
134
137
  arguments = { 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins) }
135
- results = executor.run_task(targets, task, arguments)
138
+ results = run_task(targets, task, arguments, options)
136
139
 
137
140
  # TODO: Standardize RunFailure type with error above
138
141
  raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
@@ -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
- r = if executor.in_parallel
116
- require 'concurrent'
117
- require 'fiber'
118
- future = Concurrent::Future.execute do
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, Puppet::Pops::PuppetStack.top_of_stack)
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
- skein = data.each_with_index.map do |object, index|
39
- executor.create_yarn(scope, block, object, index)
40
- end
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
- result = executor.round_robin(skein)
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
- failed_indices = result.each_index.select do |i|
45
- result[i].is_a?(Bolt::Error)
46
- end
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
- # TODO: Inner catch errors block?
49
- if failed_indices.any?
50
- raise Bolt::ParallelFailure.new(result, failed_indices)
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
- result
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
- r = if executor.in_parallel
91
- require 'concurrent'
92
- require 'fiber'
93
- future = Concurrent::Future.execute do
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
- r = if executor.in_parallel
134
- require 'concurrent'
135
- require 'fiber'
136
- future = Concurrent::Future.execute do
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]
@@ -130,28 +130,19 @@ Puppet::Functions.create_function(:run_task) do
130
130
  end
131
131
  end
132
132
 
133
+ # Report whether the task was run in noop mode.
134
+ executor.report_noop_mode(executor.noop || options[:noop])
135
+
133
136
  if targets.empty?
134
137
  Bolt::ResultSet.new([])
135
138
  else
136
- result = if executor.in_parallel
137
- require 'concurrent'
138
- require 'fiber'
139
- future = Concurrent::Future.execute do
140
- executor.run_task(targets,
141
- task,
142
- params,
143
- options,
144
- Puppet::Pops::PuppetStack.top_of_stack)
139
+ file_line = Puppet::Pops::PuppetStack.top_of_stack
140
+ result = if executor.in_parallel?
141
+ executor.run_in_thread do
142
+ executor.run_task(targets, task, params, options, file_line)
145
143
  end
146
-
147
- Fiber.yield('unfinished') while future.incomplete?
148
- future.value || future.reason
149
144
  else
150
- executor.run_task(targets,
151
- task,
152
- params,
153
- options,
154
- Puppet::Pops::PuppetStack.top_of_stack)
145
+ executor.run_task(targets, task, params, options, file_line)
155
146
  end
156
147
 
157
148
  if !result.ok && !options[:catch_errors]
@@ -175,28 +175,21 @@ Puppet::Functions.create_function(:run_task_with) do
175
175
  end
176
176
  end
177
177
 
178
+ # Report whether the task was run in noop mode.
179
+ executor.report_noop_mode(executor.noop || options[:noop])
180
+
178
181
  if targets.empty?
179
182
  Bolt::ResultSet.new([])
180
183
  else
181
184
  # Combine the results from the task run with any failing results that were
182
185
  # generated earlier when creating the target mapping
183
- task_result = if executor.in_parallel
184
- require 'concurrent'
185
- require 'fiber'
186
- future = Concurrent::Future.execute do
187
- executor.run_task_with(target_mapping,
188
- task,
189
- options,
190
- Puppet::Pops::PuppetStack.top_of_stack)
186
+ file_line = Puppet::Pops::PuppetStack.top_of_stack
187
+ task_result = if executor.in_parallel?
188
+ executor.run_in_thread do
189
+ executor.run_task_with(target_mapping, task, options, file_line)
191
190
  end
192
-
193
- Fiber.yield('unfinished') while future.incomplete?
194
- future.value || future.reason
195
191
  else
196
- executor.run_task_with(target_mapping,
197
- task,
198
- options,
199
- Puppet::Pops::PuppetStack.top_of_stack)
192
+ executor.run_task_with(target_mapping, task, options, file_line)
200
193
  end
201
194
  result = Bolt::ResultSet.new(task_result.results + error_set)
202
195
 
@@ -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
- r = if executor.in_parallel
91
- require 'concurrent'
92
- require 'fiber'
93
- future = Concurrent::Future.execute do
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