bolt 3.4.0 → 3.7.1

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 +2 -2
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -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_container.rb +162 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  16. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  17. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  18. data/guides/guide.txt +17 -0
  19. data/guides/links.txt +13 -0
  20. data/guides/targets.txt +29 -0
  21. data/guides/transports.txt +23 -0
  22. data/lib/bolt/analytics.rb +4 -8
  23. data/lib/bolt/bolt_option_parser.rb +329 -225
  24. data/lib/bolt/cli.rb +58 -29
  25. data/lib/bolt/config.rb +11 -7
  26. data/lib/bolt/config/options.rb +41 -9
  27. data/lib/bolt/config/transport/podman.rb +33 -0
  28. data/lib/bolt/container_result.rb +105 -0
  29. data/lib/bolt/error.rb +15 -0
  30. data/lib/bolt/executor.rb +17 -13
  31. data/lib/bolt/inventory.rb +5 -4
  32. data/lib/bolt/inventory/inventory.rb +3 -2
  33. data/lib/bolt/inventory/options.rb +9 -0
  34. data/lib/bolt/inventory/target.rb +16 -0
  35. data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
  36. data/lib/bolt/outputter/human.rb +242 -76
  37. data/lib/bolt/outputter/json.rb +6 -4
  38. data/lib/bolt/outputter/logger.rb +17 -0
  39. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  40. data/lib/bolt/plan_creator.rb +2 -2
  41. data/lib/bolt/plugin.rb +13 -11
  42. data/lib/bolt/puppetdb/client.rb +54 -0
  43. data/lib/bolt/result.rb +1 -4
  44. data/lib/bolt/shell/bash.rb +23 -10
  45. data/lib/bolt/transport/docker.rb +1 -1
  46. data/lib/bolt/transport/docker/connection.rb +23 -34
  47. data/lib/bolt/transport/podman.rb +19 -0
  48. data/lib/bolt/transport/podman/connection.rb +98 -0
  49. data/lib/bolt/transport/ssh/connection.rb +3 -6
  50. data/lib/bolt/util.rb +34 -0
  51. data/lib/bolt/version.rb +1 -1
  52. data/lib/bolt_server/transport_app.rb +3 -0
  53. data/lib/bolt_spec/plans/mock_executor.rb +91 -11
  54. data/modules/puppet_connect/plans/test_input_data.pp +22 -0
  55. metadata +13 -2
data/lib/bolt/cli.rb CHANGED
@@ -47,6 +47,8 @@ module Bolt
47
47
  'guide' => %w[]
48
48
  }.freeze
49
49
 
50
+ TARGETING_OPTIONS = %i[query rerun targets].freeze
51
+
50
52
  attr_reader :config, :options
51
53
 
52
54
  def initialize(argv)
@@ -262,7 +264,7 @@ module Bolt
262
264
  end
263
265
 
264
266
  def update_targets(options)
265
- target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
267
+ target_opts = options.keys.select { |opt| TARGETING_OPTIONS.include?(opt) }
266
268
  target_string = "'--targets', '--rerun', or '--query'"
267
269
  if target_opts.length > 1
268
270
  raise Bolt::CLIError, "Only one targeting option #{target_string} can be specified"
@@ -541,7 +543,11 @@ module Bolt
541
543
  end
542
544
  code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
543
545
  else
544
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
546
+ executor = Bolt::Executor.new(config.concurrency,
547
+ analytics,
548
+ options[:noop],
549
+ config.modified_concurrency,
550
+ config.future)
545
551
  targets = options[:targets]
546
552
 
547
553
  results = nil
@@ -630,8 +636,39 @@ module Bolt
630
636
  end
631
637
 
632
638
  def list_targets
