bolt 3.6.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.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +27 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
  9. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +10 -6
  12. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +61 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +5 -9
  14. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +29 -13
  15. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +5 -15
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +10 -18
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +5 -17
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +5 -15
  21. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +10 -18
  22. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +91 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  24. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  25. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  26. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +9 -3
  27. data/bolt-modules/file/lib/puppet/functions/file/read.rb +6 -2
  28. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +8 -3
  29. data/guides/guide.txt +17 -0
  30. data/guides/inventory.txt +5 -0
  31. data/guides/links.txt +13 -0
  32. data/guides/targets.txt +29 -0
  33. data/guides/transports.txt +23 -0
  34. data/lib/bolt/applicator.rb +4 -3
  35. data/lib/bolt/bolt_option_parser.rb +353 -227
  36. data/lib/bolt/catalog.rb +2 -1
  37. data/lib/bolt/cli.rb +94 -36
  38. data/lib/bolt/config/options.rb +2 -1
  39. data/lib/bolt/config/transport/docker.rb +5 -1
  40. data/lib/bolt/config/transport/lxd.rb +1 -1
  41. data/lib/bolt/config/transport/options.rb +2 -1
  42. data/lib/bolt/config/transport/podman.rb +5 -1
  43. data/lib/bolt/error.rb +11 -1
  44. data/lib/bolt/executor.rb +51 -72
  45. data/lib/bolt/fiber_executor.rb +141 -0
  46. data/lib/bolt/inventory.rb +5 -4
  47. data/lib/bolt/inventory/inventory.rb +3 -2
  48. data/lib/bolt/logger.rb +1 -1
  49. data/lib/bolt/module_installer/specs.rb +1 -1
  50. data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
  51. data/lib/bolt/outputter/human.rb +59 -29
  52. data/lib/bolt/outputter/json.rb +8 -4
  53. data/lib/bolt/pal.rb +64 -3
  54. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  55. data/lib/bolt/plan_creator.rb +2 -2
  56. data/lib/bolt/plan_future.rb +66 -0
  57. data/lib/bolt/puppetdb/client.rb +54 -0
  58. data/lib/bolt/result.rb +5 -0
  59. data/lib/bolt/transport/docker/connection.rb +7 -4
  60. data/lib/bolt/transport/lxd/connection.rb +4 -0
  61. data/lib/bolt/transport/podman/connection.rb +4 -0
  62. data/lib/bolt/transport/ssh/connection.rb +3 -6
  63. data/lib/bolt/util.rb +73 -1
  64. data/lib/bolt/version.rb +1 -1
  65. data/lib/bolt_spec/plans/mock_executor.rb +42 -45
  66. metadata +12 -3
  67. data/lib/bolt/yarn.rb +0 -23
@@ -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
  #
@@ -10,6 +8,7 @@ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunct
10
8
  # Map a block onto an array, where each array element executes in parallel.
11
9
  # This function is experimental.
12
10
  # @param data The array to apply the block to.
11
+ # @param block The code block to execute for each array element.
13
12
  # @return [Array] An array of PlanResult objects. Each input from the input
14
13
  # array returns a corresponding PlanResult object.
15
14
  # @example Execute two tasks on two targets.
@@ -34,21 +33,38 @@ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunct
34
33
  executor = Puppet.lookup(:bolt_executor)
35
34
  executor.report_function_call(self.class.name)
36
35
 
37
- skein = data.each_with_index.map do |object, index|
38
- executor.create_yarn(scope, block, object, index)
39
- 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
40
47
 
41
- 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)
42
51
 
43
- failed_indices = result.each_index.select do |i|
44
- result[i].is_a?(Bolt::Error)
45
- 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
46
56
 
47
- # TODO: Inner catch errors block?
48
- if failed_indices.any?
49
- 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
50
65
  end
51
66
 
52
- result
67
+ # We may eventually want parallelize to accept a timeout
68
+ executor.wait(futures)
53
69
  end
