bolt 2.21.0 → 2.25.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 +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
- data/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +5 -5
- data/lib/bolt/applicator.rb +4 -3
- data/lib/bolt/bolt_option_parser.rb +64 -23
- data/lib/bolt/catalog.rb +9 -1
- data/lib/bolt/cli.rb +218 -73
- data/lib/bolt/config.rb +7 -0
- data/lib/bolt/config/options.rb +4 -4
- data/lib/bolt/executor.rb +9 -7
- data/lib/bolt/inventory/group.rb +3 -3
- data/lib/bolt/logger.rb +3 -4
- data/lib/bolt/module.rb +2 -1
- data/lib/bolt/outputter.rb +56 -0
- data/lib/bolt/outputter/human.rb +10 -9
- data/lib/bolt/outputter/json.rb +11 -4
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/outputter/rainbow.rb +15 -0
- data/lib/bolt/pal.rb +5 -9
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/plugin/prompt.rb +3 -3
- data/lib/bolt/project.rb +6 -4
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/shell/bash.rb +7 -7
- data/lib/bolt/transport/docker/connection.rb +9 -9
- data/lib/bolt/transport/local/connection.rb +2 -2
- data/lib/bolt/transport/orch.rb +3 -3
- data/lib/bolt/transport/ssh/connection.rb +5 -5
- data/lib/bolt/transport/ssh/exec_connection.rb +4 -4
- data/lib/bolt/transport/winrm/connection.rb +8 -8
- data/lib/bolt/util.rb +1 -1
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -1
- data/lib/bolt_server/transport_app.rb +76 -0
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- data/lib/bolt_spec/run.rb +3 -0
- data/libexec/apply_catalog.rb +2 -2
- data/libexec/bolt_catalog +1 -1
- data/libexec/custom_facts.rb +1 -1
- data/libexec/query_resources.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bfb8de382e2a2ab6931f43ffce1eb97187fda6e2e56036b20637afb8ae8da2d
|
4
|
+
data.tar.gz: 56cf9c3fda7f29434413feb8b7f2eb3b358c1ed4316f2d5f4e1e47875a6919c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec5bf195ad19bac051942f2689e58a325982a8c47b12d8970e1ed6a6758e714d8f22ce5275dc3fb7160ea6d06e7ee63bf528b2f18d67ee754b9ae41c3e92a7c5
|
7
|
+
data.tar.gz: cc6a87ed720484d04c7bb2275cd6812ac211733e2efce265b69cfb5ee5614fa743122ce50369079697ad08374eccea6e9797e8398176d7669846887b0f0832aa
|
data/Puppetfile
CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
|
|
6
6
|
|
7
7
|
# Core modules used by 'apply'
|
8
8
|
mod 'puppetlabs-service', '1.3.0'
|
9
|
-
mod 'puppetlabs-puppet_agent', '
|
9
|
+
mod 'puppetlabs-puppet_agent', '4.1.1'
|
10
10
|
mod 'puppetlabs-facts', '1.0.0'
|
11
11
|
|
12
12
|
# Core types and providers for Puppet 6
|
@@ -4,30 +4,36 @@
|
|
4
4
|
Puppet::Functions.create_function(:'ctrl::do_until') do
|
5
5
|
# @param options A hash of additional options.
|
6
6
|
# @option options [Numeric] limit The number of times to repeat the block.
|
7
|
+
# @option options [Numeric] interval The number of seconds to wait before repeating the block.
|
7
8
|
# @example Run a task until it succeeds
|
8
9
|
# ctrl::do_until() || {
|
9
|
-
# run_task('test', $target, _catch_errors => true).ok()
|
10
|
+
# run_task('test', $target, '_catch_errors' => true).ok()
|
10
11
|
# }
|
11
|
-
#
|
12
12
|
# @example Run a task until it succeeds or fails 10 times
|
13
13
|
# ctrl::do_until('limit' => 10) || {
|
14
|
-
# run_task('test', $target, _catch_errors => true).ok()
|
14
|
+
# run_task('test', $target, '_catch_errors' => true).ok()
|
15
|
+
# }
|
16
|
+
# @example Run a task and wait 10 seconds before running it again
|
17
|
+
# ctrl::do_until('interval' => 10) || {
|
18
|
+
# run_task('test', $target, '_catch_errors' => true).ok()
|
15
19
|
# }
|
16
|
-
#
|
17
20
|
dispatch :do_until do
|
18
21
|
optional_param 'Hash[String[1], Any]', :options
|
19
22
|
block_param
|
20
23
|
end
|
21
24
|
|
22
|
-
def do_until(options = {
|
25
|
+
def do_until(options = {})
|
23
26
|
# Send Analytics Report
|
24
27
|
Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
|
25
28
|
|
26
|
-
limit = options['limit']
|
29
|
+
limit = options['limit'] || 0
|
30
|
+
interval = options['interval']
|
31
|
+
|
27
32
|
i = 0
|
28
33
|
until (x = yield)
|
29
34
|
i += 1
|
30
35
|
break if limit != 0 && i >= limit
|
36
|
+
Kernel.sleep(interval) if interval
|
31
37
|
end
|
32
38
|
x
|
33
39
|
end
|
data/exe/bolt
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
TOPIC
|
2
|
+
inventory
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
The inventory describes the targets that you run Bolt commands on, along
|
6
|
+
with any data and configuration for the targets. Targets in an inventory can
|
7
|
+
belong to one or more groups, allowing you to share data and configuration
|
8
|
+
across multiple targets and to specify multiple targets for your Bolt
|
9
|
+
commands without the need to list each target individually.
|
10
|
+
|
11
|
+
In most cases, Bolt loads the inventory from an inventory file in your Bolt
|
12
|
+
project. The inventory file is a YAML file named 'inventory.yaml'. Because
|
13
|
+
Bolt loads the inventory file from a Bolt project, you must have an existing
|
14
|
+
project configuration file named 'bolt-project.yaml' alongside the inventory
|
15
|
+
file.
|
16
|
+
|
17
|
+
DOCUMENTATION
|
18
|
+
https://pup.pt/bolt-inventory
|
19
|
+
https://pup.pt/bolt-inventory-reference
|
data/guides/project.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
TOPIC
|
2
|
+
project
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
A Bolt project is a directory that serves as the launching point for Bolt
|
6
|
+
and allows you to create a shareable orchestration application. Projects
|
7
|
+
typically include a project configuration file, an inventory file, and any
|
8
|
+
content you use in your project workflow, such as tasks and plans.
|
9
|
+
|
10
|
+
When you run Bolt, it runs in the context of a project. If the directory you
|
11
|
+
run Bolt from is not a project, Bolt attempts to find a project by
|
12
|
+
traversing the parent directories. If Bolt is unable to find a project, it
|
13
|
+
runs from the default project, located at '~/.puppetlabs/bolt'.
|
14
|
+
|
15
|
+
A directory is only considered a Bolt project when it has a project
|
16
|
+
configuration file named 'bolt-project.yaml'. Bolt doesn't load project data
|
17
|
+
and content, including inventory files, unless the data and content are part
|
18
|
+
of a project.
|
19
|
+
|
20
|
+
DOCUMENTATION
|
21
|
+
https://pup.pt/bolt-projects
|
22
|
+
https://pup.pt/bolt-project-reference
|
data/lib/bolt/analytics.rb
CHANGED
@@ -72,7 +72,7 @@ module Bolt
|
|
72
72
|
|
73
73
|
def self.load_config(filename, logger)
|
74
74
|
if File.exist?(filename)
|
75
|
-
|
75
|
+
Bolt::Util.read_optional_yaml_hash(filename, 'analytics')
|
76
76
|
else
|
77
77
|
unless ENV['BOLT_DISABLE_ANALYTICS']
|
78
78
|
logger.warn <<~ANALYTICS
|
@@ -161,9 +161,9 @@ module Bolt
|
|
161
161
|
# Handle analytics submission in the background to avoid blocking the
|
162
162
|
# app or polluting the log with errors
|
163
163
|
Concurrent::Future.execute(executor: @executor) do
|
164
|
-
@logger.
|
164
|
+
@logger.trace "Submitting analytics: #{JSON.pretty_generate(params)}"
|
165
165
|
@http.post(TRACKING_URL, params)
|
166
|
-
@logger.
|
166
|
+
@logger.trace "Completed analytics submission"
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
@@ -215,13 +215,13 @@ module Bolt
|
|
215
215
|
end
|
216
216
|
|
217
217
|
def screen_view(screen, **_kwargs)
|
218
|
-
@logger.
|
218
|
+
@logger.trace "Skipping submission of '#{screen}' screenview because analytics is disabled"
|
219
219
|
end
|
220
220
|
|
221
221
|
def report_bundled_content(mode, name); end
|
222
222
|
|
223
223
|
def event(category, action, **_kwargs)
|
224
|
-
@logger.
|
224
|
+
@logger.trace "Skipping submission of '#{category} #{action}' event because analytics is disabled"
|
225
225
|
end
|
226
226
|
|
227
227
|
def finish; end
|
data/lib/bolt/applicator.rb
CHANGED
@@ -27,7 +27,7 @@ module Bolt
|
|
27
27
|
@hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
|
28
28
|
@apply_settings = apply_settings || {}
|
29
29
|
|
30
|
-
@pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
|
30
|
+
@pool = Concurrent::ThreadPoolExecutor.new(name: 'apply', max_threads: max_compiles)
|
31
31
|
@logger = Logging.logger[self]
|
32
32
|
end
|
33
33
|
|
@@ -217,6 +217,7 @@ module Bolt
|
|
217
217
|
r = @executor.log_action(description, targets) do
|
218
218
|
futures = targets.map do |target|
|
219
219
|
Concurrent::Future.execute(executor: @pool) do
|
220
|
+
Thread.current[:name] ||= Thread.current.name
|
220
221
|
@executor.with_node_logging("Compiling manifest block", [target]) do
|
221
222
|
compile(target, scope)
|
222
223
|
end
|
@@ -300,7 +301,7 @@ module Bolt
|
|
300
301
|
|
301
302
|
files.each do |file|
|
302
303
|
tar_path = Pathname.new(file).relative_path_from(parent)
|
303
|
-
@logger.
|
304
|
+
@logger.trace("Packing plugin #{file} to #{tar_path}")
|
304
305
|
stat = File.stat(file)
|
305
306
|
content = File.binread(file)
|
306
307
|
output.tar.add_file_simple(
|
@@ -314,7 +315,7 @@ module Bolt
|
|
314
315
|
end
|
315
316
|
|
316
317
|
duration = Time.now - start_time
|
317
|
-
@logger.
|
318
|
+
@logger.trace("Packed plugins in #{duration * 1000} ms")
|
318
319
|
|
319
320
|
output.close
|
320
321
|
Base64.encode64(sio.string)
|
@@ -61,11 +61,17 @@ module Bolt
|
|
61
61
|
{ flags: OPTIONS[:global],
|
62
62
|
banner: GROUP_HELP }
|
63
63
|
end
|
64
|
+
when 'guide'
|
65
|
+
{ flags: OPTIONS[:global] + %w[format],
|
66
|
+
banner: GUIDE_HELP }
|
64
67
|
when 'plan'
|
65
68
|
case action
|
66
69
|
when 'convert'
|
67
70
|
{ flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
|
68
71
|
banner: PLAN_CONVERT_HELP }
|
72
|
+
when 'new'
|
73
|
+
{ flags: OPTIONS[:global] + %w[configfile project],
|
74
|
+
banner: PLAN_NEW_HELP }
|
69
75
|
when 'run'
|
70
76
|
{ flags: ACTION_OPTS + %w[params compile-concurrency tmpdir hiera-config],
|
71
77
|
banner: PLAN_RUN_HELP }
|
@@ -82,7 +88,7 @@ module Bolt
|
|
82
88
|
{ flags: OPTIONS[:global] + %w[modules],
|
83
89
|
banner: PROJECT_INIT_HELP }
|
84
90
|
when 'migrate'
|
85
|
-
{ flags: OPTIONS[:global] + %w[inventoryfile
|
91
|
+
{ flags: OPTIONS[:global] + %w[inventoryfile project configfile],
|
86
92
|
banner: PROJECT_MIGRATE_HELP }
|
87
93
|
else
|
88
94
|
{ flags: OPTIONS[:global],
|
@@ -159,15 +165,19 @@ module Bolt
|
|
159
165
|
SUBCOMMANDS
|
160
166
|
apply Apply Puppet manifest code
|
161
167
|
command Run a command remotely
|
162
|
-
file
|
168
|
+
file Copy files between the controller and targets
|
163
169
|
group Show the list of groups in the inventory
|
170
|
+
guide View guides for Bolt concepts and features
|
164
171
|
inventory Show the list of targets an action would run on
|
165
|
-
plan Convert, show, and run Bolt plans
|
172
|
+
plan Convert, create, show, and run Bolt plans
|
166
173
|
project Create and migrate Bolt projects
|
167
174
|
puppetfile Install and list modules and generate type references
|
168
175
|
script Upload a local script and run it remotely
|
169
176
|
secret Create encryption keys and encrypt and decrypt values
|
170
177
|
task Show and run Bolt tasks
|
178
|
+
|
179
|
+
GUIDES
|
180
|
+
For a list of guides on Bolt's concepts and features, run 'bolt guide'.
|
171
181
|
HELP
|
172
182
|
|
173
183
|
APPLY_HELP = <<~HELP
|
@@ -286,6 +296,26 @@ module Bolt
|
|
286
296
|
Show the list of groups in the inventory.
|
287
297
|
HELP
|
288
298
|
|
299
|
+
GUIDE_HELP = <<~HELP
|
300
|
+
NAME
|
301
|
+
guide
|
302
|
+
|
303
|
+
USAGE
|
304
|
+
bolt guide [topic] [options]
|
305
|
+
|
306
|
+
DESCRIPTION
|
307
|
+
View guides for Bolt's concepts and features.
|
308
|
+
|
309
|
+
Omitting a topic will display a list of available guides,
|
310
|
+
while providing a topic will display the relevant guide.
|
311
|
+
|
312
|
+
EXAMPLES
|
313
|
+
View a list of available guides
|
314
|
+
bolt guide
|
315
|
+
View the 'project' guide page
|
316
|
+
bolt guide project
|
317
|
+
HELP
|
318
|
+
|
289
319
|
INVENTORY_HELP = <<~HELP
|
290
320
|
NAME
|
291
321
|
inventory
|
@@ -319,10 +349,11 @@ module Bolt
|
|
319
349
|
bolt plan <action> [parameters] [options]
|
320
350
|
|
321
351
|
DESCRIPTION
|
322
|
-
Convert, show, and run Bolt plans.
|
352
|
+
Convert, create, show, and run Bolt plans.
|
323
353
|
|
324
354
|
ACTIONS
|
325
355
|
convert Convert a YAML plan to a Bolt plan
|
356
|
+
new Create a new plan in the current project
|
326
357
|
run Run a plan on the specified targets
|
327
358
|
show Show available plans and plan documentation
|
328
359
|
HELP
|
@@ -345,6 +376,20 @@ module Bolt
|
|
345
376
|
bolt plan convert path/to/plan/myplan.yaml
|
346
377
|
HELP
|
347
378
|
|
379
|
+
PLAN_NEW_HELP = <<~HELP
|
380
|
+
NAME
|
381
|
+
new
|
382
|
+
|
383
|
+
USAGE
|
384
|
+
bolt plan new <plan> [options]
|
385
|
+
|
386
|
+
DESCRIPTION
|
387
|
+
Create a new plan in the current project.
|
388
|
+
|
389
|
+
EXAMPLES
|
390
|
+
bolt plan new myproject::myplan
|
391
|
+
HELP
|
392
|
+
|
348
393
|
PLAN_RUN_HELP = <<~HELP
|
349
394
|
NAME
|
350
395
|
run
|
@@ -402,19 +447,18 @@ module Bolt
|
|
402
447
|
init
|
403
448
|
|
404
449
|
USAGE
|
405
|
-
bolt project init [
|
450
|
+
bolt project init [name] [options]
|
406
451
|
|
407
452
|
DESCRIPTION
|
408
|
-
Create a new Bolt project.
|
453
|
+
Create a new Bolt project in the current working directory.
|
409
454
|
|
410
|
-
Specify a
|
411
|
-
curent working directory.
|
455
|
+
Specify a name for the Bolt project. Defaults to the basename of the current working directory.
|
412
456
|
|
413
457
|
EXAMPLES
|
414
|
-
Create a new Bolt project
|
458
|
+
Create a new Bolt project using the directory as the project name.
|
415
459
|
bolt project init
|
416
|
-
Create a new Bolt project
|
417
|
-
bolt project init
|
460
|
+
Create a new Bolt project with a specified name.
|
461
|
+
bolt project init myproject
|
418
462
|
Create a new Bolt project with existing modules.
|
419
463
|
bolt project init --modules puppetlabs-apt,puppetlabs-ntp
|
420
464
|
HELP
|
@@ -427,10 +471,7 @@ module Bolt
|
|
427
471
|
bolt project migrate [options]
|
428
472
|
|
429
473
|
DESCRIPTION
|
430
|
-
Migrate a Bolt project to the latest version.
|
431
|
-
|
432
|
-
Loads a Bolt project's inventory file and migrates it to the latest version. The
|
433
|
-
inventory file is modified in place and will not preserve comments or formatting.
|
474
|
+
Migrate a Bolt project to use current best practices and the latest version of configuration files.
|
434
475
|
HELP
|
435
476
|
|
436
477
|
PUPPETFILE_HELP = <<~HELP
|
@@ -676,9 +717,9 @@ module Bolt
|
|
676
717
|
@options[:password] = password
|
677
718
|
end
|
678
719
|
define('--password-prompt', 'Prompt for user to input password') do |_password|
|
679
|
-
|
680
|
-
@options[:password] =
|
681
|
-
|
720
|
+
$stderr.print "Please enter your password: "
|
721
|
+
@options[:password] = $stdin.noecho(&:gets).chomp
|
722
|
+
$stderr.puts
|
682
723
|
end
|
683
724
|
define('--private-key KEY', 'Path to private ssh key to authenticate with') do |key|
|
684
725
|
@options[:'private-key'] = File.expand_path(key)
|
@@ -702,9 +743,9 @@ module Bolt
|
|
702
743
|
@options[:'sudo-password'] = password
|
703
744
|
end
|
704
745
|
define('--sudo-password-prompt', 'Prompt for user to input escalation password') do |_password|
|
705
|
-
|
706
|
-
@options[:'sudo-password'] =
|
707
|
-
|
746
|
+
$stderr.print "Please enter your privilege escalation password: "
|
747
|
+
@options[:'sudo-password'] = $stdin.noecho(&:gets).chomp
|
748
|
+
$stderr.puts
|
708
749
|
end
|
709
750
|
define('--sudo-executable EXEC', "Specify an executable for running as another user.",
|
710
751
|
"This option is experimental.") do |exec|
|
@@ -846,7 +887,7 @@ module Bolt
|
|
846
887
|
end
|
847
888
|
define('--log-level LEVEL',
|
848
889
|
"Set the log level for the console. Available options are",
|
849
|
-
"debug, info,
|
890
|
+
"trace, debug, info, warn, error, fatal, any.") do |level|
|
850
891
|
@options[:log] = { 'console' => { 'level' => level } }
|
851
892
|
end
|
852
893
|
define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
|
@@ -887,7 +928,7 @@ module Bolt
|
|
887
928
|
file = value.sub(/^@/, '')
|
888
929
|
read_arg_file(file)
|
889
930
|
elsif value == '-'
|
890
|
-
|
931
|
+
$stdin.read
|
891
932
|
else
|
892
933
|
value
|
893
934
|
end
|
data/lib/bolt/catalog.rb
CHANGED
@@ -76,7 +76,15 @@ module Bolt
|
|
76
76
|
target = request['target']
|
77
77
|
plan_vars = shadow_vars('plan', request['plan_vars'], target['facts'])
|
78
78
|
target_vars = shadow_vars('target', target['variables'], target['facts'])
|
79
|
-
|
79
|
+
|
80
|
+
# Merge plan vars with target vars, while maintaining the order of the plan
|
81
|
+
# vars. It's critical that the order of plan vars is not changed, as Puppet
|
82
|
+
# will deserialize the variables in the order they appear. Variables may
|
83
|
+
# contain local references to variables that appear earlier in a plan. If
|
84
|
+
# these variables are moved before the variable they reference, Puppet will
|
85
|
+
# be unable to deserialize the data and raise an error.
|
86
|
+
topscope_vars = target_vars.reject { |k, _v| plan_vars.key?(k) }.merge(plan_vars)
|
87
|
+
|
80
88
|
env_conf = { modulepath: request['modulepath'],
|
81
89
|
facts: target['facts'],
|
82
90
|
variables: topscope_vars }
|
data/lib/bolt/cli.rb
CHANGED
@@ -20,6 +20,7 @@ require 'bolt/logger'
|
|
20
20
|
require 'bolt/outputter'
|
21
21
|
require 'bolt/puppetdb'
|
22
22
|
require 'bolt/plugin'
|
23
|
+
require 'bolt/project_migrate'
|
23
24
|
require 'bolt/pal'
|
24
25
|
require 'bolt/target'
|
25
26
|
require 'bolt/version'
|
@@ -31,14 +32,15 @@ module Bolt
|
|
31
32
|
COMMANDS = { 'command' => %w[run],
|
32
33
|
'script' => %w[run],
|
33
34
|
'task' => %w[show run],
|
34
|
-
'plan' => %w[show run convert],
|
35
|
+
'plan' => %w[show run convert new],
|
35
36
|
'file' => %w[download upload],
|
36
37
|
'puppetfile' => %w[install show-modules generate-types],
|
37
38
|
'secret' => %w[encrypt decrypt createkeys],
|
38
39
|
'inventory' => %w[show],
|
39
40
|
'group' => %w[show],
|
40
41
|
'project' => %w[init migrate],
|
41
|
-
'apply' => %w[]
|
42
|
+
'apply' => %w[],
|
43
|
+
'guide' => %w[] }.freeze
|
42
44
|
|
43
45
|
attr_reader :config, :options
|
44
46
|
|
@@ -283,6 +285,10 @@ module Bolt
|
|
283
285
|
raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
|
284
286
|
end
|
285
287
|
|
288
|
+
if options[:subcommand] == 'plan' && options[:action] == 'new' && !options[:object]
|
289
|
+
raise Bolt::CLIError, "Must specify a plan name."
|
290
|
+
end
|
291
|
+
|
286
292
|
if options.key?(:debug) && options.key?(:log)
|
287
293
|
raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
|
288
294
|
end
|
@@ -350,19 +356,11 @@ module Bolt
|
|
350
356
|
# Initialize inventory and targets. Errors here are better to catch early.
|
351
357
|
# options[:target_args] will contain a string/array version of the targetting options this is passed to plans
|
352
358
|
# options[:targets] will contain a resolved set of Target objects
|
353
|
-
unless options[:subcommand]
|
354
|
-
options[:
|
355
|
-
options[:subcommand] == 'project' ||
|
356
|
-
options[:action] == 'show' ||
|
357
|
-
options[:action] == 'convert'
|
359
|
+
unless %w[project puppetfile secret guide].include?(options[:subcommand]) ||
|
360
|
+
%w[convert new show].include?(options[:action])
|
358
361
|
update_targets(options)
|
359
362
|
end
|
360
363
|
|
361
|
-
if options[:action] == 'convert'
|
362
|
-
convert_plan(options[:object])
|
363
|
-
return 0
|
364
|
-
end
|
365
|
-
|
366
364
|
screen = "#{options[:subcommand]}_#{options[:action]}"
|
367
365
|
# submit a different screen for `bolt task show` and `bolt task show foo`
|
368
366
|
if options[:action] == 'show' && options[:object]
|
@@ -414,6 +412,9 @@ module Bolt
|
|
414
412
|
when 'show-modules'
|
415
413
|
list_modules
|
416
414
|
return 0
|
415
|
+
when 'convert'
|
416
|
+
pal.convert_plan(options[:object])
|
417
|
+
return 0
|
417
418
|
end
|
418
419
|
|
419
420
|
message = 'There may be processes left executing on some nodes.'
|
@@ -423,15 +424,28 @@ module Bolt
|
|
423
424
|
end
|
424
425
|
|
425
426
|
case options[:subcommand]
|
427
|
+
when 'guide'
|
428
|
+
code = if options[:object]
|
429
|
+
show_guide(options[:object])
|
430
|
+
else
|
431
|
+
list_topics
|
432
|
+
end
|
426
433
|
when 'project'
|
427
434
|
case options[:action]
|
428
435
|
when 'init'
|
429
436
|
code = initialize_project
|
430
437
|
when 'migrate'
|
431
|
-
|
438
|
+
inv = config.inventoryfile
|
439
|
+
path = config.project.path
|
440
|
+
code = Bolt::ProjectMigrate.new(path, outputter, inv).migrate_project
|
432
441
|
end
|
433
442
|
when 'plan'
|
434
|
-
|
443
|
+
case options[:action]
|
444
|
+
when 'new'
|
445
|
+
code = new_plan(options[:object])
|
446
|
+
when 'run'
|
447
|
+
code = run_plan(options[:object], options[:task_options], options[:target_args], options)
|
448
|
+
end
|
435
449
|
when 'puppetfile'
|
436
450
|
case options[:action]
|
437
451
|
when 'generate-types'
|
@@ -522,7 +536,7 @@ module Bolt
|
|
522
536
|
tasks = pal.list_tasks
|
523
537
|
tasks.select! { |task| task.first.include?(options[:filter]) } if options[:filter]
|
524
538
|
tasks.select! { |task| config.project.tasks.include?(task.first) } unless config.project.tasks.nil?
|
525
|
-
outputter.print_tasks(tasks, pal.
|
539
|
+
outputter.print_tasks(tasks, pal.user_modulepath)
|
526
540
|
end
|
527
541
|
|
528
542
|
def show_plan(plan_name)
|
@@ -533,7 +547,7 @@ module Bolt
|
|
533
547
|
plans = pal.list_plans
|
534
548
|
plans.select! { |plan| plan.first.include?(options[:filter]) } if options[:filter]
|
535
549
|
plans.select! { |plan| config.project.plans.include?(plan.first) } unless config.project.plans.nil?
|
536
|
-
outputter.print_plans(plans, pal.
|
550
|
+
outputter.print_plans(plans, pal.user_modulepath)
|
537
551
|
end
|
538
552
|
|
539
553
|
def list_targets
|
@@ -551,6 +565,118 @@ module Bolt
|
|
551
565
|
outputter.print_groups(groups)
|
552
566
|
end
|
553
567
|
|
568
|
+
def new_plan(plan_name)
|
569
|
+
@logger.warn("Command 'bolt plan new' is experimental and subject to changes.")
|
570
|
+
|
571
|
+
if config.project.name.nil?
|
572
|
+
raise Bolt::Error.new(
|
573
|
+
"Project directory '#{config.project.path}' is not a named project. Unable to create "\
|
574
|
+
"a project-level plan. To name a project, set the 'name' key in the 'bolt-project.yaml' "\
|
575
|
+
"configuration file.",
|
576
|
+
"bolt/unnamed-project-error"
|
577
|
+
)
|
578
|
+
end
|
579
|
+
|
580
|
+
if plan_name !~ Bolt::Module::CONTENT_NAME_REGEX
|
581
|
+
message = <<~MESSAGE.chomp
|
582
|
+
Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
|
583
|
+
separated by double colons '::'.
|
584
|
+
|
585
|
+
Each name segment must begin with a lowercase letter, and may only include lowercase
|
586
|
+
letters, digits, and underscores.
|
587
|
+
|
588
|
+
Examples of valid plan names:
|
589
|
+
- #{config.project.name}
|
590
|
+
- #{config.project.name}::my_plan
|
591
|
+
MESSAGE
|
592
|
+
|
593
|
+
raise Bolt::ValidationError, message
|
594
|
+
end
|
595
|
+
|
596
|
+
prefix, *name_segments, basename = plan_name.split('::')
|
597
|
+
|
598
|
+
# If the plan name is just the project name, then create an 'init' plan.
|
599
|
+
# Otherwise, use the last name segment for the plan's filename.
|
600
|
+
basename ||= 'init'
|
601
|
+
|
602
|
+
unless prefix == config.project.name
|
603
|
+
message = "First segment of plan name '#{plan_name}' must match project name '#{config.project.name}'. "\
|
604
|
+
"Did you mean '#{config.project.name}::#{plan_name}'?"
|
605
|
+
|
606
|
+
raise Bolt::ValidationError, message
|
607
|
+
end
|
608
|
+
|
609
|
+
dir_path = config.project.plans_path.join(*name_segments)
|
610
|
+
|
611
|
+
%w[pp yaml].each do |ext|
|
612
|
+
next unless (path = config.project.plans_path + "#{basename}.#{ext}").exist?
|
613
|
+
raise Bolt::Error.new(
|
614
|
+
"A plan with the name '#{plan_name}' already exists at '#{path}', nothing to do.",
|
615
|
+
'bolt/existing-plan-error'
|
616
|
+
)
|
617
|
+
end
|
618
|
+
|
619
|
+
begin
|
620
|
+
FileUtils.mkdir_p(dir_path)
|
621
|
+
rescue Errno::EEXIST => e
|
622
|
+
raise Bolt::Error.new(
|
623
|
+
"#{e.message}; unable to create plan directory '#{dir_path}'",
|
624
|
+
'bolt/existing-file-error'
|
625
|
+
)
|
626
|
+
end
|
627
|
+
|
628
|
+
plan_path = dir_path + "#{basename}.yaml"
|
629
|
+
|
630
|
+
plan_template = <<~PLAN
|
631
|
+
# This is the structure of a simple plan. To learn more about writing
|
632
|
+
# YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
|
633
|
+
|
634
|
+
# The description sets the description of the plan that will appear
|
635
|
+
# in 'bolt plan show' output.
|
636
|
+
description: A plan created with bolt plan new
|
637
|
+
|
638
|
+
# The parameters key defines the parameters that can be passed to
|
639
|
+
# the plan.
|
640
|
+
parameters:
|
641
|
+
targets:
|
642
|
+
type: TargetSpec
|
643
|
+
description: A list of targets to run actions on
|
644
|
+
default: localhost
|
645
|
+
|
646
|
+
# The steps key defines the actions the plan will take in order.
|
647
|
+
steps:
|
648
|
+
- message: Hello from #{plan_name}
|
649
|
+
- name: command_step
|
650
|
+
command: whoami
|
651
|
+
targets: $targets
|
652
|
+
|
653
|
+
# The return key sets the return value of the plan.
|
654
|
+
return: $command_step
|
655
|
+
PLAN
|
656
|
+
|
657
|
+
begin
|
658
|
+
File.write(plan_path, plan_template)
|
659
|
+
rescue Errno::EACCES => e
|
660
|
+
raise Bolt::FileError.new(
|
661
|
+
"#{e.message}; unable to create plan",
|
662
|
+
plan_path
|
663
|
+
)
|
664
|
+
end
|
665
|
+
|
666
|
+
output = <<~OUTPUT
|
667
|
+
Created plan '#{plan_name}' at '#{plan_path}'
|
668
|
+
|
669
|
+
Show this plan with:
|
670
|
+
bolt plan show #{plan_name}
|
671
|
+
Run this plan with:
|
672
|
+
bolt plan run #{plan_name}
|
673
|
+
OUTPUT
|
674
|
+
|
675
|
+
outputter.print_message(output)
|
676
|
+
|
677
|
+
0
|
678
|
+
end
|
679
|
+
|
554
680
|
def run_plan(plan_name, plan_arguments, nodes, options)
|
555
681
|
unless nodes.empty?
|
556
682
|
if plan_arguments['nodes'] || plan_arguments['targets']
|
@@ -654,8 +780,26 @@ module Bolt
|
|
654
780
|
# Initializes a specified directory as a Bolt project and installs any modules
|
655
781
|
# specified by the user, along with their dependencies
|
656
782
|
def initialize_project
|
657
|
-
|
658
|
-
|
783
|
+
# Dir.pwd will return backslashes on Windows, but Pathname always uses
|
784
|
+
# forward slashes to concatenate paths. This results in paths like
|
785
|
+
# C:\User\Administrator/modules, which fail module install. This ensure
|
786
|
+
# forward slashes in the cwd path.
|
787
|
+
dir = File.expand_path(Dir.pwd)
|
788
|
+
name = options[:object] || File.basename(dir)
|
789
|
+
if name !~ Bolt::Module::MODULE_NAME_REGEX
|
790
|
+
if options[:object]
|
791
|
+
raise Bolt::ValidationError, "The provided project name '#{name}' is invalid; "\
|
792
|
+
"project name must begin with a lowercase letter and can include lowercase "\
|
793
|
+
"letters, numbers, and underscores."
|
794
|
+
else
|
795
|
+
raise Bolt::ValidationError, "The current directory name '#{name}' is an invalid "\
|
796
|
+
"project name. Please specify a name using 'bolt project init <name>'."
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
project = Pathname.new(dir)
|
801
|
+
old_config = project + 'bolt.yaml'
|
802
|
+
config = project + 'bolt-project.yaml'
|
659
803
|
puppetfile = project + 'Puppetfile'
|
660
804
|
modulepath = [project + 'modules']
|
661
805
|
|
@@ -676,18 +820,24 @@ module Bolt
|
|
676
820
|
|
677
821
|
# Warn the user if the project directory already exists. We don't error here since users
|
678
822
|
# might not have installed any modules yet.
|
823
|
+
# If both bolt.yaml and bolt-project.yaml exist, this will just warn
|
824
|
+
# about bolt-project.yaml and subsequent Bolt actions will warn about
|
825
|
+
# both files existing
|
679
826
|
if config.exist?
|
680
|
-
@logger.warn "Found existing project directory at #{project}"
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
827
|
+
@logger.warn "Found existing project directory at #{project}. Skipping file creation."
|
828
|
+
# This won't get called if bolt-project.yaml exists
|
829
|
+
elsif old_config.exist?
|
830
|
+
@logger.warn "Found existing #{old_config.basename} at #{project}. "\
|
831
|
+
"#{old_config.basename} is deprecated, please rename to #{config.basename}."
|
686
832
|
# Bless the project directory as a...wait for it...project
|
687
|
-
if FileUtils.touch(config)
|
688
|
-
outputter.print_message "Successfully created Bolt project at #{project}"
|
689
833
|
else
|
690
|
-
|
834
|
+
begin
|
835
|
+
content = { 'name' => name }
|
836
|
+
File.write(config.to_path, content.to_yaml)
|
837
|
+
outputter.print_message "Successfully created Bolt project at #{project}"
|
838
|
+
rescue StandardError => e
|
839
|
+
raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
|
840
|
+
end
|
691
841
|
end
|
692
842
|
|
693
843
|
# Write the generated Puppetfile to the fancy new project
|
@@ -763,49 +913,6 @@ module Bolt
|
|
763
913
|
end
|
764
914
|
end
|
765
915
|
|
766
|
-
def migrate_project
|
767
|
-
inventory_file = config.inventoryfile || config.default_inventoryfile
|
768
|
-
data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
|
769
|
-
|
770
|
-
data.delete('version') if data['version'] != 2
|
771
|
-
|
772
|
-
migrated = migrate_group(data)
|
773
|
-
|
774
|
-
ok = File.write(inventory_file, data.to_yaml) if migrated
|
775
|
-
|
776
|
-
result = if migrated && ok
|
777
|
-
"Successfully migrated Bolt project to latest version."
|
778
|
-
elsif !migrated
|
779
|
-
"Bolt project already on latest version. Nothing to do."
|
780
|
-
else
|
781
|
-
"Could not migrate Bolt project to latest version."
|
782
|
-
end
|
783
|
-
outputter.print_message result
|
784
|
-
|
785
|
-
ok ? 0 : 1
|
786
|
-
end
|
787
|
-
|
788
|
-
# Walks an inventory hash and replaces all 'nodes' keys with 'targets' keys
|
789
|
-
# and all 'name' keys nested in a 'targets' hash with 'uri' keys. Data is
|
790
|
-
# modified in place.
|
791
|
-
def migrate_group(group)
|
792
|
-
migrated = false
|
793
|
-
if group.key?('nodes')
|
794
|
-
migrated = true
|
795
|
-
targets = group['nodes'].map do |target|
|
796
|
-
target['uri'] = target.delete('name') if target.is_a?(Hash)
|
797
|
-
target
|
798
|
-
end
|
799
|
-
group.delete('nodes')
|
800
|
-
group['targets'] = targets
|
801
|
-
end
|
802
|
-
(group['groups'] || []).each do |subgroup|
|
803
|
-
migrated_group = migrate_group(subgroup)
|
804
|
-
migrated ||= migrated_group
|
805
|
-
end
|
806
|
-
migrated
|
807
|
-
end
|
808
|
-
|
809
916
|
def install_puppetfile(config, puppetfile, modulepath)
|
810
917
|
require 'r10k/cli'
|
811
918
|
require 'bolt/r10k_log_proxy'
|
@@ -848,8 +955,46 @@ module Bolt
|
|
848
955
|
config.project)
|
849
956
|
end
|
850
957
|
|
851
|
-
|
852
|
-
|
958
|
+
# Collects the list of Bolt guides and maps them to their topics.
|
959
|
+
def guides
|
960
|
+
@guides ||= begin
|
961
|
+
root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
|
962
|
+
files = Dir.children(root_path).sort
|
963
|
+
|
964
|
+
files.each_with_object({}) do |file, guides|
|
965
|
+
next if file !~ /\.txt\z/
|
966
|
+
topic = File.basename(file, '.txt')
|
967
|
+
guides[topic] = File.join(root_path, file)
|
968
|
+
end
|
969
|
+
rescue SystemCallError => e
|
970
|
+
raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
# Display the list of available Bolt guides.
|
975
|
+
def list_topics
|
976
|
+
outputter.print_topics(guides.keys)
|
977
|
+
0
|
978
|
+
end
|
979
|
+
|
980
|
+
# Display a specific Bolt guide.
|
981
|
+
def show_guide(topic)
|
982
|
+
if guides[topic]
|
983
|
+
analytics.event('Guide', 'known_topic', label: topic)
|
984
|
+
|
985
|
+
begin
|
986
|
+
guide = File.read(guides[topic])
|
987
|
+
rescue SystemCallError => e
|
988
|
+
raise Bolt::FileError("#{e.message}: unable to load guide page", filepath)
|
989
|
+
end
|
990
|
+
|
991
|
+
outputter.print_guide(guide, topic)
|
992
|
+
else
|
993
|
+
analytics.event('Guide', 'unknown_topic', label: topic)
|
994
|
+
outputter.print_message("Did not find guide for topic '#{topic}'.\n\n")
|
995
|
+
list_topics
|
996
|
+
end
|
997
|
+
0
|
853
998
|
end
|
854
999
|
|
855
1000
|
def validate_file(type, path, allow_dir = false)
|
@@ -916,7 +1061,7 @@ module Bolt
|
|
916
1061
|
msg = <<~MSG.chomp
|
917
1062
|
Loaded configuration from: '#{config.config_files.join("', '")}'
|
918
1063
|
MSG
|
919
|
-
@logger.
|
1064
|
+
@logger.info(msg)
|
920
1065
|
end
|
921
1066
|
|
922
1067
|
# Gem installs include the aggregate, canary, and puppetdb_fact modules, while
|