bolt 3.5.0 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +27 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +5 -1
  15. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +5 -1
  16. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  17. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  18. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +9 -3
  19. data/bolt-modules/file/lib/puppet/functions/file/read.rb +6 -2
  20. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +8 -3
  21. data/guides/guide.txt +17 -0
  22. data/guides/links.txt +13 -0
  23. data/guides/targets.txt +29 -0
  24. data/guides/transports.txt +23 -0
  25. data/lib/bolt/analytics.rb +4 -8
  26. data/lib/bolt/applicator.rb +1 -1
  27. data/lib/bolt/bolt_option_parser.rb +351 -225
  28. data/lib/bolt/catalog.rb +2 -1
  29. data/lib/bolt/cli.rb +122 -55
  30. data/lib/bolt/config.rb +11 -7
  31. data/lib/bolt/config/options.rb +41 -9
  32. data/lib/bolt/config/transport/podman.rb +33 -0
  33. data/lib/bolt/executor.rb +15 -11
  34. data/lib/bolt/inventory.rb +5 -4
  35. data/lib/bolt/inventory/inventory.rb +3 -2
  36. data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
  37. data/lib/bolt/outputter/human.rb +194 -79
  38. data/lib/bolt/outputter/json.rb +10 -4
  39. data/lib/bolt/pal.rb +45 -0
  40. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  41. data/lib/bolt/plan_creator.rb +2 -2
  42. data/lib/bolt/plugin.rb +13 -11
  43. data/lib/bolt/puppetdb/client.rb +54 -0
  44. data/lib/bolt/result.rb +5 -0
  45. data/lib/bolt/shell/bash.rb +23 -10
  46. data/lib/bolt/transport/docker.rb +1 -1
  47. data/lib/bolt/transport/docker/connection.rb +10 -6
  48. data/lib/bolt/transport/podman.rb +19 -0
  49. data/lib/bolt/transport/podman/connection.rb +98 -0
  50. data/lib/bolt/transport/ssh/connection.rb +3 -6
  51. data/lib/bolt/util.rb +71 -0
  52. data/lib/bolt/version.rb +1 -1
  53. data/lib/bolt_server/transport_app.rb +3 -0
  54. data/lib/bolt_spec/plans/mock_executor.rb +2 -1
  55. metadata +10 -2
data/lib/bolt/catalog.rb CHANGED
@@ -65,7 +65,8 @@ module Bolt
65
65
  puppet_overrides = {
66
66
  bolt_pdb_client: pdb_client,
67
67
  bolt_inventory: inv,
68
- bolt_project: bolt_project
68
+ bolt_project: bolt_project,
69
+ future: request['future']
69
70
  }
70
71
 
71
72
  # Facts will be set by the catalog compiler, so we need to ensure
data/lib/bolt/cli.rb CHANGED
@@ -33,20 +33,23 @@ module Bolt
33
33
 
34
34
  class CLI
35
35
  COMMANDS = {
36
- 'command' => %w[run],
37
- 'script' => %w[run],
38
- 'task' => %w[show run],
39
- 'plan' => %w[show run convert new],
40
- 'file' => %w[download upload],
41
- 'secret' => %w[encrypt decrypt createkeys],
36
+ 'apply' => %w[],
37
+ 'command' => %w[run],
38
+ 'file' => %w[download upload],
39
+ 'group' => %w[show],
40
+ 'guide' => %w[],
42
41
  'inventory' => %w[show],
43
- 'group' => %w[show],
44
- 'project' => %w[init migrate],
45
- 'module' => %w[add generate-types install show],
46
- 'apply' => %w[],
47
- 'guide' => %w[]
42
+ 'lookup' => %w[],
43
+ 'module' => %w[add generate-types install show],
44
+ 'plan' => %w[show run convert new],
45
+ 'project' => %w[init migrate],
46
+ 'script' => %w[run],
47
+ 'secret' => %w[encrypt decrypt createkeys],
48
+ 'task' => %w[show run]
48
49
  }.freeze
49
50
 
