bolt 3.8.1 → 3.11.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -4
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  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/guides/debugging.txt +28 -0
  15. data/guides/inventory.txt +5 -0
  16. data/lib/bolt/applicator.rb +3 -2
  17. data/lib/bolt/bolt_option_parser.rb +51 -4
  18. data/lib/bolt/cli.rb +38 -9
  19. data/lib/bolt/config/transport/docker.rb +1 -1
  20. data/lib/bolt/config/transport/lxd.rb +1 -1
  21. data/lib/bolt/config/transport/podman.rb +1 -1
  22. data/lib/bolt/error.rb +11 -1
  23. data/lib/bolt/executor.rb +55 -72
  24. data/lib/bolt/fiber_executor.rb +141 -0
  25. data/lib/bolt/module_installer/installer.rb +1 -1
  26. data/lib/bolt/outputter/human.rb +46 -2
  27. data/lib/bolt/outputter/json.rb +9 -0
  28. data/lib/bolt/pal.rb +117 -17
  29. data/lib/bolt/plan_future.rb +66 -0
  30. data/lib/bolt/plugin.rb +38 -0
  31. data/lib/bolt/plugin/env_var.rb +8 -1
  32. data/lib/bolt/plugin/module.rb +1 -1
  33. data/lib/bolt/plugin/prompt.rb +8 -1
  34. data/lib/bolt/plugin/puppet_connect_data.rb +8 -1
  35. data/lib/bolt/plugin/puppetdb.rb +7 -1
  36. data/lib/bolt/plugin/task.rb +9 -1
  37. data/lib/bolt/project.rb +2 -1
  38. data/lib/bolt/task.rb +7 -0
  39. data/lib/bolt/transport/docker/connection.rb +5 -2
  40. data/lib/bolt/transport/lxd/connection.rb +4 -0
  41. data/lib/bolt/transport/podman/connection.rb +4 -0
  42. data/lib/bolt/version.rb +1 -1
  43. data/lib/bolt_server/config.rb +1 -1
  44. data/lib/bolt_server/request_error.rb +11 -0
  45. data/lib/bolt_server/transport_app.rb +133 -95
  46. data/lib/bolt_spec/plans/mock_executor.rb +40 -45
  47. data/lib/bolt_spec/run.rb +4 -1
  48. data/modules/puppet_connect/plans/test_input_data.pp +8 -3
  49. data/resources/bolt_bash_completion.sh +214 -0
  50. metadata +10 -3
  51. data/lib/bolt/yarn.rb +0 -23