54
70
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Send a command with a payload to PuppetDB.
6
+ #
7
+ # The `pdb_command` function only supports version 5 of the `replace_facts`
8
+ # command. Other commands might also work, but are not tested or supported
9
+ # by Bolt.
10
+ #
11
+ # See the [commands endpoint](https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html)
12
+ # documentation for more information about available commands and payload
13
+ # format.
14
+ #
15
+ # _This function is experimental and subject to change._
16
+ #
17
+ # > **Note:** Not available in apply block
18
+ #
19
+ Puppet::Functions.create_function(:puppetdb_command) do
20
+ # @param command The command to invoke.
21
+ # @param version The version of the command to invoke.
22
+ # @param payload The payload to the command.
23
+ # @return The UUID identifying the response sent by PuppetDB.
24
+ # @example Replace facts for a target
25
+ # $payload = {
26
+ # 'certname' => 'localhost',
27
+ # 'environment' => 'dev',
28
+ # 'producer' => 'bolt',
29
+ # 'producer_timestamp' => '1970-01-01',
30
+ # 'values' => { 'orchestrator' => 'bolt' }
31
+ # }
32
+ #
33
+ # puppetdb_command('replace_facts', 5, $payload)
34
+ dispatch :puppetdb_command do
35
+ param 'String[1]', :command
36
+ param 'Integer', :version
37
+ param 'Hash[Data, Data]', :payload
38
+ return_type 'String'
39
+ end
40
+
41
+ def puppetdb_command(command, version, payload)
42
+ # Disallow in apply blocks.
43
+ unless Puppet[:tasks]
44
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
45
+ Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
46
+ action: 'puppetdb_command'
47
+ )
48
+ end
49
+
50
+ # Send analytics report.
51
+ Puppet.lookup(:bolt_executor).report_function_call(self.class.name)
52
+
53
+ puppetdb_client = Puppet.lookup(:bolt_pdb_client)
54
+
55
+ # Error if the PDB client does not implement :send_command
56
+ unless puppetdb_client.respond_to?(:send_command)
57
+ raise Bolt::Error.new(
58
+ "PuppetDB client #{puppetdb_client.class} does not implement :send_command, "\
59
+ "unable to invoke command.",
60
+ 'bolt/pdb-command'
61
+ )
62
+ end
63
+
64
+ puppetdb_client.send_command(command, version, payload)
65
+ end
66
+ end
@@ -11,6 +11,7 @@ require 'bolt/error'
11
11
  Puppet::Functions.create_function(:remove_from_group) do
12
12
  # @param target A pattern identifying a single target.
13
13
  # @param group The name of the group to remove the target from.
14
+ # @return [nil]
14
15
  # @example Remove Target from group.
15
16
  # remove_from_group('foo@example.com', 'group1')
16
17
  # @example Remove failing Targets from the rest of a plan
@@ -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]
@@ -108,7 +108,11 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
108
108
  # Send Analytics Report
109
109
  executor.report_function_call(self.class.name)
110
110
 
111
- found = Puppet::Parser::Files.find_file(script, scope.compiler.environment)
111
+ future = executor&.future || {}
112
+ fallback = future.fetch('file_paths', false)
113
+
114
+ # Find the file path if it exists, otherwise return nil
115
+ found = Bolt::Util.find_file_from_scope(script, scope, fallback)
112
116
  unless found && Puppet::FileSystem.exist?(found)
113
117
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
114
118
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: script
@@ -126,25 +130,13 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
126
130
  if targets.empty?
127
131
  Bolt::ResultSet.new([])
128
132
  else
129
- r = if executor.in_parallel
130
- require 'concurrent'
131
- require 'fiber'
132
- future = Concurrent::Future.execute do
133
- executor.run_script(targets,
134
- found,
135
- arguments,
136
- options,
137
- 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)
138
137
  end
139
-
140
- Fiber.yield('unfinished') while future.incomplete?
141
- future.value || future.reason
142
138
  else
143
- executor.run_script(targets,
144
- found,
145
- arguments,
146
- options,
147
- Puppet::Pops::PuppetStack.top_of_stack)
139
+ executor.run_script(targets, found, arguments, options, file_line)
148
140
  end
149
141
 
150
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
 
@@ -70,7 +70,11 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
70
70
  # Send Analytics Report
71
71
  executor.report_function_call(self.class.name)
72
72
 
73
- found = Puppet::Parser::Files.find_file(source, scope.compiler.environment)
73
+ future = executor&.future || {}
74
+ fallback = future.fetch('file_paths', false)
75
+
76
+ # Find the file path if it exists, otherwise return nil
77
+ found = Bolt::Util.find_file_from_scope(source, scope, fallback)
74
78
  unless found && Puppet::FileSystem.exist?(found)
75
79
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
76
80
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: source
@@ -83,25 +87,13 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
83
87
  call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
84
88
  Bolt::ResultSet.new([])
85
89
  else
86
- r = if executor.in_parallel
87
- require 'concurrent'
88
- require 'fiber'
89
- future = Concurrent::Future.execute do
90
- executor.upload_file(targets,
91
- found,
92
- destination,
93
- options,
94
- 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)
95
94
  end
96
-
97
- Fiber.yield('unfinished') while future.incomplete?
98
- future.value || future.reason
99
95
  else
100
- executor.upload_file(targets,
101
- found,
102
- destination,
103
- options,
104
- Puppet::Pops::PuppetStack.top_of_stack)
96
+ executor.upload_file(targets, found, destination, options, file_line)
105
97
  end
106
98
  if !r.ok && !options[:catch_errors]
107
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
@@ -9,6 +9,7 @@ Puppet::Functions.create_function(:write_file) do
9
9
  # @param content File content to write.
10
10
  # @param destination An absolute path on the target(s).
11
11
  # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
12
+ # @param options A hash of additional options.
12
13
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
13
14
  # @option options [String] _run_as User to run as using privilege escalation.
14
15
  # @return A list of results, one entry per target.