bolt 3.9.2 → 3.10.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/guides/debugging.txt +28 -0
- data/lib/bolt/bolt_option_parser.rb +49 -2
- data/lib/bolt/cli.rb +38 -9
- data/lib/bolt/outputter/human.rb +32 -0
- data/lib/bolt/outputter/json.rb +9 -0
- data/lib/bolt/pal.rb +98 -14
- data/lib/bolt/plugin.rb +38 -0
- data/lib/bolt/plugin/env_var.rb +8 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin/prompt.rb +8 -1
- data/lib/bolt/plugin/puppet_connect_data.rb +8 -1
- data/lib/bolt/plugin/puppetdb.rb +7 -1
- data/lib/bolt/plugin/task.rb +9 -1
- data/lib/bolt/project.rb +2 -1
- data/lib/bolt/task.rb +7 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +1 -1
- data/resources/bolt_bash_completion.sh +214 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa1075046a81e0596ff3f0483dc7b7a5913a073dd6e998f66c70687049531e74
|
4
|
+
data.tar.gz: 9cdb2d3503e1bd2d84fa7c0fc846e3f4d2293780f648d9d07d549fd50d2b1a51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b2f636ec7f5f038b69cd5c4f82e9fd93e59521035e15fa678cef67501a4e00287e29f40ce1672d2f5db0fa4972cbc26d9763f62480612e30ebcfae404d12a1f
|
7
|
+
data.tar.gz: f87b3966ea10cf5a5fe46937e9a47b73d4553924bff00ee578f6c659a70fe82864e2cc78b76a685dfb9e3d4a4e5a3fb7bea136730bf6b5160454e665f4f02ea8
|
@@ -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
|
@@ -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
|
@@ -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
|
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
|
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
|
-
|
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.
|
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
|
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
|
-
|
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)
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -460,6 +460,10 @@ module Bolt
|
|
460
460
|
@stream.puts(guide)
|
461
461
|
end
|
462
462
|
|
463
|
+
def print_plan_lookup(value)
|
464
|
+
@stream.puts(value)
|
465
|
+
end
|
466
|
+
|
463
467
|
def print_module_list(module_list)
|
464
468
|
module_list.each do |path, modules|
|
465
469
|
if (mod = modules.find { |m| m[:internal_module_group] })
|
@@ -488,6 +492,34 @@ module Bolt
|
|
488
492
|
end
|
489
493
|
end
|
490
494
|
|
495
|
+
def print_plugin_list(plugin_list, modulepath)
|
496
|
+
info = +''
|
497
|
+
length = plugin_list.values.map(&:keys).flatten.map(&:length).max + 4
|
498
|
+
|
499
|
+
plugin_list.each do |hook, plugins|
|
500
|
+
next if plugins.empty?
|
501
|
+
next if hook == :validate_resolve_reference
|
502
|
+
|
503
|
+
info << colorize(:cyan, "#{hook}\n")
|
504
|
+
|
505
|
+
plugins.each do |name, description|
|
506
|
+
info << indent(2, name.ljust(length))
|
507
|
+
info << truncate(description, 80 - length) if description
|
508
|
+
info << "\n"
|
509
|
+
end
|
510
|
+
|
511
|
+
info << "\n"
|
512
|
+
end
|
513
|
+
|
514
|
+
info << colorize(:cyan, "Modulepath\n")
|
515
|
+
info << indent(2, "#{modulepath.join(File::PATH_SEPARATOR)}\n\n")
|
516
|
+
|
517
|
+
info << colorize(:cyan, "Additional information\n")
|
518
|
+
info << indent(2, "For more information about using plugins see https://pup.pt/bolt-plugins")
|
519
|
+
|
520
|
+
@stream.puts info.chomp
|
521
|
+
end
|
522
|
+
|
491
523
|
def print_targets(target_list, inventory_source, default_inventory, target_flag)
|
492
524
|
adhoc = colorize(:yellow, "(Not found in inventory file)")
|
493
525
|
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -60,6 +60,11 @@ module Bolt
|
|
60
60
|
print_table('tasks' => tasks, 'modulepath' => modulepath)
|
61
61
|
end
|
62
62
|
|
63
|
+
def print_plugin_list(plugins, modulepath)
|
64
|
+
plugins.delete(:validate_resolve_reference)
|
65
|
+
print_table('plugins' => plugins, 'modulepath' => modulepath)
|
66
|
+
end
|
67
|
+
|
63
68
|
def print_plan_info(plan)
|
64
69
|
path = plan.delete('module')
|
65
70
|
plan['module_dir'] = if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
@@ -98,6 +103,10 @@ module Bolt
|
|
98
103
|
}.to_json)
|
99
104
|
end
|
100
105
|
|
106
|
+
def print_plan_lookup(value)
|
107
|
+
@stream.puts(value.to_json)
|
108
|
+
end
|
109
|
+
|
101
110
|
def print_puppetfile_result(success, puppetfile, moduledir)
|
102
111
|
@stream.puts({ success: success,
|
103
112
|
puppetfile: puppetfile,
|
data/lib/bolt/pal.rb
CHANGED
@@ -163,9 +163,10 @@ module Bolt
|
|
163
163
|
# Runs a block in a PAL script compiler configured for Bolt. Catches
|
164
164
|
# exceptions thrown by the block and re-raises them ensuring they are
|
165
165
|
# Bolt::Errors since the script compiler block will squash all exceptions.
|
166
|
-
def in_bolt_compiler
|
166
|
+
def in_bolt_compiler(compiler_params: {})
|
167
167
|
# TODO: If we always call this inside a bolt_executor we can remove this here
|
168
168
|
setup
|
169
|
+
compiler_params = compiler_params.merge(set_local_facts: false)
|
169
170
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: full_modulepath, facts: {}) do |pal|
|
170
171
|
# Only load the project if it a) exists, b) has a name it can be loaded with
|
171
172
|
Puppet.override(bolt_project: @project,
|
@@ -174,7 +175,7 @@ module Bolt
|
|
174
175
|
# of modules, it must happen *after* we have overridden
|
175
176
|
# bolt_project or the project will be ignored
|
176
177
|
detect_project_conflict(@project, Puppet.lookup(:environments).get('bolt'))
|
177
|
-
pal.with_script_compiler(
|
178
|
+
pal.with_script_compiler(**compiler_params) do |compiler|
|
178
179
|
alias_types(compiler)
|
179
180
|
register_resource_types(Puppet.lookup(:loaders)) if @resource_types
|
180
181
|
begin
|
@@ -299,6 +300,49 @@ module Bolt
|
|
299
300
|
end
|
300
301
|
end
|
301
302
|
|
303
|
+
def list_tasks_with_cache(filter_content: false)
|
304
|
+
# Don't filter content yet, so that if users update their task filters
|
305
|
+
# we don't need to refresh the cache
|
306
|
+
task_names = list_tasks(filter_content: false).map(&:first)
|
307
|
+
task_cache = if @project
|
308
|
+
Bolt::Util.read_optional_json_file(@project.task_cache_file, 'Task cache file')
|
309
|
+
else
|
310
|
+
{}
|
311
|
+
end
|
312
|
+
updated = false
|
313
|
+
|
314
|
+
task_list = task_names.each_with_object([]) do |task_name, list|
|
315
|
+
data = task_cache[task_name] || get_task_info(task_name, with_mtime: true)
|
316
|
+
|
317
|
+
# Make sure all the keys are strings - if we get data from
|
318
|
+
# get_task_info they will be symbols
|
319
|
+
data = Bolt::Util.walk_keys(data, &:to_s)
|
320
|
+
|
321
|
+
# If any files in the task were updated, refresh the cache
|
322
|
+
if data['files']&.any?
|
323
|
+
# For all the files that are part of the task
|
324
|
+
data['files'].each do |f|
|
325
|
+
# If any file has been updated since we last cached, update the
|
326
|
+
# cache
|
327
|
+
next if File.mtime(f['path']).to_s == f['mtime']
|
328
|
+
data = get_task_info(task_name, with_mtime: true)
|
329
|
+
data = Bolt::Util.walk_keys(data, &:to_s)
|
330
|
+
# Tell Bolt to write to the cache file once we're done
|
331
|
+
updated = true
|
332
|
+
# Update the cache data
|
333
|
+
task_cache[task_name] = data
|
334
|
+
end
|
335
|
+
end
|
336
|
+
metadata = data['metadata'] || {}
|
337
|
+
# Don't add tasks to the list to return if they are private
|
338
|
+
list << [task_name, metadata['description']] unless metadata['private']
|
339
|
+
end
|
340
|
+
|
341
|
+
# Write the cache if any entries were updated
|
342
|
+
File.write(@project.task_cache_file, task_cache.to_json) if updated
|
343
|
+
filter_content ? filter_content(task_list, @project&.tasks) : task_list
|
344
|
+
end
|
345
|
+
|
302
346
|
def list_tasks(filter_content: false)
|
303
347
|
in_bolt_compiler do |compiler|
|
304
348
|
tasks = compiler.list_tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
@@ -350,14 +394,20 @@ module Bolt
|
|
350
394
|
end
|
351
395
|
end
|
352
396
|
|
353
|
-
def get_task(task_name)
|
397
|
+
def get_task(task_name, with_mtime: false)
|
354
398
|
task = task_signature(task_name)
|
355
399
|
|
356
400
|
if task.nil?
|
357
401
|
raise Bolt::Error.unknown_task(task_name)
|
358
402
|
end
|
359
403
|
|
360
|
-
Bolt::Task.from_task_signature(task)
|
404
|
+
task = Bolt::Task.from_task_signature(task)
|
405
|
+
task.add_mtimes if with_mtime
|
406
|
+
task
|
407
|
+
end
|
408
|
+
|
409
|
+
def get_task_info(task_name, with_mtime: false)
|
410
|
+
get_task(task_name, with_mtime: with_mtime).to_h
|
361
411
|
end
|
362
412
|
|
363
413
|
def list_plans_with_cache(filter_content: false)
|
@@ -372,20 +422,20 @@ module Bolt
|
|
372
422
|
updated = false
|
373
423
|
|
374
424
|
plan_list = plan_names.each_with_object([]) do |plan_name, list|
|
375
|
-
|
425
|
+
data = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
|
376
426
|
|
377
427
|
# If the plan is a 'local' plan (in the project itself, or the
|
378
428
|
# modules/ directory) then verify it hasn't been updated since we
|
379
429
|
# cached it. If it has been updated, refresh the cache and use the
|
380
430
|
# new data.
|
381
|
-
if
|
382
|
-
|
383
|
-
|
431
|
+
if data['file'] &&
|
432
|
+
File.mtime(data.dig('file', 'path')).to_s != data.dig('file', 'mtime')
|
433
|
+
data = get_plan_info(plan_name, with_mtime: true)
|
384
434
|
updated = true
|
385
|
-
plan_cache[plan_name] =
|
435
|
+
plan_cache[plan_name] = data
|
386
436
|
end
|
387
437
|
|
388
|
-
list << [plan_name,
|
438
|
+
list << [plan_name, data['description']] unless data['private']
|
389
439
|
end
|
390
440
|
|
391
441
|
File.write(@project.plan_cache_file, plan_cache.to_json) if updated
|
@@ -585,6 +635,7 @@ module Bolt
|
|
585
635
|
inputs = generator.find_inputs(:pcore)
|
586
636
|
FileUtils.mkdir_p(@resource_types)
|
587
637
|
cache_plan_info if @project && cache
|
638
|
+
cache_task_info if @project && cache
|
588
639
|
generator.generate(inputs, @resource_types, true)
|
589
640
|
end
|
590
641
|
end
|
@@ -600,6 +651,17 @@ module Bolt
|
|
600
651
|
File.write(@project.plan_cache_file, plans_info.to_json)
|
601
652
|
end
|
602
653
|
|
654
|
+
def cache_task_info
|
655
|
+
# task_name is an array here
|
656
|
+
tasks_info = list_tasks(filter_content: false).map do |task_name,|
|
657
|
+
data = get_task_info(task_name, with_mtime: true)
|
658
|
+
{ task_name => data }
|
659
|
+
end.reduce({}, :merge)
|
660
|
+
|
661
|
+
FileUtils.touch(@project.task_cache_file)
|
662
|
+
File.write(@project.task_cache_file, tasks_info.to_json)
|
663
|
+
end
|
664
|
+
|
603
665
|
def run_task(task_name, targets, params, executor, inventory, description = nil)
|
604
666
|
in_task_compiler(executor, inventory) do |compiler|
|
605
667
|
params = params.merge('_bolt_api_call' => true, '_catch_errors' => true)
|
@@ -632,7 +694,21 @@ module Bolt
|
|
632
694
|
Bolt::PlanResult.new(e, 'failure')
|
633
695
|
end
|
634
696
|
|
635
|
-
def
|
697
|
+
def plan_hierarchy_lookup(key, plan_vars: {})
|
698
|
+
# Do a lookup with a script compiler, which uses the 'plan_hierarchy' key in
|
699
|
+
# Hiera config.
|
700
|
+
with_puppet_settings do
|
701
|
+
# We want all of the setup and teardown that `in_bolt_compiler` does,
|
702
|
+
# but also want to pass keys to the script compiler.
|
703
|
+
in_bolt_compiler(compiler_params: { variables: plan_vars }) do |compiler|
|
704
|
+
compiler.call_function('lookup', key)
|
705
|
+
end
|
706
|
+
rescue Puppet::Error => e
|
707
|
+
raise PALError.from_error(e)
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
def lookup(key, targets, inventory, executor, plan_vars: {})
|
636
712
|
# Install the puppet-agent package and collect facts. Facts are
|
637
713
|
# automatically added to the targets.
|
638
714
|
in_plan_compiler(executor, inventory, nil) do |compiler|
|
@@ -654,21 +730,29 @@ module Bolt
|
|
654
730
|
|
655
731
|
trusted = Puppet::Context::TrustedInformation.local(node).to_h
|
656
732
|
|
733
|
+
# Separate environment configuration from interpolation data the same
|
734
|
+
# way we do when compiling Puppet catalogs.
|
657
735
|
env_conf = {
|
658
736
|
modulepath: @modulepath.full_modulepath,
|
659
|
-
facts: target.facts
|
660
|
-
|
737
|
+
facts: target.facts
|
738
|
+
}
|
739
|
+
|
740
|
+
interpolations = {
|
741
|
+
variables: plan_vars,
|
742
|
+
target_variables: target.vars
|
661
743
|
}
|
662
744
|
|
663
745
|
with_puppet_settings do
|
664
746
|
Puppet::Pal.in_tmp_environment(target.name, **env_conf) do |pal|
|
665
747
|
Puppet.override(overrides) do
|
666
748
|
Puppet.lookup(:pal_current_node).trusted_data = trusted
|
667
|
-
pal.with_catalog_compiler do |compiler|
|
749
|
+
pal.with_catalog_compiler(**interpolations) do |compiler|
|
668
750
|
Bolt::Result.for_lookup(target, key, compiler.call_function('lookup', key))
|
669
751
|
rescue StandardError => e
|
670
752
|
Bolt::Result.from_exception(target, e)
|
671
753
|
end
|
754
|
+
rescue Puppet::Error => e
|
755
|
+
raise PALError.from_error(e)
|
672
756
|
end
|
673
757
|
end
|
674
758
|
end
|
data/lib/bolt/plugin.rb
CHANGED
@@ -250,6 +250,44 @@ module Bolt
|
|
250
250
|
end
|
251
251
|
end
|
252
252
|
|
253
|
+
# Loads all plugins and returns a map of plugin names to hooks.
|
254
|
+
#
|
255
|
+
def list_plugins
|
256
|
+
load_all_plugins
|
257
|
+
|
258
|
+
hooks = KNOWN_HOOKS.map { |hook| [hook, {}] }.to_h
|
259
|
+
|
260
|
+
@plugins.sort.each do |name, plugin|
|
261
|
+
# Don't show the Puppet Connect plugin for now.
|
262
|
+
next if name == 'puppet_connect_data'
|
263
|
+
|
264
|
+
case plugin
|
265
|
+
when Bolt::Plugin::Module
|
266
|
+
plugin.hook_map.each do |hook, spec|
|
267
|
+
next unless hooks.include?(hook)
|
268
|
+
hooks[hook][name] = spec['task'].description
|
269
|
+
end
|
270
|
+
else
|
271
|
+
plugin.hook_descriptions.each do |hook, description|
|
272
|
+
hooks[hook][name] = description
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
hooks
|
278
|
+
end
|
279
|
+
|
280
|
+
# Loads all plugins available to the project.
|
281
|
+
#
|
282
|
+
private def load_all_plugins
|
283
|
+
modules.each do |name, mod|
|
284
|
+
next unless mod.plugin?
|
285
|
+
by_name(name)
|
286
|
+
end
|
287
|
+
|
288
|
+
RUBY_PLUGINS.each { |name| by_name(name) }
|
289
|
+
end
|
290
|
+
|
253
291
|
def puppetdb_client
|
254
292
|
by_name('puppetdb').puppetdb_client
|
255
293
|
end
|
data/lib/bolt/plugin/env_var.rb
CHANGED
@@ -10,7 +10,14 @@ module Bolt
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def hooks
|
13
|
-
|
13
|
+
hook_descriptions.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def hook_descriptions
|
17
|
+
{
|
18
|
+
resolve_reference: 'Read values stored in environment variables.',
|
19
|
+
validate_resolve_reference: nil
|
20
|
+
}
|
14
21
|
end
|
15
22
|
|
16
23
|
def validate_resolve_reference(opts)
|
data/lib/bolt/plugin/module.rb
CHANGED
data/lib/bolt/plugin/prompt.rb
CHANGED
@@ -10,7 +10,14 @@ module Bolt
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def hooks
|
13
|
-
|
13
|
+
hook_descriptions.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def hook_descriptions
|
17
|
+
{
|
18
|
+
resolve_reference: 'Prompt the user for a sensitive value.',
|
19
|
+
validate_resolve_reference: nil
|
20
|
+
}
|
14
21
|
end
|
15
22
|
|
16
23
|
def validate_resolve_reference(opts)
|
@@ -49,7 +49,14 @@ module Bolt
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def hooks
|
52
|
-
|
52
|
+
hook_descriptions.keys
|
53
|
+
end
|
54
|
+
|
55
|
+
def hook_descriptions
|
56
|
+
{
|
57
|
+
resolve_reference: nil,
|
58
|
+
validate_resolve_reference: nil
|
59
|
+
}
|
53
60
|
end
|
54
61
|
|
55
62
|
def resolve_reference(opts)
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
data/lib/bolt/plugin/task.rb
CHANGED
@@ -4,7 +4,15 @@ module Bolt
|
|
4
4
|
class Plugin
|
5
5
|
class Task
|
6
6
|
def hooks
|
7
|
-
|
7
|
+
hook_descriptions.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def hook_descriptions
|
11
|
+
{
|
12
|
+
puppet_library: 'Run a task to install the Puppet agent package.',
|
13
|
+
resolve_reference: 'Run a task as a plugin.',
|
14
|
+
validate_resolve_reference: nil
|
15
|
+
}
|
8
16
|
end
|
9
17
|
|
10
18
|
def name
|
data/lib/bolt/project.rb
CHANGED
@@ -14,7 +14,7 @@ module Bolt
|
|
14
14
|
attr_reader :path, :data, :inventory_file, :hiera_config,
|
15
15
|
:puppetfile, :rerunfile, :type, :resource_types, :project_file,
|
16
16
|
:downloads, :plans_path, :modulepath, :managed_moduledir,
|
17
|
-
:backup_dir, :plugin_cache_file, :plan_cache_file
|
17
|
+
:backup_dir, :plugin_cache_file, :plan_cache_file, :task_cache_file
|
18
18
|
|
19
19
|
def self.default_project
|
20
20
|
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
@@ -114,6 +114,7 @@ module Bolt
|
|
114
114
|
@backup_dir = @path + '.bolt-bak'
|
115
115
|
@plugin_cache_file = @path + '.plugin_cache.json'
|
116
116
|
@plan_cache_file = @path + '.plan_cache.json'
|
117
|
+
@task_cache_file = @path + '.task_cache.json'
|
117
118
|
@modulepath = [(@path + 'modules').to_s]
|
118
119
|
|
119
120
|
if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & data.keys).any?
|
data/lib/bolt/task.rb
CHANGED
@@ -17,6 +17,7 @@ module Bolt
|
|
17
17
|
remote supports_noop].freeze
|
18
18
|
|
19
19
|
attr_reader :name, :files, :metadata, :remote
|
20
|
+
attr_accessor :mtime
|
20
21
|
|
21
22
|
# name [String] name of the task
|
22
23
|
# files [Array<Hash>] where each entry includes `name` and `path`
|
@@ -77,6 +78,12 @@ module Bolt
|
|
77
78
|
file_map[file_name]['path']
|
78
79
|
end
|
79
80
|
|
81
|
+
def add_mtimes
|
82
|
+
@files.each do |f|
|
83
|
+
f['mtime'] = File.mtime(f['path']) if File.exist?(f['path'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
80
87
|
def implementations
|
81
88
|
metadata['implementations']
|
82
89
|
end
|
data/lib/bolt/version.rb
CHANGED
@@ -0,0 +1,214 @@
|
|
1
|
+
#####################################################################
|
2
|
+
###
|
3
|
+
### Bash autocompletion for bolt
|
4
|
+
### include this file in your .bash_profile or .bashrc to use completion
|
5
|
+
### "source bolt_bash_completion.sh"
|
6
|
+
###
|
7
|
+
### Marc Schoechlin ms-github@256bit.org
|
8
|
+
|
9
|
+
get_json_keys() {
|
10
|
+
/opt/puppetlabs/bolt/bin/ruby <<-EOF
|
11
|
+
require 'json'
|
12
|
+
data = JSON.parse(File.read("${1}"))
|
13
|
+
puts data.keys.uniq.sort.join(' ')
|
14
|
+
EOF
|
15
|
+
}
|
16
|
+
|
17
|
+
_bolt_complete() {
|
18
|
+
local prev
|
19
|
+
local cur
|
20
|
+
# Get the current word and previous word without colons
|
21
|
+
_get_comp_words_by_ref -n : cur
|
22
|
+
_get_comp_words_by_ref -n : prev
|
23
|
+
|
24
|
+
local next=""
|
25
|
+
local subcommand=${COMP_WORDS[1]}
|
26
|
+
[[ ${#COMP_WORDS[@]} -gt 2 ]] && local action=${COMP_WORDS[2]}
|
27
|
+
local context_opts="-m --modulepath --project"
|
28
|
+
local global_opts="--clear-cache -h --help --log-level --version"
|
29
|
+
local inventory_opts="-q --query --rerun -t --targets"
|
30
|
+
local authentication_opts="-u --user -p --password --password-prompt --private-key --host-key-check --no-host-key-check --ssl --no-ssl --ssl-verify --no-ssl-verify"
|
31
|
+
local escalation_opts="--run-as --sudo-password --sudo-password-prompt --sudo-executable"
|
32
|
+
local run_context_opts="-c --concurrency -i --inventoryfile --save-rerun --no-save-rerun --cleanup --no-cleanup"
|
33
|
+
local transport_opts="--transport --connect-timeout --tty --no-tty --native-ssh --no-native-ssh --ssh-command --copy-command"
|
34
|
+
local display_opts="--format --color --no-color -v --verbose --no-verbose --trace --stream"
|
35
|
+
local action_opts="${inventory_opts} ${context_opts} ${authentication_opts} ${escalation_opts} ${run_context_opts} ${transport_opts} ${display_opts}"
|
36
|
+
local apply_opts="--compile-concurrency --hiera-config"
|
37
|
+
local file_opts=" -m --modulepath --project --private-key -i --inventoryfile --hiera-config "
|
38
|
+
|
39
|
+
# If there's only one word and it's "bolt", tab complete the subcommand
|
40
|
+
if [ $COMP_CWORD -eq 1 ]; then
|
41
|
+
next="apply command file group guide inventory lookup module plan plugin project script secret task"
|
42
|
+
fi
|
43
|
+
|
44
|
+
# Tab complete files for options that accept files. The spaces are important!
|
45
|
+
# They make it so `bolt inventory` isn't confused with `--inventoryfile`.
|
46
|
+
if [[ $file_opts =~ " ${prev} " ]]; then
|
47
|
+
next=$(compgen -f -d "" -- $cur)
|
48
|
+
# Handle tab completing enumerable CLI options
|
49
|
+
elif [ "$prev" == "--log-level" ]; then
|
50
|
+
next="trace debug info warn error fatal any"
|
51
|
+
elif [ "$prev" == "--transport" ]; then
|
52
|
+
next="docker local lxd pcp podman remote ssh winrm"
|
53
|
+
elif [ "$prev" == "--format" ]; then
|
54
|
+
next="human json rainbow"
|
55
|
+
else
|
56
|
+
# Once we have subcommands, tab complete actions
|
57
|
+
case $subcommand in
|
58
|
+
apply)
|
59
|
+
next="${action_opts} ${apply_opts} -e --execute"
|
60
|
+
;;
|
61
|
+
command)
|
62
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
63
|
+
next="run"
|
64
|
+
elif [ $action = "run" ]; then
|
65
|
+
next="${action_opts} --env-var"
|
66
|
+
fi
|
67
|
+
;;
|
68
|
+
file)
|
69
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
70
|
+
next="download upload"
|
71
|
+
else
|
72
|
+
case $action in
|
73
|
+
download) next="${action_opts} --tmpdir";;
|
74
|
+
upload) next=$action_opts;;
|
75
|
+
esac
|
76
|
+
fi
|
77
|
+
;;
|
78
|
+
group)
|
79
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
80
|
+
next="show"
|
81
|
+
elif [ $action == 'show' ]; then
|
82
|
+
next="--project --format --inventoryfile"
|
83
|
+
fi
|
84
|
+
;;
|
85
|
+
guide)
|
86
|
+
next="--format"
|
87
|
+
;;
|
88
|
+
inventory)
|
89
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
90
|
+
next="show"
|
91
|
+
elif [ $action == 'show' ]; then
|
92
|
+
next="${inventory_opts} --project --format --inventoryfile --detail"
|
93
|
+
fi
|
94
|
+
;;
|
95
|
+
lookup)
|
96
|
+
next="${action_opts} --hiera-config --plan-hierarchy"
|
97
|
+
;;
|
98
|
+
module)
|
99
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
100
|
+
next="add generate-types install show"
|
101
|
+
else
|
102
|
+
case $action in
|
103
|
+
add | install) next="--project";;
|
104
|
+
generate-types) next="${context_opts}";;
|
105
|
+
show) next="${context_opts} --filter --format";;
|
106
|
+
esac
|
107
|
+
fi
|
108
|
+
;;
|
109
|
+
plan)
|
110
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
111
|
+
next="convert new run show"
|
112
|
+
elif [[ ($COMP_CWORD -eq 3 || \
|
113
|
+
( $COMP_CWORD -eq 4 && "$cur" == *"::" ) || \
|
114
|
+
( $COMP_CWORD -eq 5 && "$cur" == *"::"* )) &&
|
115
|
+
$action != "new" ]]; then
|
116
|
+
|
117
|
+
if [ -f "${PWD}/.plan_cache.json" ]; then
|
118
|
+
# Use Puppet's ruby instead of jq since we know it will be there.
|
119
|
+
next=$(get_json_keys "${PWD}/.plan_cache.json")
|
120
|
+
elif [ -f "${HOME}/.puppetlabs/bolt/.plan_cache.json" ]; then
|
121
|
+
next=$(get_json_keys "${HOME}/.puppetlabs/bolt/.plan_cache.json")
|
122
|
+
else
|
123
|
+
case $action in
|
124
|
+
convert) next="${context_opts}";;
|
125
|
+
new) next="--project --pp";;
|
126
|
+
run) next="${action_opts} ${apply_opts} --params --tmpdir";;
|
127
|
+
show) next="${context_opts} --filter --format";;
|
128
|
+
esac
|
129
|
+
fi
|
130
|
+
else
|
131
|
+
case $action in
|
132
|
+
convert) next="${context_opts}";;
|
133
|
+
new) next="--project --pp";;
|
134
|
+
run) next="${action_opts} ${apply_opts} --params --tmpdir";;
|
135
|
+
show) next="${context_opts} --filter --format";;
|
136
|
+
esac
|
137
|
+
fi
|
138
|
+
;;
|
139
|
+
plugin)
|
140
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
141
|
+
next="show"
|
142
|
+
else
|
143
|
+
next="${context_opts} --format --color --no-color"
|
144
|
+
fi
|
145
|
+
;;
|
146
|
+
project)
|
147
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
148
|
+
next="init migrate"
|
149
|
+
else
|
150
|
+
case $action in
|
151
|
+
init) next="--modules";;
|
152
|
+
migrate) next="--project --inventoryfile";;
|
153
|
+
esac
|
154
|
+
fi
|
155
|
+
;;
|
156
|
+
script)
|
157
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
158
|
+
next="run"
|
159
|
+
elif [[ $COMP_CWORD -eq 3 && $action == 'run' ]]; then
|
160
|
+
# List files and directories
|
161
|
+
next=$(compgen -f -d "" -- $cur)
|
162
|
+
elif [ $action == 'run' ]; then
|
163
|
+
next="${action_opts} --env-var --tmpdir"
|
164
|
+
fi
|
165
|
+
;;
|
166
|
+
secret)
|
167
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
168
|
+
next="createkeys encrypt decrypt"
|
169
|
+
else
|
170
|
+
case $action in
|
171
|
+
createkeys) next="${context_opts} --plugin --force";;
|
172
|
+
encrypt | decrypt) next="${context_opts} --plugin";;
|
173
|
+
esac
|
174
|
+
fi
|
175
|
+
;;
|
176
|
+
task)
|
177
|
+
if [ $COMP_CWORD -eq 2 ]; then
|
178
|
+
next="run show"
|
179
|
+
elif [[ $COMP_CWORD -eq 3 || \
|
180
|
+
( $COMP_CWORD -eq 4 && "$cur" == *"::" ) || \
|
181
|
+
( $COMP_CWORD -eq 5 && "$cur" == *"::"* ) ]]; then
|
182
|
+
if [ -f "${PWD}/.task_cache.json" ]; then
|
183
|
+
# Use Puppet's ruby instead of jq since we know it will be there.
|
184
|
+
next=$(get_json_keys "${PWD}/.task_cache.json")
|
185
|
+
elif [ -f "${HOME}/.puppetlabs/bolt/.task_cache.json" ]; then
|
186
|
+
next=$(get_json_keys "${HOME}/.puppetlabs/bolt/.task_cache.json")
|
187
|
+
else
|
188
|
+
case $action in
|
189
|
+
run) next="${action_opts} --env-var --tmpdir";;
|
190
|
+
show) next="${context_opts} --filter --format";;
|
191
|
+
esac
|
192
|
+
fi
|
193
|
+
else
|
194
|
+
case $action in
|
195
|
+
run) next="${action_opts} --env-var --tmpdir";;
|
196
|
+
show) next="${context_opts} --filter --format";;
|
197
|
+
esac
|
198
|
+
fi
|
199
|
+
;;
|
200
|
+
esac
|
201
|
+
fi
|
202
|
+
|
203
|
+
# If any of the next options are flags, we've reached the end of the
|
204
|
+
# building-a-Bolt-command part and can re-enable file and directory name
|
205
|
+
# completion and include Bolt's global flags.
|
206
|
+
if [[ "$next" =~ "--" ]]; then
|
207
|
+
next+=" ${global_opts}"
|
208
|
+
fi
|
209
|
+
|
210
|
+
COMPREPLY=($(compgen -W "$next" -- $cur))
|
211
|
+
__ltrim_colon_completions $cur
|
212
|
+
}
|
213
|
+
|
214
|
+
complete -F _bolt_complete bolt
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -453,6 +453,7 @@ files:
|
|
453
453
|
- bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb
|
454
454
|
- bolt-modules/system/lib/puppet/functions/system/env.rb
|
455
455
|
- exe/bolt
|
456
|
+
- guides/debugging.txt
|
456
457
|
- guides/guide.txt
|
457
458
|
- guides/inventory.txt
|
458
459
|
- guides/links.txt
|
@@ -636,6 +637,7 @@ files:
|
|
636
637
|
- modules/canary/plans/init.pp
|
637
638
|
- modules/puppet_connect/plans/test_input_data.pp
|
638
639
|
- modules/puppetdb_fact/plans/init.pp
|
640
|
+
- resources/bolt_bash_completion.sh
|
639
641
|
homepage: https://github.com/puppetlabs/bolt
|
640
642
|
licenses:
|
641
643
|
- Apache-2.0
|