@@ -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]]
@@ -0,0 +1,28 @@
1
+ TOPIC
2
+ debugging
3
+
4
+ DESCRIPTION
5
+ When Bolt isn't behaving as expected, there are a few helpful commands and
6
+ logs that can help identify common issues. The first place to look is in
7
+ `<PROJECT>/bolt-debug.log`, which contains debug-level logs from the last Bolt
8
+ run. This log file includes where the Bolt project was loaded from, the
9
+ location of any configuration or inventory files that were loaded, and the
10
+ modulepath that modules were loaded from.
11
+
12
+ If you're having issues with loading targets or target configuration, you
13
+ can see the list of resolved Bolt target names by running `bolt inventory
14
+ show` on *nix systems or `Get-BoltInventory` in PowerShell. To see the
15
+ resolved configuration for each target, run the command with the `--detail` or
16
+ `-Detail` options.
17
+
18
+ Lastly, if you're having trouble loading Bolt content you can use `bolt
19
+ module show` on *nix systems or `Get-BoltModule` in PowerShell to see the list
20
+ of loaded modules, including where they were loaded from. You can also use
21
+ `bolt task show` or `Get-BoltTask` to list loaded tasks, and `bolt plan show`
22
+ or `Get-BoltPlan` to list loaded plans.
23
+
24
+ Visit the linked documentation for more in-depth troubleshooting help for
25
+ specific issues.
26
+
27
+ DOCUMENTATION
28
+ https://pup.pt/bolt-troubleshooting
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)
@@ -67,7 +67,7 @@ module Bolt
67
67
  { flags: OPTIONS[:global] + %w[format],
68
68
  banner: GUIDE_HELP }
69
69
  when 'lookup'
70
- { flags: ACTION_OPTS + %w[hiera-config],
70
+ { flags: ACTION_OPTS + %w[hiera-config plan-hierarchy],
71
71
  banner: LOOKUP_HELP }
72
72
  when 'module'
73
73
  case action
@@ -105,6 +105,15 @@ module Bolt
105
105
  { flags: OPTIONS[:global],
106
106
  banner: PLAN_HELP }
107
107
  end
108
+ when 'plugin'
109
+ case action
110
+ when 'show'
111
+ { flags: OPTIONS[:global] + %w[color format modulepath project],
112
+ banner: PLUGIN_SHOW_HELP }
113
+ else
114
+ { flags: OPTIONS[:global],
115
+ banner: PLUGIN_HELP }
116
+ end
108
117
  when 'project'
109
118
  case action
110
119
  when 'init'
@@ -192,6 +201,7 @@ module Bolt
192
201
  module Manage Bolt project modules
193
202
  lookup Look up a value with Hiera
194
203
  plan Convert, create, show, and run Bolt plans
204
+ plugin Show available plugins
195
205
  project Create and migrate Bolt projects
196
206
  script Upload a local script and run it remotely
197
207
  secret Create encryption keys and encrypt and decrypt values
@@ -407,7 +417,7 @@ module Bolt
407
417
  lookup
408
418
 
409
419
  #{colorize(:cyan, 'Usage')}
410
- bolt lookup <key> {--targets TARGETS | --query QUERY | --rerun FILTER}
420
+ bolt lookup <key> {--targets TARGETS | --query QUERY | --rerun FILTER | --plan-hierarchy}
411
421
  [options]
412
422
 
413
423
  #{colorize(:cyan, 'Description')}
@@ -418,6 +428,7 @@ module Bolt
418
428
 
419
429
  #{colorize(:cyan, 'Examples')}
420
430
  bolt lookup password --targets servers
431
+ bolt lookup password --plan-hierarchy variable=value
421
432
  HELP
422
433
 
423
434
  MODULE_HELP = <<~HELP
@@ -608,6 +619,37 @@ module Bolt
608
619
  bolt plan show aggregate::count
609
620
  HELP
610
621
 
622
+ PLUGIN_HELP = <<~HELP
623
+ #{colorize(:cyan, 'Name')}
624
+ plugin
625
+
626
+ #{colorize(:cyan, 'Usage')}
627
+ bolt plugin <action> [options]
628
+
629
+ #{colorize(:cyan, 'Description')}
630
+ Show available plugins.
631
+
632
+ #{colorize(:cyan, 'Documentation')}
633
+ Learn more about Bolt plugins at https://pup.pt/bolt-plugins.
634
+
635
+ #{colorize(:cyan, 'Actions')}
636
+ show Show available plugins
637
+ HELP
638
+
639
+ PLUGIN_SHOW_HELP = <<~HELP
640
+ #{colorize(:cyan, 'Name')}
641
+ show
642
+
643
+ #{colorize(:cyan, 'Usage')}
644
+ bolt plugin show [options]
645
+
646
+ #{colorize(:cyan, 'Description')}
647
+ Show available plugins.
648
+
649
+ #{colorize(:cyan, 'Documentation')}
650
+ Learn more about Bolt plugins at https://pup.pt/bolt-plugins.
651
+ HELP
652
+
611
653
  PROJECT_HELP = <<~HELP
612
654
  #{colorize(:cyan, 'Name')}
613
655
  project
@@ -676,7 +718,7 @@ module Bolt
676
718
  Run a script on the specified targets.
677
719
 
678
720
  #{colorize(:cyan, 'Documentation')}
679
- Learn more about running scrips at https://pup.pt/bolt-commands.
721
+ Learn more about running scripts at https://pup.pt/bolt-commands.
680
722
 
681
723
  #{colorize(:cyan, 'Actions')}
682
724
  run Run a script on the specified targets.
@@ -698,7 +740,7 @@ module Bolt
698
740
  be quoted.
699
741
 
700
742
  #{colorize(:cyan, 'Documentation')}
701
- Learn more about running scrips at https://pup.pt/bolt-commands.
743
+ Learn more about running scripts at https://pup.pt/bolt-commands.
702
744
 
703
745
  #{colorize(:cyan, 'Examples')}
704
746
  bolt script run myscript.sh 'echo hello' --targets target1,target2
@@ -988,6 +1030,11 @@ module Bolt
988
1030
  @options[:resolve] = resolve
989
1031
  end
990
1032
 
1033
+ separator "\n#{self.class.colorize(:cyan, 'Lookup options')}"
1034
+ define('--plan-hierarchy', 'Look up a value with Hiera in the context of a specific plan.') do |_|
1035
+ @options[:plan_hierarchy] = true
1036
+ end
1037
+
991
1038
  separator "\n#{self.class.colorize(:cyan, 'Plan options')}"
992
1039
  define('--pp', 'Create a new Puppet language plan.') do |_|
993
1040
  @options[:puppet] = true
data/lib/bolt/cli.rb CHANGED
@@ -42,6 +42,7 @@ module Bolt
42
42
  'lookup' => %w[],
43
43
  'module' => %w[add generate-types install show],
44
44
  'plan' => %w[show run convert new],
45
+ 'plugin' => %w[show],
45
46
  'project' => %w[init migrate],
46
47
  'script' => %w[run],
47
48
  'secret' => %w[encrypt decrypt createkeys],
@@ -354,12 +355,18 @@ module Bolt
354
355
  "the project, run '#{command} #{options[:object]}'."
355
356
  end
356
357
 
357
- if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
358
+ if !%w[file script lookup].include?(options[:subcommand]) &&
358
359
  !options[:leftovers].empty?
359
360
  raise Bolt::CLIError,
360
361
  "Unknown argument(s) #{options[:leftovers].join(', ')}"
361
362
  end
362
363
 
364
+ target_opts = options.keys.select { |opt| TARGETING_OPTIONS.include?(opt) }
365
+ if options[:subcommand] == 'lookup' &&
366
+ target_opts.any? && options[:plan_hierarchy]
367
+ raise Bolt::CLIError, "The 'lookup' command accepts either targeting option OR --plan-hierarchy."
368
+ end
369
+
363
370
  if options[:noop] &&
364
371
  !(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
365
372
  raise Bolt::CLIError,
@@ -432,10 +439,11 @@ module Bolt
432
439
  end
433
440
 
434
441
  # Initialize inventory and targets. Errors here are better to catch early.
435
- # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
442
+ # options[:target_args] will contain a string/array version of the targeting options this is passed to plans
436
443
  # options[:targets] will contain a resolved set of Target objects
437
444
  unless %w[guide module project secret].include?(options[:subcommand]) ||
438
- %w[convert new show].include?(options[:action])
445
+ %w[convert new show].include?(options[:action]) ||
446
+ options[:plan_hierarchy]
439
447
  update_targets(options)
440
448
  end
441
449
 
@@ -487,6 +495,8 @@ module Bolt
487
495
  list_groups
488
496
  when 'module'
489
497
  list_modules
498
+ when 'plugin'
499
+ list_plugins
490
500
  end
491
501
  return 0
492
502
  when 'convert'
@@ -516,7 +526,13 @@ module Bolt
516
526
  code = Bolt::ProjectManager.new(config, outputter, pal).migrate
517
527
  end
518
528
  when 'lookup'
519
- code = lookup(options[:object], options[:targets])
529
+ plan_vars = Hash[options[:leftovers].map { |a| a.split('=', 2) }]
530
+ # Validate functions verifies one of these was passed
531
+ if options[:targets]
532
+ code = lookup(options[:object], options[:targets], plan_vars: plan_vars)
533
+ elsif options[:plan_hierarchy]
534
+ code = plan_lookup(options[:object], plan_vars: plan_vars)
535
+ end
520
536
  when 'plan'
521
537
  case options[:action]
522
538
  when 'new'
@@ -630,7 +646,7 @@ module Bolt
630
646
  end
631
647
 
632
648
  def list_tasks
633
- tasks = filter_content(pal.list_tasks(filter_content: true), options[:filter])
649
+ tasks = filter_content(pal.list_tasks_with_cache(filter_content: true), options[:filter])
634
650
  outputter.print_tasks(tasks, pal.user_modulepath)
635
651
  end
636
652
 
@@ -694,10 +710,19 @@ module Bolt
694
710
  outputter.print_groups(inventory.group_names.sort, inventory.source, config.default_inventoryfile)
695
711
  end
696
712
 
713
+ # Looks up a value with Hiera as if in a plan outside an apply block, using
714
+ # provided variable values for interpolations
715
+ #
716
+ def plan_lookup(key, plan_vars: {})
717
+ result = pal.plan_hierarchy_lookup(key, plan_vars: plan_vars)
718
+ outputter.print_plan_lookup(result)
719
+ 0
720
+ end
721
+
697
722
  # Looks up a value with Hiera, using targets as the contexts to perform the
698
- # look ups in.
723
+ # look ups in. This should return the same value as a lookup in an apply block.
699
724
  #
700
- def lookup(key, targets)
725
+ def lookup(key, targets, plan_vars: {})
701
726
  executor = Bolt::Executor.new(
702
727
  config.concurrency,
703
728
  analytics,
@@ -706,7 +731,7 @@ module Bolt
706
731
  config.future
707
732
  )
708
733
 
709
- executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
734
+ executor.subscribe(outputter) if config.format == 'human'
710
735
  executor.subscribe(log_outputter)
711
736
  executor.publish_event(type: :plan_start, plan: nil)
712
737
 
@@ -716,7 +741,7 @@ module Bolt
716
741
  targets,
717
742
  inventory,
718
743
  executor,
719
- config.concurrency
744
+ plan_vars: plan_vars
720
745
  )
721
746
  end
722
747
 
@@ -827,6 +852,10 @@ module Bolt
827
852
  outputter.print_module_list(pal.list_modules)
828
853
  end
829
854
 
855
+ def list_plugins
856
+ outputter.print_plugin_list(plugins.list_plugins, pal.user_modulepath)
857
+ end
858
+
830
859
  def generate_types
831
860
  # generate_types will surface a nice error with helpful message if it fails
832
861
  pal.generate_types(cache: true)
@@ -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
@@ -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
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)
@@ -242,6 +242,10 @@ module Bolt
242
242
  @analytics&.event('Plan', plan_function, label: label)
243
243
  end
244
244
 
245
+ def report_noop_mode(noop)
246
+ @analytics&.event('Task', 'noop', label: (!!noop).to_s)
247
+ end
248
+
245
249
  def report_apply(statement_count, resource_counts)
246
250
  data = { statement_count: statement_count }
247
251
 
@@ -373,83 +377,62 @@ module Bolt
373
377
  plan.call_by_name_with_scope(scope, params, true)
374
378
  end
375
379
 
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)
380
+ # Call into FiberExecutor to avoid this class getting
381
+ # overloaded while also minimizing the Puppet lookups needed from plan
382
+ # functions
383
+ #
384
+ def create_future(scope: nil, name: nil, &block)
385
+ @fiber_executor.create_future(scope: scope, name: name, &block)
386
+ end
399
387
 
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
388
+ def plan_complete?
389
+ @fiber_executor.plan_complete?
390
+ end
404
391
 
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
392
+ def round_robin
393
+ @fiber_executor.round_robin
394
+ end
414
395
 
415
- Bolt::Yarn.new(fiber, index)
396
+ def in_parallel?
397
+ @fiber_executor.in_parallel?
416
398
  end
417
399
 
418
- def handle_event(event)
419
- case event[:type]
420
- when :node_result
421
- @thread_completed = true
422
- end
400
+ def wait(futures, **opts)
401
+ @fiber_executor.wait(futures, **opts)
423
402
  end
424
403
 
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)
404
+ def plan_futures
405
+ @fiber_executor.plan_futures
406
+ end
430
407
 
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
408
+ # Execute a plan function concurrently. This function accepts the executor
409
+ # function to be run and the parameters to pass to it, and returns the
410
+ # result of running the executor function.
411
+ #
412
+ def run_in_thread
413
+ require 'concurrent'
414
+ require 'fiber'
415
+ future = Concurrent::Future.execute do
416
+ yield
417
+ end
444
418
 
445
- next unless r == 'unfinished'
446
- sleep(0.1) until @thread_completed || skein.empty?
419
+ # Used to track how often we resume the same executor function
420
+ still_running = 0
421
+ # While the thread is still running
422
+ while future.incomplete?
423
+ # If the Fiber gets resumed, increment the resume tracker. This means
424
+ # the tracker starts at 1 since it needs to increment before yielding,
425
+ # since it can't yield then increment.
426
+ still_running += 1
427
+ # If the Fiber has been resumed before, still_running will be 2 or
428
+ # more. Yield different values for when the same Fiber is resumed
429
+ # multiple times and when it's resumed the first time in order to know
430
+ # if progress was made in the plan.
431
+ Fiber.yield(still_running < 2 ? :something_happened : :returned_immediately)
447
432
  end
448
433
 
449
- publish_event(type: :stop_spin)
450
- @in_parallel = false
451
- unsubscribe(self, [:node_result])
452
- results
434
+ # Once the thread completes, return the result.
435
+ future.value || future.reason
453
436
  end
454
437
 
455
438
  class TimeoutError < RuntimeError; end
@@ -519,7 +502,7 @@ module Bolt
519
502
  # coupled with the orchestrator transport since the transport behaves
520
503
  # differently when a plan is running. In order to limit how much this
521
504
  # pollutes the transport API we only handle the orchestrator transport here.
522
- # Since we callt this function without resolving targets this will result
505
+ # Since we call this function without resolving targets this will result
523
506
  # in the orchestrator transport always being initialized during plan runs.
524
507
  # For now that's ok.
525
508
  #