51
+ TARGETING_OPTIONS = %i[query rerun targets].freeze
52
+
50
53
  attr_reader :config, :options
51
54
 
52
55
  def initialize(argv)
@@ -262,7 +265,7 @@ module Bolt
262
265
  end
263
266
 
264
267
  def update_targets(options)
265
- target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
268
+ target_opts = options.keys.select { |opt| TARGETING_OPTIONS.include?(opt) }
266
269
  target_string = "'--targets', '--rerun', or '--query'"
267
270
  if target_opts.length > 1
268
271
  raise Bolt::CLIError, "Only one targeting option #{target_string} can be specified"
@@ -324,6 +327,10 @@ module Bolt
324
327
  raise Bolt::CLIError, "a manifest file or --execute is required"
325
328
  end
326
329
 
330
+ if options[:subcommand] == 'lookup' && !options[:object]
331
+ raise Bolt::CLIError, "Must specify a key to look up"
332
+ end
333
+
327
334
  if options[:subcommand] == 'command' && (!options[:object] || options[:object].empty?)
328
335
  raise Bolt::CLIError, "Must specify a command to run"
329
336
  end
@@ -509,6 +516,8 @@ module Bolt
509
516
  when 'migrate'
510
517
  code = Bolt::ProjectManager.new(config, outputter, pal).migrate
511
518
  end
519
+ when 'lookup'
520
+ code = lookup(options[:object], options[:targets])
512
521
  when 'plan'
513
522
  case options[:action]
514
523
  when 'new'
@@ -541,7 +550,11 @@ module Bolt
541
550
  end
542
551
  code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
543
552
  else
544
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
553
+ executor = Bolt::Executor.new(config.concurrency,
554
+ analytics,
555
+ options[:noop],
556
+ config.modified_concurrency,
557
+ config.future)
545
558
  targets = options[:targets]
546
559
 
547
560
  results = nil
@@ -557,7 +570,8 @@ module Bolt
557
570
  when 'command'
558
571
  executor.run_command(targets, options[:object], executor_opts)
559
572
  when 'script'
560
- script_path = find_file(options[:object])
573
+ script_path = find_file(options[:object], executor.future&.fetch('file_paths', false))
574
+ validate_file('script', script_path)
561
575
  executor.run_script(targets, script_path, options[:leftovers], executor_opts)
562
576
  when 'task'