633
- inventoryfile = config.inventoryfile || config.default_inventoryfile
639
+ if options.keys.any? { |key| TARGETING_OPTIONS.include?(key) }
640
+ target_flag = true
641
+ else
642
+ options[:targets] = 'all'
643
+ end
644
+
645
+ outputter.print_targets(
646
+ group_targets_by_source,
647
+ inventory.source,
648
+ config.default_inventoryfile,
649
+ target_flag
650
+ )
651
+ end
634
652
 
653
+ def show_targets
654
+ if options.keys.any? { |key| TARGETING_OPTIONS.include?(key) }
655
+ target_flag = true
656
+ else
657
+ options[:targets] = 'all'
658
+ end
659
+
660
+ outputter.print_target_info(
661
+ group_targets_by_source,
662
+ inventory.source,
663
+ config.default_inventoryfile,
664
+ target_flag
665
+ )
666
+ end
667
+
668
+ # Returns a hash of targets sorted by those that are found in the
669
+ # inventory and those that are provided on the command line.
670
+ #
671
+ private def group_targets_by_source
635
672
  # Retrieve the known group and target names. This needs to be done before
636
673
  # updating targets, as that will add adhoc targets to the inventory.
637
674
  known_names = inventory.target_names
@@ -642,22 +679,11 @@ module Bolt
642
679
  known_names.include?(target.name)
643
680
  end
644
681
 
645
- target_list = {
646
- inventory: inventory_targets,
647
- adhoc: adhoc_targets
648
- }
649
-
650
- outputter.print_targets(target_list, inventoryfile)
651
- end
652
-
653
- def show_targets
654
- update_targets(options)
655
- outputter.print_target_info(options[:targets])
682
+ { inventory: inventory_targets, adhoc: adhoc_targets }
656
683
  end
657
684
 
658
685
  def list_groups
659
- groups = inventory.group_names
660
- outputter.print_groups(groups)
686
+ outputter.print_groups(inventory.group_names.sort, inventory.source, config.default_inventoryfile)
661
687
  end
662
688
 
663
689
  def run_plan(plan_name, plan_arguments, nodes, options)
@@ -688,7 +714,11 @@ module Bolt
688
714
  plan_context = { plan_name: plan_name,
689
715
  params: plan_arguments }
690
716
 
691
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
717
+ executor = Bolt::Executor.new(config.concurrency,
718
+ analytics,
719
+ options[:noop],
720
+ config.modified_concurrency,
721
+ config.future)
692
722
  if %w[human rainbow].include?(options.fetch(:format, 'human'))
693
723
  executor.subscribe(outputter)
694
724
  else
@@ -724,7 +754,11 @@ module Bolt
724
754
  Bolt::Logger.warn("empty_manifest", message)
725
755
  end
726
756
 
727
- executor = Bolt::Executor.new(config.concurrency, analytics, noop, config.modified_concurrency)
757
+ executor = Bolt::Executor.new(config.concurrency,
758
+ analytics,
759
+ noop,
760
+ config.modified_concurrency,
761
+ config.future)
728
762
  executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
729
763
  executor.subscribe(log_outputter)
730
764
  # apply logging looks like plan logging, so tell the outputter we're in a
@@ -809,15 +843,10 @@ module Bolt
809
843
  #
810
844
  def assert_project_file(project)
811
845
  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
846
+ command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
847
+
848
+ msg = "Could not find project configuration file #{project.project_file}, unable "\
849
+ "to install modules. To create a Bolt project, run '#{command}'."
821
850
 
822
851
  raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
823
852
  end
@@ -863,7 +892,7 @@ module Bolt
863
892
 
864
893
  # Display the list of available Bolt guides.
865
894
  def list_topics
866
- outputter.print_topics(guides.keys)
895
+ outputter.print_topics(guides.keys - ['guide'])
867
896
  0
868
897
  end
869
898
 
@@ -935,7 +964,7 @@ module Bolt
935
964
 
936
965
  def analytics
937
966
  @analytics ||= begin
938
- client = Bolt::Analytics.build_client
967
+ client = Bolt::Analytics.build_client(config.analytics)
939
968
  client.bundled_content = bundled_content
