bolt 3.5.0 → 3.6.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/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/cli.rb +32 -24
- 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/executor.rb +15 -11
- data/lib/bolt/outputter/human.rb +155 -69
- data/lib/bolt/outputter/json.rb +3 -1
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +8 -4
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/util.rb +11 -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 +2 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa3e5ea7d868b032557568e9635fcf159f00b81e4961dce4032246cc150a7c16
|
4
|
+
data.tar.gz: 0e9fada8a0518ab174412e21ec843091272cb0afe87980703ebd6d8ed91002dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66d8df12e0142b6d5872a40f7c0d97b0438c833e4681be6646e17b85496c3a9e1148ff0a2ff34572e0ce788861d928c406dddcb97feb5281ac0d7eb2d7d14ead
|
7
|
+
data.tar.gz: f6dafaa37a4af7633610e26cc084d69c865f0b8df18221d407f2ee077e58e0a582e88cccf49d3a590e7984d56d8ac467ee8704e6ac0e60cc7e37b1a846396d26
|
data/lib/bolt/analytics.rb
CHANGED
@@ -29,7 +29,7 @@ module Bolt
|
|
29
29
|
yaml_plan_count: :cd13
|
30
30
|
}.freeze
|
31
31
|
|
32
|
-
def self.build_client
|
32
|
+
def self.build_client(enabled = true)
|
33
33
|
logger = Bolt::Logger.logger(self)
|
34
34
|
begin
|
35
35
|
config_file = config_path
|
@@ -38,7 +38,7 @@ module Bolt
|
|
38
38
|
config = { 'disabled' => true }
|
39
39
|
end
|
40
40
|
|
41
|
-
if config['disabled'] || ENV['BOLT_DISABLE_ANALYTICS']
|
41
|
+
if !enabled || config['disabled'] || ENV['BOLT_DISABLE_ANALYTICS']
|
42
42
|
logger.debug "Analytics opt-out is set, analytics will be disabled"
|
43
43
|
NoopClient.new
|
44
44
|
else
|
@@ -80,12 +80,8 @@ module Bolt
|
|
80
80
|
unless ENV['BOLT_DISABLE_ANALYTICS']
|
81
81
|
msg = <<~ANALYTICS
|
82
82
|
Bolt collects data about how you use it. You can opt out of providing this data.
|
83
|
-
|
84
|
-
|
85
|
-
disabled: true
|
86
|
-
|
87
|
-
Read more about what data Bolt collects and why here:
|
88
|
-
https://puppet.com/docs/bolt/latest/bolt_installing.html#analytics-data-collection
|
83
|
+
To learn how to disable data collection, or see what data Bolt collects and why,
|
84
|
+
see http://pup.pt/bolt-analytics
|
89
85
|
ANALYTICS
|
90
86
|
Bolt::Logger.warn_once('analytics_opt_out', msg)
|
91
87
|
end
|
data/lib/bolt/cli.rb
CHANGED
@@ -541,7 +541,11 @@ module Bolt
|
|
541
541
|
end
|
542
542
|
code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
|
543
543
|
else
|
544
|
-
executor = Bolt::Executor.new(config.concurrency,
|
544
|
+
executor = Bolt::Executor.new(config.concurrency,
|
545
|
+
analytics,
|
546
|
+
options[:noop],
|
547
|
+
config.modified_concurrency,
|
548
|
+
config.future)
|
545
549
|
targets = options[:targets]
|
546
550
|
|
547
551
|
results = nil
|
@@ -631,7 +635,18 @@ module Bolt
|
|
631
635
|
|
632
636
|
def list_targets
|
633
637
|
inventoryfile = config.inventoryfile || config.default_inventoryfile
|
638
|
+
outputter.print_targets(group_targets_by_source, inventoryfile)
|
639
|
+
end
|
634
640
|
|
641
|
+
def show_targets
|
642
|
+
inventoryfile = config.inventoryfile || config.default_inventoryfile
|
643
|
+
outputter.print_target_info(group_targets_by_source, inventoryfile)
|
644
|
+
end
|
645
|
+
|
646
|
+
# Returns a hash of targets sorted by those that are found in the
|
647
|
+
# inventory and those that are provided on the command line.
|
648
|
+
#
|
649
|
+
private def group_targets_by_source
|
635
650
|
# Retrieve the known group and target names. This needs to be done before
|
636
651
|
# updating targets, as that will add adhoc targets to the inventory.
|
637
652
|
known_names = inventory.target_names
|
@@ -642,17 +657,7 @@ module Bolt
|
|
642
657
|
known_names.include?(target.name)
|
643
658
|
end
|
644
659
|
|
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])
|
660
|
+
{ inventory: inventory_targets, adhoc: adhoc_targets }
|
656
661
|
end
|
657
662
|
|
658
663
|
def list_groups
|
@@ -688,7 +693,11 @@ module Bolt
|
|
688
693
|
plan_context = { plan_name: plan_name,
|
689
694
|
params: plan_arguments }
|
690
695
|
|
691
|
-
executor = Bolt::Executor.new(config.concurrency,
|
696
|
+
executor = Bolt::Executor.new(config.concurrency,
|
697
|
+
analytics,
|
698
|
+
options[:noop],
|
699
|
+
config.modified_concurrency,
|
700
|
+
config.future)
|
692
701
|
if %w[human rainbow].include?(options.fetch(:format, 'human'))
|
693
702
|
executor.subscribe(outputter)
|
694
703
|
else
|
@@ -724,7 +733,11 @@ module Bolt
|
|
724
733
|
Bolt::Logger.warn("empty_manifest", message)
|
725
734
|
end
|
726
735
|
|
727
|
-
executor = Bolt::Executor.new(config.concurrency,
|
736
|
+
executor = Bolt::Executor.new(config.concurrency,
|
737
|
+
analytics,
|
738
|
+
noop,
|
739
|
+
config.modified_concurrency,
|
740
|
+
config.future)
|
728
741
|
executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
|
729
742
|
executor.subscribe(log_outputter)
|
730
743
|
# apply logging looks like plan logging, so tell the outputter we're in a
|
@@ -809,15 +822,10 @@ module Bolt
|
|
809
822
|
#
|
810
823
|
def assert_project_file(project)
|
811
824
|
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
|
825
|
+
command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
|
826
|
+
|
827
|
+
msg = "Could not find project configuration file #{project.project_file}, unable "\
|
828
|
+
"to install modules. To create a Bolt project, run '#{command}'."
|
821
829
|
|
822
830
|
raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
|
823
831
|
end
|
@@ -935,7 +943,7 @@ module Bolt
|
|
935
943
|
|
936
944
|
def analytics
|
937
945
|
@analytics ||= begin
|
938
|
-
client = Bolt::Analytics.build_client
|
946
|
+
client = Bolt::Analytics.build_client(config.analytics)
|
939
947
|
client.bundled_content = bundled_content
|
940
948
|
client
|
941
949
|
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
|
data/lib/bolt/executor.rb
CHANGED
@@ -12,34 +12,37 @@ require 'bolt/config'
|
|
12
12
|
require 'bolt/result_set'
|
13
13
|
require 'bolt/puppetdb'
|
14
14
|
# Load transports
|
15
|
-
require 'bolt/transport/
|
16
|
-
require 'bolt/transport/winrm'
|
17
|
-
require 'bolt/transport/orch'
|
15
|
+
require 'bolt/transport/docker'
|
18
16
|
require 'bolt/transport/local'
|
19
17
|
require 'bolt/transport/lxd'
|
20
|
-
require 'bolt/transport/
|
18
|
+
require 'bolt/transport/orch'
|
19
|
+
require 'bolt/transport/podman'
|
21
20
|
require 'bolt/transport/remote'
|
21
|
+
require 'bolt/transport/ssh'
|
22
|
+
require 'bolt/transport/winrm'
|
22
23
|
require 'bolt/yarn'
|
23
24
|
|
24
25
|
module Bolt
|
25
26
|
TRANSPORTS = {
|
26
|
-
|
27
|
-
winrm: Bolt::Transport::WinRM,
|
28
|
-
pcp: Bolt::Transport::Orch,
|
27
|
+
docker: Bolt::Transport::Docker,
|
29
28
|
local: Bolt::Transport::Local,
|
30
29
|
lxd: Bolt::Transport::LXD,
|
31
|
-
|
32
|
-
|
30
|
+
pcp: Bolt::Transport::Orch,
|
31
|
+
podman: Bolt::Transport::Podman,
|
32
|
+
remote: Bolt::Transport::Remote,
|
33
|
+
ssh: Bolt::Transport::SSH,
|
34
|
+
winrm: Bolt::Transport::WinRM
|
33
35
|
}.freeze
|
34
36
|
|
35
37
|
class Executor
|
36
|
-
attr_reader :noop, :transports, :in_parallel
|
38
|
+
attr_reader :noop, :transports, :in_parallel, :future
|
37
39
|
attr_accessor :run_as
|
38
40
|
|
39
41
|
def initialize(concurrency = 1,
|
40
42
|
analytics = Bolt::Analytics::NoopClient.new,
|
41
43
|
noop = false,
|
42
|
-
modified_concurrency = false
|
44
|
+
modified_concurrency = false,
|
45
|
+
future = {})
|
43
46
|
# lazy-load expensive gem code
|
44
47
|
require 'concurrent'
|
45
48
|
@analytics = analytics
|
@@ -64,6 +67,7 @@ module Bolt
|
|
64
67
|
@noop = noop
|
65
68
|
@run_as = nil
|
66
69
|
@in_parallel = false
|
70
|
+
@future = future
|
67
71
|
@pool = if concurrency > 0
|
68
72
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
69
73
|
else
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -6,6 +6,7 @@ module Bolt
|
|
6
6
|
class Outputter
|
7
7
|
class Human < Bolt::Outputter
|
8
8
|
COLORS = {
|
9
|
+
dim: "2", # Dim, the other color of the rainbow
|
9
10
|
red: "31",
|
10
11
|
green: "32",
|
11
12
|
yellow: "33",
|
@@ -313,78 +314,115 @@ module Bolt
|
|
313
314
|
|
314
315
|
# @param [Hash] task A hash representing the task
|
315
316
|
def print_task_info(task)
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
317
|
+
params = (task.parameters || []).sort
|
318
|
+
|
319
|
+
info = +''
|
320
|
+
|
321
|
+
# Add task name and description
|
322
|
+
info << colorize(:cyan, "#{task.name}\n")
|
323
|
+
info << if task.description
|
324
|
+
indent(2, task.description.chomp)
|
321
325
|
else
|
322
|
-
|
326
|
+
indent(2, 'No description')
|
323
327
|
end
|
328
|
+
info << "\n\n"
|
324
329
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
330
|
+
# Build usage string
|
331
|
+
usage = +''
|
332
|
+
usage << if Bolt::Util.powershell?
|
333
|
+
"Invoke-BoltTask -Name #{task.name} -Targets <targets>"
|
334
|
+
else
|
335
|
+
"bolt task run #{task.name} --targets <targets>"
|
336
|
+
end
|
337
|
+
usage << (Bolt::Util.powershell? ? ' [-Noop]' : ' [--noop]') if task.supports_noop
|
338
|
+
params.each do |name, data|
|
339
|
+
usage << if data['type']&.start_with?('Optional')
|
340
|
+
" [#{name}=<value>]"
|
331
341
|
else
|
332
|
-
" #{
|
342
|
+
" #{name}=<value>"
|
333
343
|
end
|
334
344
|
end
|
335
345
|
|
336
|
-
|
337
|
-
|
346
|
+
# Add usage
|
347
|
+
info << colorize(:cyan, "Usage\n")
|
348
|
+
info << indent(2, wrap(usage))
|
349
|
+
info << "\n"
|
350
|
+
|
351
|
+
# Add parameters, if any
|
352
|
+
if params.any?
|
353
|
+
info << colorize(:cyan, "Parameters\n")
|
354
|
+
params.each do |name, data|
|
355
|
+
info << indent(2, "#{colorize(:yellow, name)} #{colorize(:dim, data['type'] || 'Any')}\n")
|
356
|
+
info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
|
357
|
+
info << indent(4, "Default: #{data['default'].inspect}\n") if data.key?('default')
|
358
|
+
info << "\n"
|
359
|
+
end
|
338
360
|
end
|
339
361
|
|
340
|
-
|
341
|
-
task_info << " - #{task.description}" if task.description
|
342
|
-
task_info << "\n\n"
|
343
|
-
task_info << "USAGE:\n#{usage}\n\n"
|
344
|
-
task_info << "PARAMETERS:\n#{pretty_params}\n" unless pretty_params.empty?
|
345
|
-
task_info << "MODULE:\n"
|
346
|
-
|
362
|
+
# Add module location
|
347
363
|
path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
364
|
+
info << colorize(:cyan, "Module\n")
|
365
|
+
info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
366
|
+
indent(2, 'built-in module')
|
367
|
+
else
|
368
|
+
indent(2, path)
|
369
|
+
end
|
370
|
+
|
371
|
+
@stream.puts info
|
354
372
|
end
|
355
373
|
|
356
374
|
# @param [Hash] plan A hash representing the plan
|
357
375
|
def print_plan_info(plan)
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
376
|
+
params = plan['parameters'].sort
|
377
|
+
|
378
|
+
info = +''
|
379
|
+
|
380
|
+
# Add plan name and description
|
381
|
+
info << colorize(:cyan, "#{plan['name']}\n")
|
382
|
+
info << if plan['description']
|
383
|
+
indent(2, plan['description'].chomp)
|
363
384
|
else
|
364
|
-
|
385
|
+
indent(2, 'No description')
|
365
386
|
end
|
387
|
+
info << "\n\n"
|
366
388
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
389
|
+
# Build the usage string
|
390
|
+
usage = +''
|
391
|
+
usage << if Bolt::Util.powershell?
|
392
|
+
"Invoke-BoltPlan -Name #{plan['name']}"
|
393
|
+
else
|
394
|
+
"bolt plan run #{plan['name']}"
|
395
|
+
end
|
396
|
+
params.each do |name, data|
|
397
|
+
usage << (data.include?('default_value') ? " [#{name}=<value>]" : " #{name}=<value>")
|
372
398
|
end
|
373
399
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
plan_info << "PARAMETERS:\n#{pretty_params}\n" unless plan['parameters'].empty?
|
379
|
-
plan_info << "MODULE:\n"
|
400
|
+
# Add usage
|
401
|
+
info << colorize(:cyan, "Usage\n")
|
402
|
+
info << indent(2, wrap(usage))
|
403
|
+
info << "\n"
|
380
404
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
405
|
+
# Add parameters, if any
|
406
|
+
if params.any?
|
407
|
+
info << colorize(:cyan, "Parameters\n")
|
408
|
+
|
409
|
+
params.each do |name, data|
|
410
|
+
info << indent(2, "#{colorize(:yellow, name)} #{colorize(:dim, data['type'])}\n")
|
411
|
+
info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
|
412
|
+
info << indent(4, "Default: #{data['default_value']}\n") unless data['default_value'].nil?
|
413
|
+
info << "\n"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Add module location
|
418
|
+
info << colorize(:cyan, "Module\n")
|
419
|
+
info << if plan['module'].start_with?(Bolt::Config::Modulepath::MODULES_PATH)
|
420
|
+
indent(2, 'built-in module')
|
421
|
+
else
|
422
|
+
indent(2, plan['module'])
|
423
|
+
end
|
424
|
+
|
425
|
+
@stream.puts info
|
388
426
|
end
|
389
427
|
|
390
428
|
def print_plans(plans, modulepath)
|
@@ -452,29 +490,77 @@ module Bolt
|
|
452
490
|
targets += target_list[:inventory].map { |target| [target.name, nil] }
|
453
491
|
targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
|
454
492
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
493
|
+
info = +''
|
494
|
+
|
495
|
+
# Add target list
|
496
|
+
info << colorize(:cyan, "Targets\n")
|
497
|
+
info << if targets.any?
|
498
|
+
format_table(targets, 2, 2).to_s
|
499
|
+
else
|
500
|
+
indent(2, 'No targets')
|
501
|
+
end
|
502
|
+
info << "\n\n"
|
503
|
+
|
504
|
+
@stream.puts info
|
505
|
+
|
506
|
+
print_inventory_summary(
|
507
|
+
target_list[:inventory].count,
|
508
|
+
target_list[:adhoc].count,
|
509
|
+
inventoryfile
|
510
|
+
)
|
511
|
+
end
|
512
|
+
|
513
|
+
def print_target_info(target_list, inventoryfile)
|
514
|
+
adhoc_targets = target_list[:adhoc].map(&:name).to_set
|
515
|
+
inventory_targets = target_list[:inventory].map(&:name).to_set
|
516
|
+
targets = target_list.values.flatten.sort_by(&:name)
|
517
|
+
|
518
|
+
info = +''
|
459
519
|
|
460
|
-
|
461
|
-
|
462
|
-
|
520
|
+
if targets.any?
|
521
|
+
adhoc = colorize(:yellow, " (Not found in inventory file)")
|
522
|
+
|
523
|
+
targets.each do |target|
|
524
|
+
info << colorize(:cyan, target.name)
|
525
|
+
info << adhoc if adhoc_targets.include?(target.name)
|
526
|
+
info << "\n"
|
527
|
+
info << indent(2, target.detail.to_yaml.lines.drop(1).join)
|
528
|
+
info << "\n"
|
529
|
+
end
|
463
530
|
else
|
464
|
-
|
531
|
+
info << colorize(:cyan, "Targets\n")
|
532
|
+
info << indent(2, "No targets\n\n")
|
465
533
|
end
|
466
534
|
|
467
|
-
@stream.puts
|
468
|
-
@stream.puts "#{targets.count} total, #{target_list[:inventory].count} from inventory, "\
|
469
|
-
"#{target_list[:adhoc].count} adhoc"
|
470
|
-
end
|
535
|
+
@stream.puts info
|
471
536
|
|
472
|
-
|
473
|
-
|
474
|
-
|
537
|
+
print_inventory_summary(
|
538
|
+
inventory_targets.count,
|
539
|
+
adhoc_targets.count,
|
540
|
+
inventoryfile
|
475
541
|
)
|
476
|
-
|
477
|
-
|
542
|
+
end
|
543
|
+
|
544
|
+
private def print_inventory_summary(inventory_count, adhoc_count, inventoryfile)
|
545
|
+
info = +''
|
546
|
+
|
547
|
+
# Add inventory file source
|
548
|
+
info << colorize(:cyan, "Inventory file\n")
|
549
|
+
info << if File.exist?(inventoryfile)
|
550
|
+
indent(2, "#{inventoryfile}\n")
|
551
|
+
else
|
552
|
+
indent(2, wrap("Tried to load inventory from #{inventoryfile}, but the file does not exist\n"))
|
553
|
+
end
|
554
|
+
info << "\n"
|
555
|
+
|
556
|
+
# Add target count summary
|
557
|
+
count = "#{inventory_count + adhoc_count} total, "\
|
558
|
+
"#{inventory_count} from inventory, "\
|
559
|
+
"#{adhoc_count} adhoc"
|
560
|
+
info << colorize(:cyan, "Target count\n")
|
561
|
+
info << indent(2, count)
|
562
|
+
|
563
|
+
@stream.puts info
|
478
564
|
end
|
479
565
|
|
480
566
|
def print_groups(groups)
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -116,7 +116,9 @@ module Bolt
|
|
116
116
|
)
|
117
117
|
end
|
118
118
|
|
119
|
-
def print_target_info(
|
119
|
+
def print_target_info(target_list, _inventoryfile)
|
120
|
+
targets = target_list.values.flatten
|
121
|
+
|
120
122
|
@stream.puts ::JSON.pretty_generate(
|
121
123
|
targets: targets.map(&:detail),
|
122
124
|
count: targets.count
|
data/lib/bolt/plugin.rb
CHANGED
@@ -178,8 +178,6 @@ module Bolt
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def add_ruby_plugin(plugin_name)
|
181
|
-
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
182
|
-
|
183
181
|
cls_name = Bolt::Util.snake_name_to_class_name(plugin_name)
|
184
182
|
filename = "bolt/plugin/#{plugin_name}"
|
185
183
|
require filename
|
@@ -203,8 +201,6 @@ module Bolt
|
|
203
201
|
}
|
204
202
|
|
205
203
|
mod = modules[plugin_name]
|
206
|
-
raise PluginError::Unknown, plugin_name unless mod&.plugin?
|
207
|
-
raise PluginError::LoadingDisabled, plugin_name unless @load_plugins
|
208
204
|
|
209
205
|
plugin = Bolt::Plugin::Module.load(mod, opts)
|
210
206
|
add_plugin(plugin)
|
@@ -224,6 +220,12 @@ module Bolt
|
|
224
220
|
end
|
225
221
|
end
|
226
222
|
|
223
|
+
def known_plugin?(plugin_name)
|
224
|
+
@plugins.include?(plugin_name) ||
|
225
|
+
RUBY_PLUGINS.include?(plugin_name) ||
|
226
|
+
(modules.include?(plugin_name) && modules[plugin_name].plugin?)
|
227
|
+
end
|
228
|
+
|
227
229
|
def get_hook(plugin_name, hook)
|
228
230
|
plugin = by_name(plugin_name)
|
229
231
|
raise PluginError::Unknown, plugin_name unless plugin
|
@@ -235,16 +237,16 @@ module Bolt
|
|
235
237
|
|
236
238
|
# Calling by_name or get_hook will load any module based plugin automatically
|
237
239
|
def by_name(plugin_name)
|
238
|
-
|
239
|
-
|
240
|
-
|
240
|
+
if known_plugin?(plugin_name)
|
241
|
+
if @plugins.include?(plugin_name)
|
242
|
+
@plugins[plugin_name]
|
243
|
+
elsif !@load_plugins
|
244
|
+
raise PluginError::LoadingDisabled, plugin_name
|
245
|
+
elsif RUBY_PLUGINS.include?(plugin_name)
|
241
246
|
add_ruby_plugin(plugin_name)
|
242
|
-
|
247
|
+
else
|
243
248
|
add_module_plugin(plugin_name)
|
244
249
|
end
|
245
|
-
rescue PluginError::Unknown
|
246
|
-
@unknown << plugin_name
|
247
|
-
nil
|
248
250
|
end
|
249
251
|
end
|
250
252
|
|
@@ -34,6 +34,10 @@ module Bolt
|
|
34
34
|
@container_info["Id"]
|
35
35
|
end
|
36
36
|
|
37
|
+
def run_cmd(cmd, env_vars)
|
38
|
+
Bolt::Util.exec_docker(cmd, env_vars)
|
39
|
+
end
|
40
|
+
|
37
41
|
private def env_hash
|
38
42
|
# Set the DOCKER_HOST if we are using a non-default service-url
|
39
43
|
@docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
|
@@ -90,7 +94,7 @@ module Bolt
|
|
90
94
|
|
91
95
|
def upload_file(source, destination)
|
92
96
|
@logger.trace { "Uploading #{source} to #{destination}" }
|
93
|
-
_out, err, stat =
|
97
|
+
_out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
|
94
98
|
unless stat.exitstatus.zero?
|
95
99
|
raise "Error writing to container #{container_id}: #{err}"
|
96
100
|
end
|
@@ -104,7 +108,7 @@ module Bolt
|
|
104
108
|
# copy the *contents* of the directory.
|
105
109
|
# https://docs.docker.com/engine/reference/commandline/cp/
|
106
110
|
FileUtils.mkdir_p(destination)
|
107
|
-
_out, err, stat =
|
111
|
+
_out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
|
108
112
|
unless stat.exitstatus.zero?
|
109
113
|
raise "Error downloading content from container #{container_id}: #{err}"
|
110
114
|
end
|
@@ -119,9 +123,9 @@ module Bolt
|
|
119
123
|
# @param arguments [Array] Arguments to pass to the docker command
|
120
124
|
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
121
125
|
# @return [Object] Ruby object representation of the JSON string
|
122
|
-
|
126
|
+
def execute_local_json_command(subcommand, arguments = [])
|
123
127
|
cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
|
124
|
-
out, _err, _stat =
|
128
|
+
out, _err, _stat = run_cmd(cmd, env_hash)
|
125
129
|
extract_json(out)
|
126
130
|
end
|
127
131
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'bolt/transport/base'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
module Transport
|
9
|
+
class Podman < Docker
|
10
|
+
def with_connection(target)
|
11
|
+
conn = Connection.new(target)
|
12
|
+
conn.connect
|
13
|
+
yield conn
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'bolt/transport/podman/connection'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logging'
|
4
|
+
require 'bolt/node/errors'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
module Transport
|
8
|
+
class Podman < Docker
|
9
|
+
class Connection < Connection
|
10
|
+
attr_reader :user, :target
|
11
|
+
|
12
|
+
def initialize(target)
|
13
|
+
raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
|
14
|
+
@target = target
|
15
|
+
@user = ENV['USER'] || Etc.getlogin
|
16
|
+
@logger = Bolt::Logger.logger(target.safe_name)
|
17
|
+
@container_info = {}
|
18
|
+
@logger.trace("Initializing podman connection to #{target.safe_name}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_cmd(cmd, env_vars)
|
22
|
+
Bolt::Util.exec_podman(cmd, env_vars)
|
23
|
+
end
|
24
|
+
|
25
|
+
def shell
|
26
|
+
@shell ||= if Bolt::Util.windows?
|
27
|
+
Bolt::Shell::Powershell.new(target, self)
|
28
|
+
else
|
29
|
+
Bolt::Shell::Bash.new(target, self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def connect
|
34
|
+
# We don't actually have a connection, but we do need to
|
35
|
+
# check that the container exists and is running.
|
36
|
+
ps = execute_local_json_command('ps')
|
37
|
+
container = Array(ps).find { |item|
|
38
|
+
item["ID"].to_s.eql?(@target.host) ||
|
39
|
+
item["Id"].to_s.start_with?(@target.host) ||
|
40
|
+
Array(item["Names"]).include?(@target.host)
|
41
|
+
}
|
42
|
+
raise "Could not find a container with name or ID matching '#{@target.host}'" if container.nil?
|
43
|
+
# Now find the indepth container information
|
44
|
+
id = container["ID"] || container["Id"]
|
45
|
+
output = execute_local_json_command('inspect', [id])
|
46
|
+
# Store the container information for later
|
47
|
+
@container_info = output.first
|
48
|
+
@logger.trace { "Opened session" }
|
49
|
+
true
|
50
|
+
rescue StandardError => e
|
51
|
+
raise Bolt::Node::ConnectError.new(
|
52
|
+
"Failed to connect to #{target.safe_name}: #{e.message}",
|
53
|
+
'CONNECT_ERROR'
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Executes a command inside the target container. This is called from the shell class.
|
58
|
+
#
|
59
|
+
# @param command [string] The command to run
|
60
|
+
def execute(command)
|
61
|
+
args = []
|
62
|
+
args += %w[--interactive]
|
63
|
+
args += %w[--tty] if target.options['tty']
|
64
|
+
args += @env_vars if @env_vars
|
65
|
+
|
66
|
+
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
67
|
+
# escape any double quotes in command
|
68
|
+
command = command.gsub('"', '\"')
|
69
|
+
command = "#{target.options['shell-command']} \"#{command}\""
|
70
|
+
end
|
71
|
+
|
72
|
+
podman_command = %w[podman exec] + args + [container_id] + Shellwords.split(command)
|
73
|
+
@logger.trace { "Executing: #{podman_command.join(' ')}" }
|
74
|
+
|
75
|
+
Open3.popen3(*podman_command)
|
76
|
+
rescue StandardError
|
77
|
+
@logger.trace { "Command aborted" }
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
|
81
|
+
# Converts the JSON encoded STDOUT string from the podman cli into ruby objects
|
82
|
+
#
|
83
|
+
# @param stdout [String] The string to convert
|
84
|
+
# @return [Object] Ruby object representation of the JSON string
|
85
|
+
private def extract_json(stdout)
|
86
|
+
# Podman renders the output in pretty JSON, which results in a newline
|
87
|
+
# appearing in the output before the closing bracket.
|
88
|
+
# should we only get a single line with no newline at all, we also
|
89
|
+
# assume it is a single minified JSON object
|
90
|
+
stdout.strip!
|
91
|
+
newline = stdout.index("\n") || -1
|
92
|
+
bracket = stdout.index('}') || -1
|
93
|
+
JSON.parse(stdout) if bracket > newline
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/bolt/util.rb
CHANGED
@@ -343,6 +343,17 @@ module Bolt
|
|
343
343
|
Open3.capture3(env, 'docker', *cmd, { binmode: true })
|
344
344
|
end
|
345
345
|
|
346
|
+
# Executes a Podman CLI command. This is useful for running commands as
|
347
|
+
# part of this class without having to go through the `execute`
|
348
|
+
# function and manage pipes.
|
349
|
+
#
|
350
|
+
# @param cmd [String] The podman command and arguments to run
|
351
|
+
# e.g. 'cp <src> <dest>' for `podman cp <src> <dest>`
|
352
|
+
# @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
|
353
|
+
def exec_podman(cmd, env = {})
|
354
|
+
Open3.capture3(env, 'podman', *cmd, { binmode: true })
|
355
|
+
end
|
356
|
+
|
346
357
|
# Formats a map of environment variables to be passed to a command that
|
347
358
|
# accepts repeated `--env` flags
|
348
359
|
#
|
data/lib/bolt/version.rb
CHANGED
@@ -703,6 +703,9 @@ module BoltServer
|
|
703
703
|
connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
|
704
704
|
plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
|
705
705
|
plugins.add_plugin(connect_plugin)
|
706
|
+
%w[aws_inventory azure_inventory gcloud_inventory].each do |plugin_name|
|
707
|
+
plugins.add_module_plugin(plugin_name) if plugins.known_plugin?(plugin_name)
|
708
|
+
end
|
706
709
|
inventory = Bolt::Inventory.from_config(context[:config], plugins)
|
707
710
|
target_list = inventory.get_targets('all').map do |targ|
|
708
711
|
targ.to_h.merge({ 'transport' => targ.transport, 'plugin_hooks' => targ.plugin_hooks })
|
@@ -17,13 +17,14 @@ module BoltSpec
|
|
17
17
|
|
18
18
|
# Nothing on the executor is 'public'
|
19
19
|
class MockExecutor
|
20
|
-
attr_reader :noop, :error_message, :in_parallel, :transports
|
20
|
+
attr_reader :noop, :error_message, :in_parallel, :transports, :future
|
21
21
|
attr_accessor :run_as, :transport_features, :execute_any_plan
|
22
22
|
|
23
23
|
def initialize(modulepath)
|
24
24
|
@noop = false
|
25
25
|
@run_as = nil
|
26
26
|
@in_parallel = false
|
27
|
+
@future = {}
|
27
28
|
@error_message = nil
|
28
29
|
@allow_apply = false
|
29
30
|
@modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
|
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.6.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-
|
11
|
+
date: 2021-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -473,6 +473,7 @@ files:
|
|
473
473
|
- lib/bolt/config/transport/lxd.rb
|
474
474
|
- lib/bolt/config/transport/options.rb
|
475
475
|
- lib/bolt/config/transport/orch.rb
|
476
|
+
- lib/bolt/config/transport/podman.rb
|
476
477
|
- lib/bolt/config/transport/remote.rb
|
477
478
|
- lib/bolt/config/transport/ssh.rb
|
478
479
|
- lib/bolt/config/transport/winrm.rb
|
@@ -564,6 +565,8 @@ files:
|
|
564
565
|
- lib/bolt/transport/lxd/connection.rb
|
565
566
|
- lib/bolt/transport/orch.rb
|
566
567
|
- lib/bolt/transport/orch/connection.rb
|
568
|
+
- lib/bolt/transport/podman.rb
|
569
|
+
- lib/bolt/transport/podman/connection.rb
|
567
570
|
- lib/bolt/transport/remote.rb
|
568
571
|
- lib/bolt/transport/simple.rb
|
569
572
|
- lib/bolt/transport/ssh.rb
|