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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f04dc4247f20f08ee8cfe33500b7ba9477963290ab5303b5f2c72ba60c529e1d
4
- data.tar.gz: 4ebdf00f011c6d3de0e1b8e47f0763bfab04857fe4611391cf9bc800597c140e
3
+ metadata.gz: 1c6c3fbb5bdf8e8b53493a029b46560616f2ea310092823f863de14ad1fc836b
4
+ data.tar.gz: f4a3193539da70d75ff36f39a126d6f56710c91e2ff54c465a1477685b6fb19a
5
5
  SHA512:
6
- metadata.gz: 5615936c79c5f157142e1703a83c9cf28e6ef2b91c090826130f57ac9dc636888847ddd87f01374053c768cf9d4e87a929f654126f6b37375ea1ae1cdc670530
7
- data.tar.gz: 270c4669aff29a221c9a7711b7bac715bf6e821a01318f45866fdbadbf77e2e93424722db13b10812dc777d4dbc78c116be992f3a908365bc392820cbb6eeb0b
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
- 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]
@@ -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
- 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)
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
- 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)
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
- 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
@@ -13,5 +13,6 @@ type Boltlib::PlanResult = Variant[Boolean,
13
13
  Target,
14
14
  ResourceInstance,
15
15
  ContainerResult,
16
+ Future,
16
17
  Array[Boltlib::PlanResult],
17
18
  Hash[String, Boltlib::PlanResult]]
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
@@ -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
- parent = Pathname.new(mod.path).parent
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(parent)
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
@@ -11,7 +11,7 @@ module Bolt
11
11
  cleanup
12
12
  remote
13
13
  tmpdir
14
- ].freeze
14
+ ].concat(RUN_AS_OPTIONS).sort.freeze
15
15
 
16
16
  DEFAULTS = {
17
17
  'cleanup' => true,
@@ -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("Parallel block returned an invalid result: #{result_str}",
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/result_set'
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, :in_parallel, :future
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
- def create_yarn(scope, block, object, index)
377
- fiber = Fiber.new do
378
- # Create the new scope
379
- newscope = Puppet::Parser::Scope.new(scope.compiler)
380
- local = Puppet::Parser::Scope::LocalScope.new
381
-
382
- # Compress the current scopes into a single vars hash to add to the new scope
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
- # Validate the result is a PlanResult
401
- unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
402
- raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
403
- end
384
+ def plan_complete?
385
+ @fiber_executor.plan_complete?
386
+ end
404
387
 
405
- result
406
- rescue Puppet::PreformattedError => e
407
- if e.cause.is_a?(Bolt::Error)
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
- Bolt::Yarn.new(fiber, index)
392
+ def in_parallel?
393
+ @fiber_executor.in_parallel?
416
394
  end
417
395
 
418
- def handle_event(event)
419
- case event[:type]
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 round_robin(skein)
426
- subscribe(self, [:node_result])
427
- results = Array.new(skein.length)
428
- @in_parallel = true
429
- publish_event(type: :stop_spin)
400
+ def plan_futures
401
+ @fiber_executor.plan_futures
402
+ end
430
403
 
431
- until skein.empty?
432
- @thread_completed = false
433
- r = nil
434
-
435
- skein.each do |yarn|
436
- if yarn.alive?
437
- publish_event(type: :stop_spin)
438
- r = yarn.resume
439
- else
440
- results[yarn.index] = yarn.value
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
- next unless r == 'unfinished'
446
- sleep(0.1) until @thread_completed || skein.empty?
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
- publish_event(type: :stop_spin)
450
- @in_parallel = false
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 callt this function without resolving targets this will result
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/container_result'
3
4
  require 'bolt/pal'
4
5
 
5
6
  module Bolt
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 = nil, inventory = nil, pdb_client = nil, applicator = nil)
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
- r = compiler.call_function('run_plan', plan_name, params.merge('_bolt_api_call' => true))
613
- Bolt::PlanResult.from_pcore(r, 'success')
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
@@ -23,6 +23,10 @@ module Bolt
23
23
  Bolt::Shell::Bash.new(target, self)
24
24
  end
25
25
 
26
+ def reset_cwd?
27
+ true
28
+ end
29
+
26
30
  def container_id
27
31
  "#{@target.transport_config['remote']}:#{@target.host}"
28
32
  end
@@ -30,6 +30,10 @@ module Bolt
30
30
  end
31
31
  end
32
32
 
33
+ def reset_cwd?
34
+ true
35
+ end
36
+
33
37
  def connect
34
38
  # We don't actually have a connection, but we do need to
35
39
  # check that the container exists and is running.
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '3.8.1'
4
+ VERSION = '3.9.0'
5
5
  end
@@ -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, :in_parallel, :transports, :future
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
- # Evaluates a `parallelize()` block and returns the result. Normally,
261
- # Bolt's executor wraps this in a Yarn for each object passed to the
262
- # `parallelize()` function, and then executes them in parallel before
263
- # returning the result from the block. However, in BoltSpec the block is
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
- # Validate the result is a PlanResult
291
- unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
292
- raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
293
- end
265
+ def in_parallel?
266
+ false
267
+ end
294
268
 
295
- result
296
- rescue Puppet::PreformattedError => e
297
- if e.cause.is_a?(Bolt::Error)
298
- e.cause
299
- else
300
- raise e
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
- # BoltSpec already evaluated the `parallelize()` block for each object
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.8.1
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-17 00:00:00.000000000 Z
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