563
577
  pal.run_task(options[:object],
@@ -582,8 +596,9 @@ module Bolt
582
596
  dest = File.expand_path(dest, Dir.pwd)
583
597
  executor.download_file(targets, src, dest, executor_opts)
584
598
  when 'upload'
585
- validate_file('source file', src, true)
586
- executor.upload_file(targets, src, dest, executor_opts)
599
+ src_path = find_file(src, executor.future&.fetch('file_paths', false))
600
+ validate_file('source file', src_path, true)
601
+ executor.upload_file(targets, src_path, dest, executor_opts)
587
602
  end
588
603
  end
589
604
  end
@@ -630,8 +645,39 @@ module Bolt
630
645
  end
631
646
 
632
647
  def list_targets
633
- inventoryfile = config.inventoryfile || config.default_inventoryfile
648
+ if options.keys.any? { |key| TARGETING_OPTIONS.include?(key) }
649
+ target_flag = true
650
+ else
651
+ options[:targets] = 'all'
652
+ end
653
+
654
+ outputter.print_targets(
655
+ group_targets_by_source,
656
+ inventory.source,
657
+ config.default_inventoryfile,
658
+ target_flag
659
+ )
660
+ end
661
+
662
+ def show_targets
663
+ if options.keys.any? { |key| TARGETING_OPTIONS.include?(key) }
664
+ target_flag = true
665
+ else
666
+ options[:targets] = 'all'
667
+ end
634
668
 
669
+ outputter.print_target_info(
670
+ group_targets_by_source,
671
+ inventory.source,
672
+ config.default_inventoryfile,
673
+ target_flag
674
+ )
675
+ end
676
+
677
+ # Returns a hash of targets sorted by those that are found in the
678
+ # inventory and those that are provided on the command line.
679
+ #
680
+ private def group_targets_by_source
635
681
  # Retrieve the known group and target names. This needs to be done before
636
682
  # updating targets, as that will add adhoc targets to the inventory.
637
683
  known_names = inventory.target_names
@@ -642,22 +688,43 @@ module Bolt
642
688
  known_names.include?(target.name)
643
689
  end
644
690
 
645
- target_list = {
646
- inventory: inventory_targets,
647
- adhoc: adhoc_targets
648
- }
649
-
650
- outputter.print_targets(target_list, inventoryfile)
691
+ { inventory: inventory_targets, adhoc: adhoc_targets }
651
692
  end
652
693
 
653
- def show_targets
654
- update_targets(options)
655
- outputter.print_target_info(options[:targets])
694
+ def list_groups
695
+ outputter.print_groups(inventory.group_names.sort, inventory.source, config.default_inventoryfile)
656
696
  end
657
697
 
658
- def list_groups
659
- groups = inventory.group_names
660
- outputter.print_groups(groups)
698
+ # Looks up a value with Hiera, using targets as the contexts to perform the
699
+ # look ups in.
700
+ #
701
+ def lookup(key, targets)
702
+ executor = Bolt::Executor.new(
703
+ config.concurrency,
704
+ analytics,
705
+ options[:noop],
706
+ config.modified_concurrency,
707
+ config.future
708
+ )
709
+
710
+ executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
711
+ executor.subscribe(log_outputter)
712
+ executor.publish_event(type: :plan_start, plan: nil)
713
+
714
+ results = outputter.spin do
715
+ pal.lookup(
716
+ key,
717
+ targets,
718
+ inventory,
719
+ executor,
720
+ config.concurrency
721
+ )
722
+ end
723
+
724
+ executor.shutdown
725
+ outputter.print_result_set(results)
726
+
727
+ results.ok ? 0 : 1
661
728
  end
662
729
 
663
730
  def run_plan(plan_name, plan_arguments, nodes, options)
@@ -688,7 +755,11 @@ module Bolt
688
755
  plan_context = { plan_name: plan_name,
689
756
  params: plan_arguments }
690
757
 
691
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
758
+ executor = Bolt::Executor.new(config.concurrency,
759
+ analytics,
760
+ options[:noop],
761
+ config.modified_concurrency,
762
+ config.future)
692
763
  if %w[human rainbow].include?(options.fetch(:format, 'human'))
693
764
  executor.subscribe(outputter)
694
765
  else
@@ -724,7 +795,11 @@ module Bolt
724
795
  Bolt::Logger.warn("empty_manifest", message)
725
796
  end
726
797
 
727
- executor = Bolt::Executor.new(config.concurrency, analytics, noop, config.modified_concurrency)
798
+ executor = Bolt::Executor.new(config.concurrency,
799
+ analytics,
800
+ noop,
801
+ config.modified_concurrency,
802
+ config.future)
728
803
  executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
729
804
  executor.subscribe(log_outputter)
730
805
  # apply logging looks like plan logging, so tell the outputter we're in a
@@ -809,15 +884,10 @@ module Bolt
809
884
  #
810
885
  def assert_project_file(project)
811
886
  unless project.project_file?
812
- msg = if project.config_file.exist?
813
- command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
814
- "Detected Bolt configuration file #{project.config_file}, unable to install "\
815
- "modules. To update to a project configuration file, run '#{command}'."
816
- else
817
- command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
818
- "Could not find project configuration file #{project.project_file}, unable "\
819
- "to install modules. To create a Bolt project, run '#{command}'."
820
- end
887
+ command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
888
+
889
+ msg = "Could not find project configuration file #{project.project_file}, unable "\
890
+ "to install modules. To create a Bolt project, run '#{command}'."
821
891
 
822
892
  raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
823
893
  end
@@ -863,7 +933,7 @@ module Bolt
863
933
 
864
934
  # Display the list of available Bolt guides.
865
935
  def list_topics
866
- outputter.print_topics(guides.keys)
936
+ outputter.print_topics(guides.keys - ['guide'])
867
937
  0
868
938
  end
869
939
 
@@ -900,20 +970,17 @@ module Bolt
900
970
  # the path is a Puppet file path and looks for the file in a module's files
901
971
  # directory.
902
972
  #
903
- def find_file(path)
904
- unless File.exist?(path) || Pathname.new(path).absolute?
905
- modulepath = Bolt::Config::Modulepath.new(config.modulepath)
906
- modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
907
- mod, file = path.split(File::SEPARATOR, 2)
908
-
909
- if modules[mod]
910
- @logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
911
- path = File.join(modules[mod].path, 'files', file)
912
- end
973
+ def find_file(path, future_file_paths)
974
+ return path if File.exist?(path) || Pathname.new(path).absolute?
975
+ modulepath = Bolt::Config::Modulepath.new(config.modulepath)
976
+ modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
977
+ mod, file = path.split(File::SEPARATOR, 2)
978
+
979
+ if modules[mod]
980
+ @logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
981
+ found = Bolt::Util.find_file_in_module(modules[mod].path, file || "", future_file_paths)
982
+ path = found.nil? ? File.join(modules[mod].path, 'files', file) : found
913
983
  end
914
-
915
- Bolt::Util.validate_file('script', path)
916
-
917
984
  path
918
985
  end
919
986
 
@@ -935,7 +1002,7 @@ module Bolt
935
1002
 
936
1003
  def analytics
937
1004
  @analytics ||= begin
938
- client = Bolt::Analytics.build_client
1005
+ client = Bolt::Analytics.build_client(config.analytics)
939
1006
  client.bundled_content = bundled_content
940
1007
  client
941
1008
  end
data/lib/bolt/config.rb CHANGED
@@ -169,6 +169,7 @@ module Bolt
169
169
  @config_files = []
170
170
 
171
171
  default_data = {
172
+ 'analytics' => true,
172
173
  'apply-settings' => {},
173
174
  'color' => true,
174
175
  'compile-concurrency' => Etc.nprocessors,
@@ -263,6 +264,8 @@ module Bolt
263
264
  # Disabled warnings are concatenated
264
265
  when 'disable-warnings'
265
266
  val1.concat(val2)
267
+ when 'analytics'
268
+ val1 && val2
266
269
  # All other values are overwritten
267
270
  else
268
271
  val2
@@ -328,13 +331,6 @@ module Bolt
328
331
  end
329
332
 
330
333
  def validate
331
- if @data['future']
332
- Bolt::Logger.warn(
333
- "future_option",
334
- "Configuration option 'future' no longer exposes future behavior."
335
- )
336
- end
337
-
338
334
  if @data['modulepath']&.include?(@project.managed_moduledir.to_s)
339
335
  raise Bolt::ValidationError,
340
336
  "Found invalid path in modulepath: #{@project.managed_moduledir}. This path "\
@@ -395,6 +391,10 @@ module Bolt
395
391
  @data['format'] = value
396
392
  end
397
393
 
394
+ def future
395
+ @data['future']
396
+ end
397
+
398
398
  def trace
399
399
  @data['trace']
400
400
  end
@@ -459,6 +459,10 @@ module Bolt
459
459
  Set.new(@project.disable_warnings + @data['disable-warnings'])
460
460
  end
461
461
 
462
+ def analytics
463
+ @data['analytics']
464
+ end
465
+
462
466
  # Check if there is a case-insensitive match to the path
463
467
  def check_path_case(type, paths)
464
468
  return if paths.nil?
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/config/transport/ssh'
4
- require 'bolt/config/transport/winrm'
5
- require 'bolt/config/transport/orch'
3
+ require 'bolt/config/transport/docker'
6
4
  require 'bolt/config/transport/local'
7
5
  require 'bolt/config/transport/lxd'
8
- require 'bolt/config/transport/docker'
6
+ require 'bolt/config/transport/orch'
7
+ require 'bolt/config/transport/podman'
9
8
  require 'bolt/config/transport/remote'
9
+ require 'bolt/config/transport/ssh'
10
+ require 'bolt/config/transport/winrm'
10
11
 
11
12
  module Bolt
12
13
  class Config
@@ -14,13 +15,14 @@ module Bolt
14
15
  # Transport config classes. Used to load default transport config which
15
16
  # gets passed along to the inventory.
16
17
  TRANSPORT_CONFIG = {
17
- 'ssh' => Bolt::Config::Transport::SSH,
18
- 'winrm' => Bolt::Config::Transport::WinRM,
19
- 'pcp' => Bolt::Config::Transport::Orch,
18
+ 'docker' => Bolt::Config::Transport::Docker,
20
19
  'local' => Bolt::Config::Transport::Local,
21
20
  'lxd' => Bolt::Config::Transport::LXD,
22
- 'docker' => Bolt::Config::Transport::Docker,
23
- 'remote' => Bolt::Config::Transport::Remote
21
+ 'pcp' => Bolt::Config::Transport::Orch,
22
+ 'podman' => Bolt::Config::Transport::Podman,
23
+ 'remote' => Bolt::Config::Transport::Remote,
24
+ 'ssh' => Bolt::Config::Transport::SSH,
25
+ 'winrm' => Bolt::Config::Transport::WinRM
24
26
  }.freeze
25
27
 
26
28
  # Plugin definition. This is used by the JSON schemas to indicate that an option
@@ -55,6 +57,13 @@ module Bolt
55
57
  # Definitions used to validate config options.
56
58
  # https://github.com/puppetlabs/bolt/blob/main/schemas/README.md
57
59
  OPTIONS = {
60
+ "analytics" => {
61
+ description: "Whether to disable analytics. Setting this option to 'false' in the system-wide "\
62
+ "or user-level configuration will disable analytics for all projects, even if this "\
63
+ "option is set to 'true' at the project level.",
64
+ type: [TrueClass, FalseClass],
65
+ _example: false
66
+ },
58
67
  "apply-settings" => {
59
68
  description: "A map of Puppet settings to use when applying Puppet code using the `apply` "\
60
69
  "plan function or the `bolt apply` command.",
@@ -133,6 +142,20 @@ module Bolt
133
142
  _example: "json",
134
143
  _default: "human"
135
144
  },
145
+ "future" => {
146
+ description: "Enable new Bolt features that may include breaking changes.",
147
+ type: Hash,
148
+ properties: {
149
+ "file_paths" => {
150
+ description: "Load scripts from the `scripts/` directory of a module",
151
+ type: [TrueClass, FalseClass],
152
+ _example: true,
153
+ _default: false
154
+ }
155
+ },
156
+ _plugin: false,
157
+ _example: { 'file_paths' => true }
158
+ },
136
159
  "hiera-config" => {
137
160
  description: "The path to the Hiera configuration file.",
138
161
  type: String,
@@ -497,6 +520,12 @@ module Bolt
497
520
  _plugin: true,
498
521
  _example: { "job-poll-interval" => 15, "job-poll-timeout" => 30 }
499
522
  },
523
+ "podman" => {
524
+ description: "A map of configuration options for the podman transport.",
525
+ type: Hash,
526
+ _plugin: true,
527
+ _example: { "cleanup" => false, "tmpdir" => "/mount/tmp" }
528
+ },
500
529
  "remote" => {
501
530
  description: "A map of configuration options for the remote transport.",
502
531
  type: Hash,
@@ -532,6 +561,7 @@ module Bolt
532
561
 
533
562
  # Options that are available in a bolt-defaults.yaml file
534
563
  DEFAULTS_OPTIONS = %w[
564
+ analytics
535
565
  color
536
566
  compile-concurrency
537
567
  concurrency
@@ -551,12 +581,14 @@ module Bolt
551
581
 
552
582
  # Options that are available in a bolt-project.yaml file
553
583
  PROJECT_OPTIONS = %w[
584
+ analytics
554
585
  apply-settings
555
586
  color
556
587
  compile-concurrency
557
588
  concurrency
558
589
  disable-warnings
559
590
  format
591
+ future
560
592
  hiera-config
561
593
  log
562
594
  modulepath