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.
- checksums.yaml +4 -4
- data/Puppetfile +2 -2
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
- data/guides/guide.txt +17 -0
- data/guides/links.txt +13 -0
- data/guides/targets.txt +29 -0
- data/guides/transports.txt +23 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/bolt_option_parser.rb +329 -225
- data/lib/bolt/cli.rb +58 -29
- data/lib/bolt/config.rb +11 -7
- data/lib/bolt/config/options.rb +41 -9
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +15 -0
- data/lib/bolt/executor.rb +17 -13
- data/lib/bolt/inventory.rb +5 -4
- data/lib/bolt/inventory/inventory.rb +3 -2
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
- data/lib/bolt/outputter/human.rb +242 -76
- data/lib/bolt/outputter/json.rb +6 -4
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal/yaml_plan/step.rb +4 -2
- data/lib/bolt/plan_creator.rb +2 -2
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/puppetdb/client.rb +54 -0
- data/lib/bolt/result.rb +1 -4
- data/lib/bolt/shell/bash.rb +23 -10
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +23 -34
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +3 -6
- data/lib/bolt/util.rb +34 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -0
- data/lib/bolt_spec/plans/mock_executor.rb +91 -11
- data/modules/puppet_connect/plans/test_input_data.pp +22 -0
- 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|
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
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?
|
data/lib/bolt/config/options.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'bolt/config/transport/
|
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/
|
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
|
-
'
|
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
|
-
'
|
23
|
-
'
|
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
|