940
969
  client
941
970
  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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/config/transport/base'
5
+
6
+ module Bolt
7
+ class Config
8
+ module Transport
9
+ class Podman < Base
10
+ OPTIONS = %w[
11
+ cleanup
12
+ host
13
+ interpreters
14
+ shell-command
15
+ tmpdir
16
+ tty
17
+ ].freeze
18
+
19
+ DEFAULTS = {
20
+ 'cleanup' => true
21
+ }.freeze
22
+
23
+ private def validate
24
+ super
25
+
26
+ if @config['interpreters']
27
+ @config['interpreters'] = normalize_interpreters(@config['interpreters'])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'bolt/error'
5
+ require 'bolt/result'
6
+
7
+ module Bolt
8
+ class ContainerResult
9
+ attr_reader :value, :object
10
+
11
+ def self.from_exception(exception, exit_code, image, position: [])
12
+ details = Bolt::Result.create_details(position)
13
+ error = {
14
+ 'kind' => 'puppetlabs.tasks/container-error',
15
+ 'issue_code' => 'CONTAINER_ERROR',
16
+ 'msg' => "Error running container '#{image}': #{exception}",
17
+ 'details' => details
18
+ }
19
+ error['details']['exit_code'] = exit_code
20
+ ContainerResult.new({ '_error' => error }, object: image)
21
+ end
22
+
23
+ def _pcore_init_hash
24
+ { 'value' => @value,
25
+ 'object' => @image }
26
+ end
27
+
28
+ # First argument can't be named given the way that Puppet deserializes variables
29
+ def initialize(value = nil, object: nil)
30
+ @value = value || {}
31
+ @object = object
32
+ end
33
+
34
+ def eql?(other)
35
+ self.class == other.class &&
36
+ value == other.value
37
+ end
38
+ alias == eql?
39
+
40
+ def [](key)
41
+ value[key]
42
+ end
43
+
44
+ def to_json(opts = nil)
45
+ to_data.to_json(opts)
46
+ end
47
+ alias to_s to_json
48
+
49
+ # This is the value with all non-UTF-8 characters removed, suitable for
50
+ # printing or converting to JSON. It *should* only be possible to have
51
+ # non-UTF-8 characters in stdout/stderr keys as they are not allowed from
52
+ # tasks but we scrub the whole thing just in case.
53
+ def safe_value
54
+ Bolt::Util.walk_vals(value) do |val|
55
+ if val.is_a?(String)
56
+ # Replace invalid bytes with hex codes, ie. \xDE\xAD\xBE\xEF
57
+ val.scrub { |c| c.bytes.map { |b| "\\x" + b.to_s(16).upcase }.join }
58
+ else
59
+ val
60
+ end
61
+ end
62
+ end
63
+
64
+ def stdout
65
+ value['stdout']
66
+ end
67
+
68
+ def stderr
69
+ value['stderr']
70
+ end
71
+
72
+ def to_data
73
+ {
74
+ "object" => object,
75
+ "status" => status,
76
+ "value" => safe_value
77
+ }
78
+ end
79
+
80
+ def status
81
+ ok? ? 'success' : 'failure'
82
+ end
83
+
84
+ def ok?
85
+ error_hash.nil?
86
+ end
87
+ alias ok ok?
88
+ alias success? ok?
89
+
90
+ # This allows access to errors outside puppet compilation
91
+ # it should be prefered over error in bolt code
92
+ def error_hash
93
+ value['_error']
94
+ end
95
+
96
+ # Warning: This will fail outside of a compilation.
97
+ # Use error_hash inside bolt.
98
+ # Is it crazy for this to behave differently outside a compiler?
99
+ def error
100
+ if error_hash
101
+ Puppet::DataTypes::Error.from_asserted_hash(error_hash)
102
+ end
103
+ end
104
+ end
105
+ end