openbolt 5.6.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Puppetfile +35 -35
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +27 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +14 -22
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +14 -26
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +2 -2
- data/bolt-modules/file/lib/puppet/functions/file/delete.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/debug.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/error.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/info.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/trace.rb +1 -1
- data/bolt-modules/log/lib/puppet/functions/log/warn.rb +1 -1
- data/guides/debugging.yaml +1 -1
- data/guides/inventory.yaml +2 -2
- data/guides/links.yaml +18 -9
- data/guides/logging.yaml +1 -1
- data/guides/module.yaml +1 -1
- data/guides/modulepath.yaml +1 -1
- data/guides/project.yaml +2 -2
- data/guides/targets.yaml +3 -1
- data/guides/transports.yaml +4 -3
- data/lib/bolt/analytics.rb +2 -10
- data/lib/bolt/bolt_option_parser.rb +66 -66
- data/lib/bolt/cli.rb +21 -2
- data/lib/bolt/config/options.rb +18 -18
- data/lib/bolt/config/transport/choria.rb +3 -0
- data/lib/bolt/config/transport/options.rb +34 -45
- data/lib/bolt/executor.rb +4 -0
- data/lib/bolt/fiber_executor.rb +46 -2
- data/lib/bolt/inventory/options.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/module_installer/puppetfile.rb +1 -1
- data/lib/bolt/module_installer/specs.rb +1 -1
- data/lib/bolt/outputter/human.rb +1 -2
- data/lib/bolt/plan_creator.rb +4 -4
- data/lib/bolt/plan_future.rb +5 -1
- data/lib/bolt/project_manager/inventory_migrator.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +1 -1
- data/lib/bolt/project_manager.rb +1 -1
- data/lib/bolt/shell/bash.rb +7 -4
- data/lib/bolt/shell/powershell/snippets.rb +20 -1
- data/lib/bolt/shell/powershell.rb +2 -2
- data/lib/bolt/transport/base.rb +9 -4
- data/lib/bolt/transport/choria/agent_discovery.rb +32 -14
- data/lib/bolt/transport/choria/bolt_tasks.rb +64 -40
- data/lib/bolt/transport/choria/client.rb +66 -49
- data/lib/bolt/transport/choria/command_builders.rb +144 -24
- data/lib/bolt/transport/choria/file_transfer.rb +134 -0
- data/lib/bolt/transport/choria/helpers.rb +25 -20
- data/lib/bolt/transport/choria/shell.rb +255 -148
- data/lib/bolt/transport/choria.rb +96 -27
- data/lib/bolt/transport/ssh/connection.rb +2 -1
- data/lib/bolt/util.rb +13 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +4 -0
- data/libexec/apply_catalog.rb +3 -0
- metadata +22 -7
|
@@ -12,9 +12,11 @@ module Bolt
|
|
|
12
12
|
collective
|
|
13
13
|
command-timeout
|
|
14
14
|
config-file
|
|
15
|
+
file-transfer-timeout
|
|
15
16
|
host
|
|
16
17
|
interpreters
|
|
17
18
|
mcollective-certname
|
|
19
|
+
message-data-size
|
|
18
20
|
broker-timeout
|
|
19
21
|
brokers
|
|
20
22
|
puppet-environment
|
|
@@ -25,6 +27,7 @@ module Bolt
|
|
|
25
27
|
task-agent
|
|
26
28
|
task-timeout
|
|
27
29
|
tmpdir
|
|
30
|
+
upload-batch-size
|
|
28
31
|
].sort.freeze
|
|
29
32
|
|
|
30
33
|
DEFAULTS = {
|
|
@@ -19,8 +19,8 @@ module Bolt
|
|
|
19
19
|
"batch-mode" => {
|
|
20
20
|
type: [TrueClass, FalseClass],
|
|
21
21
|
description: "Whether to disable password querying. When set to `false`, SSH will fall back to " \
|
|
22
|
-
"prompting for a password if key authentication fails. This might cause
|
|
23
|
-
"To prevent
|
|
22
|
+
"prompting for a password if key authentication fails. This might cause OpenBolt to hang. " \
|
|
23
|
+
"To prevent OpenBolt from hanging, you can configure `ssh-command` to use an SSH utility " \
|
|
24
24
|
"such as sshpass that supports providing a password non-interactively. For more " \
|
|
25
25
|
"information, see [Providing a password non-interactively using " \
|
|
26
26
|
"`native-ssh`](troubleshooting.md#providing-a-password-non-interactively-using-native-ssh).",
|
|
@@ -29,7 +29,7 @@ module Bolt
|
|
|
29
29
|
_example: false
|
|
30
30
|
},
|
|
31
31
|
"bundled-ruby" => {
|
|
32
|
-
description: "Whether to use the Ruby bundled with
|
|
32
|
+
description: "Whether to use the Ruby bundled with OpenBolt packages for local targets.",
|
|
33
33
|
type: [TrueClass, FalseClass],
|
|
34
34
|
_plugin: false,
|
|
35
35
|
_example: true,
|
|
@@ -44,7 +44,7 @@ module Bolt
|
|
|
44
44
|
"cleanup" => {
|
|
45
45
|
type: [TrueClass, FalseClass],
|
|
46
46
|
description: "Whether to clean up temporary files created on targets. When running commands on a target, " \
|
|
47
|
-
"
|
|
47
|
+
"OpenBolt might create temporary files. After completing the command, these files are " \
|
|
48
48
|
"automatically deleted. This value can be set to 'false' if you wish to leave these " \
|
|
49
49
|
"temporary files on the target.",
|
|
50
50
|
_plugin: true,
|
|
@@ -101,10 +101,33 @@ module Bolt
|
|
|
101
101
|
_default: 30,
|
|
102
102
|
_example: 60
|
|
103
103
|
},
|
|
104
|
+
"file-transfer-timeout" => {
|
|
105
|
+
type: Integer,
|
|
106
|
+
description: "How long to wait in seconds for targets to respond to a file transfer request. Default is " \
|
|
107
|
+
"300, and the wait is never shorter than the agent's DDL timeout of 120 seconds.",
|
|
108
|
+
minimum: 1,
|
|
109
|
+
_plugin: true,
|
|
110
|
+
_example: 600
|
|
111
|
+
},
|
|
112
|
+
"message-data-size" => {
|
|
113
|
+
type: Integer,
|
|
114
|
+
description: "The file content in one Choria RPC message, in kibibytes, for uploads, downloads and " \
|
|
115
|
+
"directory listings. Default is 512, half the broker's 1 MiB message limit.",
|
|
116
|
+
minimum: 1,
|
|
117
|
+
_plugin: true,
|
|
118
|
+
_example: 256
|
|
119
|
+
},
|
|
120
|
+
"upload-batch-size" => {
|
|
121
|
+
type: Integer,
|
|
122
|
+
description: "How many targets one chunk request is published to at a time. Default is 256.",
|
|
123
|
+
minimum: 1,
|
|
124
|
+
_plugin: true,
|
|
125
|
+
_example: 100
|
|
126
|
+
},
|
|
104
127
|
"connect-timeout" => {
|
|
105
128
|
type: Integer,
|
|
106
129
|
description: "How long to wait in seconds when establishing connections. Set this value higher if you " \
|
|
107
|
-
"frequently encounter connection timeout errors when running
|
|
130
|
+
"frequently encounter connection timeout errors when running OpenBolt.",
|
|
108
131
|
minimum: 1,
|
|
109
132
|
_plugin: true,
|
|
110
133
|
_default: 10,
|
|
@@ -112,7 +135,7 @@ module Bolt
|
|
|
112
135
|
},
|
|
113
136
|
"copy-command" => {
|
|
114
137
|
type: [Array, String],
|
|
115
|
-
description: "The command to use when copying files using native SSH.
|
|
138
|
+
description: "The command to use when copying files using native SSH. OpenBolt runs `<copy-command> <src> " \
|
|
116
139
|
"<dest>`. This option is used when you need support for features or algorithms that are not " \
|
|
117
140
|
"supported by the net-ssh Ruby library. **This option is experimental.** You can read more " \
|
|
118
141
|
"about this option in [Native SSH transport](experimental_features.md#native-ssh-transport).",
|
|
@@ -203,7 +226,7 @@ module Bolt
|
|
|
203
226
|
"override the shebang defined in a task executable. The extension can optionally be " \
|
|
204
227
|
"specified with the `.` character (`.py` and `py` both map to a task executable " \
|
|
205
228
|
"`task.py`) and the extension is case sensitive. When a target's name is `localhost`, " \
|
|
206
|
-
"Ruby tasks run with the
|
|
229
|
+
"Ruby tasks run with the OpenBolt Ruby interpreter by default.",
|
|
207
230
|
additionalProperties: {
|
|
208
231
|
type: [String, Array],
|
|
209
232
|
_plugin: false
|
|
@@ -215,20 +238,6 @@ module Bolt
|
|
|
215
238
|
_example: { "rb" => ["/usr/bin/ruby", "-r", "puppet"],
|
|
216
239
|
".py" => "/usr/bin/python3" }
|
|
217
240
|
},
|
|
218
|
-
"job-poll-interval" => {
|
|
219
|
-
type: Integer,
|
|
220
|
-
description: "The interval, in seconds, to poll orchestrator for job status.",
|
|
221
|
-
minimum: 1,
|
|
222
|
-
_plugin: true,
|
|
223
|
-
_example: 2
|
|
224
|
-
},
|
|
225
|
-
"job-poll-timeout" => {
|
|
226
|
-
type: Integer,
|
|
227
|
-
description: "The time, in seconds, to wait for orchestrator job status.",
|
|
228
|
-
minimum: 1,
|
|
229
|
-
_plugin: true,
|
|
230
|
-
_example: 2000
|
|
231
|
-
},
|
|
232
241
|
"kex-algorithms" => {
|
|
233
242
|
type: Array,
|
|
234
243
|
description: "A list of key exchange algorithms to use when establishing a connection " \
|
|
@@ -253,7 +262,7 @@ module Bolt
|
|
|
253
262
|
},
|
|
254
263
|
"login-shell" => {
|
|
255
264
|
type: String,
|
|
256
|
-
description: "Which login shell
|
|
265
|
+
description: "Which login shell OpenBolt should expect on the target. Supported shells are " \
|
|
257
266
|
"#{LOGIN_SHELLS.join(', ')}. **This option is experimental.**",
|
|
258
267
|
enum: LOGIN_SHELLS,
|
|
259
268
|
_plugin: true,
|
|
@@ -346,13 +355,6 @@ module Bolt
|
|
|
346
355
|
_default: "production",
|
|
347
356
|
_example: "staging"
|
|
348
357
|
},
|
|
349
|
-
"read-timeout" => {
|
|
350
|
-
type: Integer,
|
|
351
|
-
description: "How long to wait in seconds when making requests to the Orchestrator.",
|
|
352
|
-
minimum: 1,
|
|
353
|
-
_plugin: true,
|
|
354
|
-
_example: 15
|
|
355
|
-
},
|
|
356
358
|
"realm" => {
|
|
357
359
|
type: String,
|
|
358
360
|
description: "The Kerberos realm (Active Directory domain) to authenticate against.",
|
|
@@ -375,9 +377,9 @@ module Bolt
|
|
|
375
377
|
},
|
|
376
378
|
"run-as-command" => {
|
|
377
379
|
type: Array,
|
|
378
|
-
description: "The command to elevate permissions.
|
|
379
|
-
"configured `run-as-command` before running it on the target. This command must not require
|
|
380
|
-
"
|
|
380
|
+
description: "The command to elevate permissions. OpenBolt appends the user and command strings to the " \
|
|
381
|
+
"configured `run-as-command` before running it on the target. This command must not require " \
|
|
382
|
+
"an interactive password prompt, and the `sudo-password` option is ignored when " \
|
|
381
383
|
"`run-as-command` is specified. The `run-as-command` must be specified as an array.",
|
|
382
384
|
items: {
|
|
383
385
|
type: String
|
|
@@ -486,13 +488,6 @@ module Bolt
|
|
|
486
488
|
_plugin: true,
|
|
487
489
|
_example: "p@$$w0rd!"
|
|
488
490
|
},
|
|
489
|
-
"task-environment" => {
|
|
490
|
-
type: String,
|
|
491
|
-
description: "The environment the orchestrator loads task code from.",
|
|
492
|
-
_plugin: true,
|
|
493
|
-
_default: "production",
|
|
494
|
-
_example: "development"
|
|
495
|
-
},
|
|
496
491
|
"task-timeout" => {
|
|
497
492
|
type: Integer,
|
|
498
493
|
description: "How long to wait in seconds for tasks to complete when using the Choria transport.",
|
|
@@ -507,12 +502,6 @@ module Bolt
|
|
|
507
502
|
_plugin: true,
|
|
508
503
|
_example: "/tmp/bolt"
|
|
509
504
|
},
|
|
510
|
-
"token-file" => {
|
|
511
|
-
type: String,
|
|
512
|
-
description: "The path to the token file.",
|
|
513
|
-
_plugin: true,
|
|
514
|
-
_example: "~/.puppetlabs/puppet/token.pem"
|
|
515
|
-
},
|
|
516
505
|
"tty" => {
|
|
517
506
|
type: [TrueClass, FalseClass],
|
|
518
507
|
description: "Whether to enable tty on exec commands.",
|
data/lib/bolt/executor.rb
CHANGED
data/lib/bolt/fiber_executor.rb
CHANGED
|
@@ -5,6 +5,11 @@ require_relative '../bolt/plan_future'
|
|
|
5
5
|
|
|
6
6
|
module Bolt
|
|
7
7
|
class FiberExecutor
|
|
8
|
+
# Limits how many plans one future can nest, so a plan that runs itself
|
|
9
|
+
# with no condition that stops it fails instead of starting Fibers until
|
|
10
|
+
# memory runs out.
|
|
11
|
+
MAX_NESTED_PLANS = 1000
|
|
12
|
+
|
|
8
13
|
attr_reader :active_futures, :finished_futures
|
|
9
14
|
|
|
10
15
|
def initialize
|
|
@@ -99,6 +104,7 @@ module Bolt
|
|
|
99
104
|
# `wait` call, Bolt should pause for long enough that something can
|
|
100
105
|
# execute before checking again. This mitigates CPU
|
|
101
106
|
# thrashing.
|
|
107
|
+
return if active_futures.empty?
|
|
102
108
|
return unless active_futures.all? { |f| %i[returned_immediately unfinished].include?(f.value) }
|
|
103
109
|
|
|
104
110
|
@logger.trace("Nothing can be resumed. Rechecking in 0.5 seconds.")
|
|
@@ -121,7 +127,45 @@ module Bolt
|
|
|
121
127
|
# Get the PlanFuture object that is currently executing
|
|
122
128
|
#
|
|
123
129
|
def get_current_future(fiber:)
|
|
124
|
-
all_futures.
|
|
130
|
+
all_futures.find { |f| f.fiber == fiber || f.nested_fibers.include?(fiber) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Run the block on a new Fiber, so it starts with an empty stack instead
|
|
134
|
+
# of adding to the current one. Every plan a plan runs adds dozens of
|
|
135
|
+
# frames, and a Fiber's stack is small and fixed. Values the block yields
|
|
136
|
+
# while it waits are yielded on to the scheduler, and an error raised into
|
|
137
|
+
# the current Fiber, like a wait() timeout, is raised inside the block.
|
|
138
|
+
#
|
|
139
|
+
def run_in_new_fiber(&block)
|
|
140
|
+
future = get_current_future(fiber: Fiber.current)
|
|
141
|
+
return yield unless future
|
|
142
|
+
|
|
143
|
+
if future.nested_fibers.length >= MAX_NESTED_PLANS
|
|
144
|
+
msg = "Plans cannot be nested more than #{MAX_NESTED_PLANS} levels deep. Check for a " \
|
|
145
|
+
"plan that runs itself without a condition that stops it."
|
|
146
|
+
# A plain error, not a Bolt::Error, so plans can't catch it with
|
|
147
|
+
# _catch_errors or catch_errors() and keep a runaway plan going.
|
|
148
|
+
raise msg
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
fiber = Fiber.new(&block)
|
|
152
|
+
future.nested_fibers.push(fiber)
|
|
153
|
+
@logger.trace("Running a nested plan on a new Fiber in future '#{future.name}'")
|
|
154
|
+
begin
|
|
155
|
+
value = fiber.resume
|
|
156
|
+
while fiber.alive?
|
|
157
|
+
begin
|
|
158
|
+
resumed_with = Fiber.yield(value)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
value = fiber.raise(e)
|
|
161
|
+
next
|
|
162
|
+
end
|
|
163
|
+
value = fiber.resume(resumed_with)
|
|
164
|
+
end
|
|
165
|
+
value
|
|
166
|
+
ensure
|
|
167
|
+
future.nested_fibers.delete(fiber)
|
|
168
|
+
end
|
|
125
169
|
end
|
|
126
170
|
|
|
127
171
|
# Get the plan invocation ID for the PlanFuture that is currently executing
|
|
@@ -146,7 +190,7 @@ module Bolt
|
|
|
146
190
|
# finished. This ensures that we include futures created inside of
|
|
147
191
|
# futures being waited on.
|
|
148
192
|
until (futures = get_futures_for_plan(plan_id: plan_id)).map(&:alive?).none?
|
|
149
|
-
if futures.
|
|
193
|
+
if futures.include?(get_current_future(fiber: Fiber.current))
|
|
150
194
|
msg = "The wait() function cannot be called with no arguments inside a " \
|
|
151
195
|
"background block in the same plan."
|
|
152
196
|
raise Bolt::Error.new(msg, 'bolt/infinite-wait')
|
|
@@ -84,7 +84,7 @@ module Bolt
|
|
|
84
84
|
"plugin_hooks" => {
|
|
85
85
|
description: "Configuration for the Puppet library plugin used to install the " \
|
|
86
86
|
"Puppet agent on the target. For more information, see " \
|
|
87
|
-
"
|
|
87
|
+
"[plugin hooks](writing_plugins.md#plugin-hooks).",
|
|
88
88
|
type: Hash,
|
|
89
89
|
properties: {
|
|
90
90
|
"puppet_library" => {
|
|
@@ -179,7 +179,7 @@ module Bolt
|
|
|
179
179
|
'dotted_fact_name',
|
|
180
180
|
"Target '#{safe_name}' includes dotted fact names: '#{dotted.join("', '")}'. Dotted fact " \
|
|
181
181
|
"names are deprecated and Bolt does not automatically convert facts with dotted names to " \
|
|
182
|
-
"structured facts. For more information, see https://
|
|
182
|
+
"structured facts. For more information, see https://docs.openvoxproject.org/openbolt/latest/developer_updates.html#deprecating-dotted-fact-names"
|
|
183
183
|
)
|
|
184
184
|
end
|
|
185
185
|
end
|
|
@@ -72,7 +72,7 @@ module Bolt
|
|
|
72
72
|
File.open(path, 'w') do |file|
|
|
73
73
|
if moduledir
|
|
74
74
|
file.puts "# This Puppetfile is managed by Bolt. Do not edit."
|
|
75
|
-
file.puts "# For more information, see https://
|
|
75
|
+
file.puts "# For more information, see https://docs.openvoxproject.org/openbolt/latest/modules.html"
|
|
76
76
|
file.puts
|
|
77
77
|
file.puts "# The following directive installs modules to the managed moduledir."
|
|
78
78
|
file.puts "moduledir '#{moduledir.basename}'"
|
|
@@ -57,7 +57,7 @@ module Bolt
|
|
|
57
57
|
Invalid module specification:
|
|
58
58
|
#{hash.to_yaml.lines.drop(1).join.chomp}
|
|
59
59
|
|
|
60
|
-
To read more about specifying modules, see https://
|
|
60
|
+
To read more about specifying modules, see https://docs.openvoxproject.org/openbolt/latest/bolt_installing_modules.html#manually-specify-modules-in-a-bolt-project
|
|
61
61
|
MESSAGE
|
|
62
62
|
end
|
|
63
63
|
|
data/lib/bolt/outputter/human.rb
CHANGED
|
@@ -51,7 +51,6 @@ module Bolt
|
|
|
51
51
|
|
|
52
52
|
@spinning = false
|
|
53
53
|
@spin_thread.terminate
|
|
54
|
-
@stream.print("\b")
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
def remove_trail(string)
|
|
@@ -645,7 +644,7 @@ module Bolt
|
|
|
645
644
|
info << indent(2, "#{modulepath.join(File::PATH_SEPARATOR)}\n\n")
|
|
646
645
|
|
|
647
646
|
info << colorize(:cyan, "Additional information\n")
|
|
648
|
-
info << indent(2, "For more information about using plugins see https://
|
|
647
|
+
info << indent(2, "For more information about using plugins see https://docs.openvoxproject.org/openbolt/latest/using_plugins.html")
|
|
649
648
|
|
|
650
649
|
@stream.puts info.chomp
|
|
651
650
|
end
|
data/lib/bolt/plan_creator.rb
CHANGED
|
@@ -113,7 +113,7 @@ module Bolt
|
|
|
113
113
|
private_class_method def self.yaml_plan(plan_name)
|
|
114
114
|
<<~YAML
|
|
115
115
|
# This is the structure of a simple plan. To learn more about writing
|
|
116
|
-
# YAML plans, see the documentation:
|
|
116
|
+
# YAML plans, see the documentation: https://docs.openvoxproject.org/openbolt/latest/writing_yaml_plans.html
|
|
117
117
|
|
|
118
118
|
# The description sets the description of the plan that will appear
|
|
119
119
|
# in 'bolt plan show' output.
|
|
@@ -146,7 +146,7 @@ module Bolt
|
|
|
146
146
|
private_class_method def self.yaml_script_plan(script)
|
|
147
147
|
<<~YAML
|
|
148
148
|
# This is the structure of a simple plan. To learn more about writing
|
|
149
|
-
# YAML plans, see the documentation:
|
|
149
|
+
# YAML plans, see the documentation: https://docs.openvoxproject.org/openbolt/latest/writing_yaml_plans.html
|
|
150
150
|
|
|
151
151
|
# The description sets the description of the plan that will appear
|
|
152
152
|
# in 'bolt plan show' output.
|
|
@@ -177,7 +177,7 @@ module Bolt
|
|
|
177
177
|
private_class_method def self.puppet_plan(plan_name)
|
|
178
178
|
<<~PUPPET
|
|
179
179
|
# This is the structure of a simple plan. To learn more about writing
|
|
180
|
-
# Puppet plans, see the documentation:
|
|
180
|
+
# Puppet plans, see the documentation: https://docs.openvoxproject.org/openbolt/latest/writing_plans.html
|
|
181
181
|
|
|
182
182
|
# The summary sets the description of the plan that will appear
|
|
183
183
|
# in 'bolt plan show' output. Bolt uses puppet-strings to parse the
|
|
@@ -202,7 +202,7 @@ module Bolt
|
|
|
202
202
|
private_class_method def self.puppet_script_plan(plan_name, script)
|
|
203
203
|
<<~PUPPET
|
|
204
204
|
# This is the structure of a simple plan. To learn more about writing
|
|
205
|
-
# Puppet plans, see the documentation:
|
|
205
|
+
# Puppet plans, see the documentation: https://docs.openvoxproject.org/openbolt/latest/writing_plans.html
|
|
206
206
|
|
|
207
207
|
# The summary sets the description of the plan that will appear
|
|
208
208
|
# in 'bolt plan show' output. Bolt uses puppet-strings to parse the
|
data/lib/bolt/plan_future.rb
CHANGED
|
@@ -4,7 +4,7 @@ require 'fiber'
|
|
|
4
4
|
|
|
5
5
|
module Bolt
|
|
6
6
|
class PlanFuture
|
|
7
|
-
attr_reader :fiber, :id, :scope
|
|
7
|
+
attr_reader :fiber, :id, :scope, :nested_fibers
|
|
8
8
|
attr_accessor :value, :plan_stack
|
|
9
9
|
|
|
10
10
|
def initialize(fiber, id, plan_id:, name: nil, scope: nil)
|
|
@@ -24,6 +24,10 @@ module Bolt
|
|
|
24
24
|
# since now we're in the calling plan. These IDs are unique to each plan
|
|
25
25
|
# invocation, not just plan names.
|
|
26
26
|
@plan_stack = [plan_id]
|
|
27
|
+
|
|
28
|
+
# Fibers this Future started with FiberExecutor#run_in_new_fiber, which
|
|
29
|
+
# still belong to this Future
|
|
30
|
+
@nested_fibers = []
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def original_plan
|
|
@@ -35,7 +35,7 @@ module Bolt
|
|
|
35
35
|
rescue StandardError => e
|
|
36
36
|
raise Bolt::FileError.new(
|
|
37
37
|
"Unable to write to #{inventory_file}: #{e.message}. See " \
|
|
38
|
-
"
|
|
38
|
+
"https://docs.openvoxproject.org/openbolt/latest/inventory_files.html to manually update.",
|
|
39
39
|
inventory_file
|
|
40
40
|
)
|
|
41
41
|
end
|
|
@@ -23,7 +23,7 @@ module Bolt
|
|
|
23
23
|
@outputter.print_action_step(
|
|
24
24
|
"Project has a non-default configured modulepath, unable to automatically " \
|
|
25
25
|
"migrate project modules. To migrate project modules manually, see " \
|
|
26
|
-
"
|
|
26
|
+
"https://docs.openvoxproject.org/openbolt/latest/modules.html"
|
|
27
27
|
)
|
|
28
28
|
true
|
|
29
29
|
# Migrate modules from Puppetfile
|
data/lib/bolt/project_manager.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Bolt
|
|
|
8
8
|
class ProjectManager
|
|
9
9
|
INVENTORY_TEMPLATE = <<~INVENTORY
|
|
10
10
|
# This is an example inventory.yaml
|
|
11
|
-
# To read more about inventory files, see https://
|
|
11
|
+
# To read more about inventory files, see https://docs.openvoxproject.org/openbolt/latest/inventory_files.html
|
|
12
12
|
#
|
|
13
13
|
# groups:
|
|
14
14
|
# - name: linux
|
data/lib/bolt/shell/bash.rb
CHANGED
|
@@ -230,15 +230,17 @@ module Bolt
|
|
|
230
230
|
''
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
+
# GNU sudo and sudo-rs word these differently
|
|
233
234
|
def handle_sudo_errors(err)
|
|
234
235
|
case err
|
|
235
|
-
when /^#{conn.user} is not in the sudoers file
|
|
236
|
+
when /^#{conn.user} is not in the sudoers file\./,
|
|
237
|
+
/Sorry, user #{conn.user} (may not run|is not allowed to execute)/
|
|
236
238
|
@logger.trace { err }
|
|
237
239
|
raise Bolt::Node::EscalateError.new(
|
|
238
240
|
"User #{conn.user} does not have sudo permission on #{target}",
|
|
239
241
|
'SUDO_DENIED'
|
|
240
242
|
)
|
|
241
|
-
when /^Sorry, try again
|
|
243
|
+
when /^Sorry, try again\./, /Authentication failed, try again/
|
|
242
244
|
@logger.trace { err }
|
|
243
245
|
raise Bolt::Node::EscalateError.new(
|
|
244
246
|
"Sudo password for user #{conn.user} not recognized on #{target}",
|
|
@@ -299,7 +301,7 @@ module Bolt
|
|
|
299
301
|
|
|
300
302
|
result = execute(command)
|
|
301
303
|
if result.exit_code != 0
|
|
302
|
-
raise Bolt::Node::FileError.new("Could not make tmpdir: #{result.stderr.string}", 'TMPDIR_ERROR')
|
|
304
|
+
raise Bolt::Node::FileError.new("Could not make tmpdir #{tmppath}: #{result.stderr.string}", 'TMPDIR_ERROR')
|
|
303
305
|
end
|
|
304
306
|
|
|
305
307
|
path = tmppath || result.stdout.string.chomp
|
|
@@ -484,7 +486,8 @@ module Bolt
|
|
|
484
486
|
@logger.trace { "Command `#{command_str}` returned successfully" }
|
|
485
487
|
when 126
|
|
486
488
|
msg = "\n\nThis might be caused by the default tmpdir being mounted " \
|
|
487
|
-
"using 'noexec'. See
|
|
489
|
+
"using 'noexec'. See https://docs.openvoxproject.org/openbolt/latest/troubleshooting.html" \
|
|
490
|
+
"#my-task-fails-with-a-permission-denied-error-noexec-issue for details and workarounds."
|
|
488
491
|
result_output.stderr << msg
|
|
489
492
|
result_output.merged_output << msg
|
|
490
493
|
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
3
5
|
module Bolt
|
|
4
6
|
class Shell
|
|
5
7
|
class Powershell < Shell
|
|
6
8
|
module Snippets
|
|
7
9
|
class << self
|
|
10
|
+
# An expression that evaluates to the value as a string. The value
|
|
11
|
+
# is sent as base64, so no character in it can end a quoted string
|
|
12
|
+
# or here-string, whatever encoding PowerShell reads the script in.
|
|
13
|
+
# A binary string, such as a command-line argument under the C
|
|
14
|
+
# locale, goes as its bytes, which PowerShell reads as UTF-8.
|
|
15
|
+
def base64_string(value)
|
|
16
|
+
value = value.to_s
|
|
17
|
+
value = value.encode('UTF-8') unless value.encoding == Encoding::BINARY
|
|
18
|
+
"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{Base64.strict_encode64(value)}'))"
|
|
19
|
+
end
|
|
20
|
+
|
|
8
21
|
def execute_process(command)
|
|
9
22
|
<<~PS
|
|
10
23
|
if ([Console]::InputEncoding -eq [System.Text.Encoding]::UTF8) {
|
|
@@ -46,7 +59,7 @@ module Bolt
|
|
|
46
59
|
|
|
47
60
|
def run_script(arguments, script_path)
|
|
48
61
|
build_arg_list = arguments.map do |a|
|
|
49
|
-
"$invokeArgs.ArgumentList +=
|
|
62
|
+
"$invokeArgs.ArgumentList += #{base64_string(a)}"
|
|
50
63
|
end.join("\n")
|
|
51
64
|
<<~PS
|
|
52
65
|
$invokeArgs = @{
|
|
@@ -138,6 +151,12 @@ module Bolt
|
|
|
138
151
|
"${boltBaseDir}\\hiera\\lib;" +
|
|
139
152
|
$ENV:RUBYLIB
|
|
140
153
|
|
|
154
|
+
#{json_functions.chomp}
|
|
155
|
+
PS
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def json_functions
|
|
159
|
+
<<~PS
|
|
141
160
|
function ConvertFrom-PSCustomObject
|
|
142
161
|
{
|
|
143
162
|
PARAM([Parameter(ValueFromPipeline = $true)] $InputObject)
|
|
@@ -88,12 +88,12 @@ module Bolt
|
|
|
88
88
|
|
|
89
89
|
def env_declarations(env_vars)
|
|
90
90
|
env_vars.map do |var, val|
|
|
91
|
-
"[Environment]::SetEnvironmentVariable(
|
|
91
|
+
"[Environment]::SetEnvironmentVariable(#{quote_string(var.to_s)}, #{Snippets.base64_string(val)})"
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def quote_string(string)
|
|
96
|
-
"'" +
|
|
96
|
+
"'" + Bolt::Util.escape_powershell_single_quotes(string) + "'"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def write_executable(dir, file, filename = nil)
|
data/lib/bolt/transport/base.rb
CHANGED
|
@@ -165,18 +165,23 @@ module Bolt
|
|
|
165
165
|
#
|
|
166
166
|
# Transports may override this method to implement their own batch processing.
|
|
167
167
|
def batch_download(targets, source, destination, options = {}, position = [], &callback)
|
|
168
|
-
require 'erb'
|
|
169
|
-
|
|
170
168
|
assert_batch_size_one("batch_download()", targets)
|
|
171
169
|
target = targets.first
|
|
172
170
|
with_events(target, callback, 'download', position) do
|
|
173
|
-
|
|
174
|
-
target_destination = File.expand_path(escaped_name, destination)
|
|
171
|
+
target_destination = download_dir(destination, target)
|
|
175
172
|
@logger.debug { "Downloading: '#{source}' on #{target.safe_name} to #{target_destination}" }
|
|
176
173
|
download(target, source, target_destination, options)
|
|
177
174
|
end
|
|
178
175
|
end
|
|
179
176
|
|
|
177
|
+
# The directory under the destination that receives one target's
|
|
178
|
+
# download, named after the target so that a batch keeps them apart.
|
|
179
|
+
def download_dir(destination, target)
|
|
180
|
+
require 'erb'
|
|
181
|
+
|
|
182
|
+
File.expand_path(ERB::Util.url_encode(target.safe_name), destination)
|
|
183
|
+
end
|
|
184
|
+
|
|
180
185
|
def batch_connected?(targets)
|
|
181
186
|
assert_batch_size_one("connected?()", targets)
|
|
182
187
|
connected?(targets.first)
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
module Bolt
|
|
4
4
|
module Transport
|
|
5
5
|
class Choria
|
|
6
|
-
SHELL_MIN_VERSION = '1.2.1'
|
|
7
|
-
|
|
8
6
|
AGENT_MIN_VERSIONS = {
|
|
9
|
-
'shell' =>
|
|
7
|
+
'shell' => '1.2.1',
|
|
8
|
+
'file_transfer' => '1.0.0'
|
|
10
9
|
}.freeze
|
|
11
10
|
|
|
12
11
|
# Discover agents and detect OS on targets via two batched RPC calls
|
|
@@ -14,18 +13,20 @@ module Bolt
|
|
|
14
13
|
# Populates @agent_cache with { agents: [...], os: 'redhat' | 'windows' | ... }.
|
|
15
14
|
#
|
|
16
15
|
# @param targets [Array<Bolt::Target>] Targets to discover agents on
|
|
16
|
+
# @return [Hash{Bolt::Target => Hash}] What discover_agent_list returns for the uncached
|
|
17
|
+
# targets, or an empty hash when every target was already discovered.
|
|
17
18
|
def discover_agents(targets)
|
|
18
|
-
uncached = targets.reject { |target|
|
|
19
|
-
return if uncached.empty?
|
|
19
|
+
uncached = targets.reject { |target| discovered?(target) }
|
|
20
|
+
return {} if uncached.empty?
|
|
20
21
|
|
|
21
22
|
logger.debug { "Discovering agents on #{target_count(uncached)}" }
|
|
22
|
-
discover_agent_list(uncached)
|
|
23
|
+
failures = discover_agent_list(uncached)
|
|
23
24
|
discover_os_family(uncached)
|
|
25
|
+
failures
|
|
26
|
+
end
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger.warn { "No response from #{identity} during agent discovery" } unless @agent_cache.key?(identity)
|
|
28
|
-
end
|
|
28
|
+
def discovered?(target)
|
|
29
|
+
@agent_cache.key?(choria_identity(target))
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def has_agent?(target, agent_name)
|
|
@@ -40,26 +41,37 @@ module Bolt
|
|
|
40
41
|
# and populate @agent_cache with agent lists.
|
|
41
42
|
#
|
|
42
43
|
# @param targets [Array<Bolt::Target>] Targets to query for agent inventory
|
|
44
|
+
# @return [Hash{Bolt::Target => Hash}] The error output of each target that did not answer,
|
|
45
|
+
# answered with an RPC error or answered without an agent list, or of every target when
|
|
46
|
+
# the call failed as a whole, such as when the client could not connect. An empty hash
|
|
47
|
+
# when discovery itself raises, which leaves those targets undiscovered.
|
|
43
48
|
def discover_agent_list(targets)
|
|
44
49
|
response = rpc_request('rpcutil', targets, 'rpcutil.agent_inventory') do |client|
|
|
45
50
|
client.agent_inventory
|
|
46
51
|
end
|
|
47
|
-
response[:errors].each { |target, output| logger.
|
|
52
|
+
response[:errors].each { |target, output| logger.warn { "agent_inventory failed for #{target.safe_name}: #{output[:error]}" } }
|
|
48
53
|
|
|
54
|
+
malformed = {}
|
|
49
55
|
response[:responded].each do |target, data|
|
|
50
56
|
sender = choria_identity(target)
|
|
51
|
-
|
|
57
|
+
# A node can send data that isn't a hash, which would raise and stop
|
|
58
|
+
# discovery for every target after it
|
|
59
|
+
agents = filter_agents(sender, data[:agents]) if data.is_a?(Hash)
|
|
52
60
|
unless agents
|
|
53
|
-
logger.warn { "Unexpected agent_inventory response from #{sender}
|
|
61
|
+
logger.warn { "Unexpected agent_inventory response from #{sender}" }
|
|
62
|
+
malformed[target] = error_output("rpcutil.agent_inventory on #{target.safe_name} returned no agent list",
|
|
63
|
+
'bolt/choria-missing-data')
|
|
54
64
|
next
|
|
55
65
|
end
|
|
56
66
|
@agent_cache[sender] = { agents: agents }
|
|
57
67
|
logger.debug { "Discovered agents on #{sender}: #{agents.join(', ')}" }
|
|
58
68
|
end
|
|
69
|
+
response[:errors].merge(malformed)
|
|
59
70
|
rescue StandardError => e
|
|
60
71
|
raise if e.is_a?(Bolt::Error)
|
|
61
72
|
|
|
62
73
|
logger.warn { "Agent discovery failed: #{e.class}: #{e.message}" }
|
|
74
|
+
{}
|
|
63
75
|
end
|
|
64
76
|
|
|
65
77
|
# Detect the OS family on targets via rpcutil.get_fact and update
|
|
@@ -68,7 +80,7 @@ module Bolt
|
|
|
68
80
|
# @param targets [Array<Bolt::Target>] Targets to detect OS on
|
|
69
81
|
def discover_os_family(targets)
|
|
70
82
|
# Only fetch OS for targets that responded to agent_inventory
|
|
71
|
-
responded = targets.select { |target|
|
|
83
|
+
responded = targets.select { |target| discovered?(target) }
|
|
72
84
|
return if responded.empty?
|
|
73
85
|
|
|
74
86
|
response = rpc_request('rpcutil', responded, 'rpcutil.get_fact') do |client|
|
|
@@ -82,6 +94,10 @@ module Bolt
|
|
|
82
94
|
|
|
83
95
|
response[:responded].each do |target, data|
|
|
84
96
|
sender = choria_identity(target)
|
|
97
|
+
unless data.is_a?(Hash)
|
|
98
|
+
logger.warn { "Unexpected get_fact response from #{sender}. Defaulting to POSIX command syntax." }
|
|
99
|
+
next
|
|
100
|
+
end
|
|
85
101
|
os_family = data[:value].to_s.downcase
|
|
86
102
|
if os_family.empty?
|
|
87
103
|
logger.warn { "os.family fact is empty on #{sender}. Defaulting to POSIX command syntax." }
|
|
@@ -107,6 +123,8 @@ module Bolt
|
|
|
107
123
|
return nil unless agent_list.is_a?(Array)
|
|
108
124
|
|
|
109
125
|
agent_list.filter_map do |entry|
|
|
126
|
+
next unless entry.is_a?(Hash)
|
|
127
|
+
|
|
110
128
|
name = entry['agent']
|
|
111
129
|
next unless name
|
|
112
130
|
|