bolt 3.0.1 → 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/Puppetfile +13 -11
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +6 -3
- data/lib/bolt/cli.rb +123 -37
- data/lib/bolt/config.rb +15 -7
- data/lib/bolt/config/options.rb +62 -12
- data/lib/bolt/config/transport/lxd.rb +23 -0
- data/lib/bolt/config/transport/options.rb +8 -1
- 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 +37 -18
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/logger.rb +8 -0
- data/lib/bolt/module_installer.rb +2 -2
- data/lib/bolt/module_installer/puppetfile.rb +2 -2
- data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
- data/lib/bolt/node/output.rb +14 -4
- data/lib/bolt/outputter/human.rb +259 -90
- data/lib/bolt/outputter/json.rb +3 -1
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal.rb +25 -4
- data/lib/bolt/pal/yaml_plan.rb +1 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
- data/lib/bolt/pal/yaml_plan/step.rb +91 -31
- data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
- data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
- data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
- data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
- data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
- data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
- data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
- data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
- data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
- data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
- data/lib/bolt/plan_creator.rb +1 -1
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/project_manager.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +1 -1
- data/lib/bolt/result.rb +11 -15
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +61 -31
- data/lib/bolt/shell/bash/tmpdir.rb +2 -2
- data/lib/bolt/shell/powershell.rb +34 -12
- data/lib/bolt/shell/powershell/snippets.rb +30 -3
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +0 -9
- data/lib/bolt/transport/docker.rb +2 -126
- data/lib/bolt/transport/docker/connection.rb +81 -167
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/lxd/connection.rb +99 -0
- data/lib/bolt/transport/orch.rb +13 -5
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +42 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +64 -33
- data/lib/bolt_spec/bolt_context.rb +9 -4
- data/lib/bolt_spec/plans.rb +1 -109
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
- data/lib/bolt_spec/plans/mock_executor.rb +91 -7
- data/modules/puppet_connect/plans/test_input_data.pp +65 -7
- metadata +12 -2
@@ -11,12 +11,24 @@ Puppet::Functions.create_function(:prompt) do
|
|
11
11
|
# @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
|
12
12
|
# The returned value will be wrapped by the `Sensitive` data type. To access the raw
|
13
13
|
# value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
|
14
|
+
# @option options [String] default The default value to return if the user does not provide
|
15
|
+
# input or if stdin is not a tty.
|
14
16
|
# @return The response to the prompt.
|
15
17
|
# @example Prompt the user if plan execution should continue
|
16
18
|
# $response = prompt('Continue executing plan? [Y\N]')
|
17
19
|
# @example Prompt the user for sensitive information
|
18
20
|
# $password = prompt('Enter your password', 'sensitive' => true)
|
19
21
|
# out::message("Password is: ${password.unwrap}")
|
22
|
+
# @example Prompt the user and provide a default value
|
23
|
+
# $user = prompt('Enter your login username', 'default' => 'root')
|
24
|
+
# @example Prompt the user for sensitive information, returning a sensitive default if one is not provided
|
25
|
+
# $token = prompt('Enter token', 'default' => lookup('default_token'), 'sensitive' => true)
|
26
|
+
# out::message("Token is: ${token.unwrap}")
|
27
|
+
# @example Prompt the user and fail with a custom message if no input was provided
|
28
|
+
# $response = prompt('Enter your name', 'default' => '')
|
29
|
+
# if $response.empty {
|
30
|
+
# fail_plan('Must provide your name')
|
31
|
+
# }
|
20
32
|
dispatch :prompt do
|
21
33
|
param 'String', :prompt
|
22
34
|
optional_param 'Hash[String[1], Any]', :options
|
@@ -30,14 +42,20 @@ Puppet::Functions.create_function(:prompt) do
|
|
30
42
|
action: 'prompt')
|
31
43
|
end
|
32
44
|
|
33
|
-
options
|
34
|
-
|
45
|
+
options = options.transform_keys(&:to_sym)
|
35
46
|
executor = Puppet.lookup(:bolt_executor)
|
47
|
+
|
36
48
|
# Send analytics report
|
37
49
|
executor.report_function_call(self.class.name)
|
38
50
|
|
51
|
+
# Require default to be a string value
|
52
|
+
if options.key?(:default) && !options[:default].is_a?(String)
|
53
|
+
raise Bolt::ValidationError, "Option 'default' must be a string"
|
54
|
+
end
|
55
|
+
|
39
56
|
response = executor.prompt(prompt, options)
|
40
57
|
|
58
|
+
# If sensitive, wrap it
|
41
59
|
if options[:sensitive]
|
42
60
|
Puppet::Pops::Types::PSensitiveType::Sensitive.new(response)
|
43
61
|
else
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
|
5
|
+
# Display a menu prompt and wait for a response. Continues to prompt
|
6
|
+
# until an option from the menu is selected.
|
7
|
+
#
|
8
|
+
# > **Note:** Not available in apply block
|
9
|
+
Puppet::Functions.create_function(:'prompt::menu') do
|
10
|
+
# Select from a list of options.
|
11
|
+
# @param prompt The prompt to display.
|
12
|
+
# @param menu A list of options to choose from.
|
13
|
+
# @param options A hash of additional options.
|
14
|
+
# @option options [String] default The default option to return if the user does not provide
|
15
|
+
# input or if standard in (stdin) is not a tty. Must be an option present in the menu.
|
16
|
+
# @return The selected option.
|
17
|
+
# @example Prompt the user to select from a list of options
|
18
|
+
# $selection = prompt::menu('Select a fruit', ['apple', 'banana', 'carrot'])
|
19
|
+
# @example Prompt the user to select from a list of options with a default value
|
20
|
+
# $selection = prompt::menu('Select environment', ['development', 'production'], 'default' => 'development')
|
21
|
+
dispatch :prompt_menu_array do
|
22
|
+
param 'String', :prompt
|
23
|
+
param 'Array[Variant[Target, Data]]', :menu
|
24
|
+
optional_param 'Hash[String[1], Variant[Target, Data]]', :options
|
25
|
+
return_type 'Variant[Target, Data]'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Select from a list of options with custom inputs.
|
29
|
+
# @param prompt The prompt to display.
|
30
|
+
# @param menu A hash of options to choose from, where keys are the input used to select a value.
|
31
|
+
# @param options A hash of additional options.
|
32
|
+
# @option options [String] default The default option to return if the user does not provide
|
33
|
+
# input or if standard in (stdin) is not a tty. Must be an option present in the menu.
|
34
|
+
# @return The selected option.
|
35
|
+
# @example Prompt the user to select from a list of options with custom inputs
|
36
|
+
# $menu = { 'y' => 'yes', 'n' => 'no' }
|
37
|
+
# $selection = prompt::menu('Install Puppet?', $menu)
|
38
|
+
dispatch :prompt_menu do
|
39
|
+
param 'String', :prompt
|
40
|
+
param 'Hash[String[1], Variant[Target, Data]]', :menu
|
41
|
+
optional_param 'Hash[String[1], Variant[Target, Data]]', :options
|
42
|
+
return_type 'Variant[TargetSpec, Data]'
|
43
|
+
end
|
44
|
+
|
45
|
+
def prompt_menu_array(prompt, menu, options = {})
|
46
|
+
menu_hash = menu.map.with_index { |value, index| [(index + 1).to_s, value] }.to_h
|
47
|
+
prompt_menu(prompt, menu_hash, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def prompt_menu(prompt, menu, options = {})
|
51
|
+
unless Puppet[:tasks]
|
52
|
+
raise Puppet::ParseErrorWithIssue
|
53
|
+
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
|
54
|
+
action: 'prompt::menu')
|
55
|
+
end
|
56
|
+
|
57
|
+
options = options.transform_keys(&:to_sym)
|
58
|
+
executor = Puppet.lookup(:bolt_executor)
|
59
|
+
|
60
|
+
# Send analytics report
|
61
|
+
executor.report_function_call(self.class.name)
|
62
|
+
|
63
|
+
# Error if there are no options
|
64
|
+
if menu.empty?
|
65
|
+
raise Bolt::ValidationError, "Menu cannot be empty"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Error if the default value is not on the menu
|
69
|
+
if options.key?(:default) && !menu.value?(options[:default])
|
70
|
+
raise Bolt::ValidationError, "Default value '#{options[:default]}' is not one of the provided menu options"
|
71
|
+
end
|
72
|
+
|
73
|
+
# The first prompt should include the menu
|
74
|
+
to_prompt = format_menu(menu) + prompt
|
75
|
+
|
76
|
+
# Request input from the user until they provide a valid option
|
77
|
+
loop do
|
78
|
+
selection = executor.prompt(to_prompt, options)
|
79
|
+
|
80
|
+
return menu[selection] if menu.key?(selection)
|
81
|
+
return selection if options.key?(:default) && options[:default] == selection
|
82
|
+
|
83
|
+
# Only reprint the prompt, not the menu
|
84
|
+
to_prompt = "Invalid option, try again. #{prompt}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Builds the menu string. Aligns all the values by padding input keys.
|
89
|
+
#
|
90
|
+
private def format_menu(menu)
|
91
|
+
# Find the longest input and add 2 for wrapping parenthesis
|
92
|
+
key_length = menu.keys.max_by(&:length).length + 2
|
93
|
+
|
94
|
+
menu_string = +''
|
95
|
+
|
96
|
+
menu.each do |key, value|
|
97
|
+
key = "(#{key})".ljust(key_length)
|
98
|
+
menu_string << "#{key} #{value}\n"
|
99
|
+
end
|
100
|
+
|
101
|
+
menu_string
|
102
|
+
end
|
103
|
+
end
|
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/apply_result.rb
CHANGED
@@ -54,7 +54,7 @@ module Bolt
|
|
54
54
|
unless missing_keys.empty?
|
55
55
|
if result['_output']
|
56
56
|
# rubocop:disable Layout/LineLength
|
57
|
-
msg = "Report result contains an '_output' key. Catalog application
|
57
|
+
msg = "Report result contains an '_output' key. Catalog application might have printed extraneous output to stdout: #{result['_output']}"
|
58
58
|
# rubocop:enable Layout/LineLength
|
59
59
|
else
|
60
60
|
msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
|
@@ -13,7 +13,7 @@ module Bolt
|
|
13
13
|
run_context: %w[concurrency inventoryfile save-rerun cleanup],
|
14
14
|
global_config_setters: PROJECT_PATHS + %w[modulepath],
|
15
15
|
transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
|
16
|
-
display: %w[format color verbose trace],
|
16
|
+
display: %w[format color verbose trace stream],
|
17
17
|
global: %w[help version log-level clear-cache] }.freeze
|
18
18
|
|
19
19
|
ACTION_OPTS = OPTIONS.values.flatten.freeze
|
@@ -454,7 +454,7 @@ module Bolt
|
|
454
454
|
DESCRIPTION
|
455
455
|
Convert a YAML plan to a Puppet language plan and print the converted plan to stdout.
|
456
456
|
|
457
|
-
Converting a YAML plan
|
457
|
+
Converting a YAML plan might result in a plan that is syntactically
|
458
458
|
correct but has different behavior. Always verify a converted plan's
|
459
459
|
functionality. Note that the converted plan is not written to a file.
|
460
460
|
|
@@ -707,7 +707,7 @@ module Bolt
|
|
707
707
|
"Or read a target list from an input file '@<file>' or stdin '-'.",
|
708
708
|
'Example: --targets localhost,target_group,ssh://nix.com:23,winrm://windows.puppet.com',
|
709
709
|
'URI format is [protocol://]host[:port]',
|
710
|
-
"SSH is the default protocol;
|
710
|
+
"SSH is the default protocol; can be #{TRANSPORTS.keys.join(', ')}",
|
711
711
|
'For Windows targets, specify the winrm:// protocol if it has not be configured',
|
712
712
|
'For SSH, port defaults to `22`',
|
713
713
|
'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |targets|
|
@@ -888,6 +888,9 @@ module Bolt
|
|
888
888
|
define('-v', '--[no-]verbose', 'Display verbose logging') do |value|
|
889
889
|
@options[:verbose] = value
|
890
890
|
end
|
891
|
+
define('--stream', 'Stream output from scripts and commands to the console') do |_|
|
892
|
+
@options[:stream] = true
|
893
|
+
end
|
891
894
|
define('--trace', 'Display error stack traces') do |_|
|
892
895
|
@options[:trace] = true
|
893
896
|
end
|
data/lib/bolt/cli.rb
CHANGED
@@ -96,6 +96,48 @@ module Bolt
|
|
96
96
|
finalize_setup
|
97
97
|
end
|
98
98
|
|
99
|
+
# Prints a welcome message when users first install Bolt and run `bolt`, `bolt help` or `bolt --help`
|
100
|
+
def welcome_message
|
101
|
+
bolt = <<~BOLT
|
102
|
+
`.::-`
|
103
|
+
`.-:///////-.`
|
104
|
+
`-:////:. `-:///:- /ooo. .ooo/
|
105
|
+
`.-:///::///:-` `-//: ymmm- :mmmy .---.
|
106
|
+
:///:-. `.:////. -//: ymmm- :mmmy +mmm+
|
107
|
+
://. ///. -//: ymmm--/++/- `-/++/:` :mmmy-:smmms::-
|
108
|
+
://. ://. .://: ymmmdmmmmmmdo` .smmmmmmmmh: :mmmysmmmmmmmms
|
109
|
+
://. ://:///:-. ymmmh/--/hmmmy -mmmd/-.:hmmm+:mmmy.-smmms--.
|
110
|
+
://:.` .-////:-` ymmm- ymmm:hmmm- `dmmm/mmmy +mmm+
|
111
|
+
`-:///:-..:///:-.` ymmm- ommm/dmmm` hmmm+mmmy +mmm+
|
112
|
+
`.-:////:-` ymmm+ /mmmm.ommms` /mmmh:mmmy +mmmo
|
113
|
+
`-.` ymmmmmhhmmmmd: ommmmhydmmmy`:mmmy -mmmmdhd
|
114
|
+
oyyy+shddhs/` .+shddhy+- -yyyo .ohddhs
|
115
|
+
|
116
|
+
|
117
|
+
BOLT
|
118
|
+
example_cmd = if Bolt::Util.windows?
|
119
|
+
"Invoke-BoltCommand -Command 'hostname' -Targets localhost"
|
120
|
+
else
|
121
|
+
"bolt command run 'hostname' --target localhost"
|
122
|
+
end
|
123
|
+
prev_cmd = String.new("bolt")
|
124
|
+
prev_cmd << " #{@argv[0]}" unless @argv.empty?
|
125
|
+
|
126
|
+
message = <<~MSG
|
127
|
+
🎉 Welcome to Bolt #{VERSION}
|
128
|
+
😌 We're here to help bring order to the chaos
|
129
|
+
📖 Find our documentation at https://bolt.guide
|
130
|
+
🙋 Ask a question in #bolt on https://slack.puppet.com/
|
131
|
+
🔩 Contribute at https://github.com/puppetlabs/bolt/
|
132
|
+
💡 Not sure where to start? Try "#{example_cmd}"
|
133
|
+
|
134
|
+
We only print this message once. Run "#{prev_cmd}" again for help text.
|
135
|
+
MSG
|
136
|
+
|
137
|
+
$stdout.print "\033[36m#{bolt}\033[0m"
|
138
|
+
$stdout.print message
|
139
|
+
end
|
140
|
+
|
99
141
|
# Parses the command and validates options. All errors that are raised here
|
100
142
|
# are not handled by the outputter, as it relies on config being loaded.
|
101
143
|
def parse_command
|
@@ -107,6 +149,16 @@ module Bolt
|
|
107
149
|
# help text
|
108
150
|
options[:subcommand] = nil unless COMMANDS.include?(options[:subcommand])
|
109
151
|
|
152
|
+
if Bolt::Util.first_run?
|
153
|
+
FileUtils.mkdir_p(Bolt::Util.first_runs_free.dirname)
|
154
|
+
FileUtils.touch(Bolt::Util.first_runs_free)
|
155
|
+
|
156
|
+
if options[:subcommand].nil? && $stdout.isatty
|
157
|
+
welcome_message
|
158
|
+
raise Bolt::CLIExit
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
110
162
|
# Update the parser for the subcommand (or lack thereof)
|
111
163
|
parser.update
|
112
164
|
puts parser.help
|
@@ -175,6 +227,7 @@ module Bolt
|
|
175
227
|
# Completes the setup process by configuring Bolt and log messages
|
176
228
|
def finalize_setup
|
177
229
|
Bolt::Logger.configure(config.log, config.color, config.disable_warnings)
|
230
|
+
Bolt::Logger.stream = config.stream
|
178
231
|
Bolt::Logger.analytics = analytics
|
179
232
|
Bolt::Logger.flush_queue
|
180
233
|
|
@@ -212,7 +265,7 @@ module Bolt
|
|
212
265
|
target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
|
213
266
|
target_string = "'--targets', '--rerun', or '--query'"
|
214
267
|
if target_opts.length > 1
|
215
|
-
raise Bolt::CLIError, "Only one targeting option #{target_string}
|
268
|
+
raise Bolt::CLIError, "Only one targeting option #{target_string} can be specified"
|
216
269
|
elsif target_opts.empty? && options[:subcommand] != 'plan'
|
217
270
|
raise Bolt::CLIError, "Command requires a targeting option: #{target_string}"
|
218
271
|
end
|
@@ -249,11 +302,14 @@ module Bolt
|
|
249
302
|
end
|
250
303
|
end
|
251
304
|
|
252
|
-
if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
|
305
|
+
if %w[task plan script].include?(options[:subcommand]) && options[:action] == 'run'
|
253
306
|
if options[:object].nil?
|
254
307
|
raise Bolt::CLIError, "Must specify a #{options[:subcommand]} to run"
|
255
308
|
end
|
256
|
-
|
309
|
+
end
|
310
|
+
|
311
|
+
# This may mean that we parsed a parameter as the object
|
312
|
+
if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
|
257
313
|
unless options[:object] =~ /\A([a-z][a-z0-9_]*)?(::[a-z][a-z0-9_]*)*\Z/
|
258
314
|
raise Bolt::CLIError,
|
259
315
|
"Invalid #{options[:subcommand]} '#{options[:object]}'"
|
@@ -301,13 +357,13 @@ module Bolt
|
|
301
357
|
if options[:noop] &&
|
302
358
|
!(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
|
303
359
|
raise Bolt::CLIError,
|
304
|
-
"Option '--noop'
|
360
|
+
"Option '--noop' can only be specified when running a task or applying manifest code"
|
305
361
|
end
|
306
362
|
|
307
363
|
if options[:env_vars]
|
308
364
|
unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
|
309
365
|
raise Bolt::CLIError,
|
310
|
-
"Option '--env-var'
|
366
|
+
"Option '--env-var' can only be specified when running a command or script"
|
311
367
|
end
|
312
368
|
end
|
313
369
|
end
|
@@ -354,7 +410,7 @@ module Bolt
|
|
354
410
|
if inventory_source && conflicting_options.any?
|
355
411
|
Bolt::Logger.warn(
|
356
412
|
"cli_overrides",
|
357
|
-
"CLI arguments #{conflicting_options.to_a}
|
413
|
+
"CLI arguments #{conflicting_options.to_a} might be overridden by Inventory: #{inventory_source}"
|
358
414
|
)
|
359
415
|
end
|
360
416
|
end
|
@@ -432,7 +488,7 @@ module Bolt
|
|
432
488
|
return 0
|
433
489
|
end
|
434
490
|
|
435
|
-
message = 'There
|
491
|
+
message = 'There might be processes left executing on some nodes.'
|
436
492
|
|
437
493
|
if %w[task plan].include?(options[:subcommand]) && options[:task_options] && !options[:params_parsed] && pal
|
438
494
|
options[:task_options] = pal.parse_params(options[:subcommand], options[:object], options[:task_options])
|
@@ -485,7 +541,11 @@ module Bolt
|
|
485
541
|
end
|
486
542
|
code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
|
487
543
|
else
|
488
|
-
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)
|
489
549
|
targets = options[:targets]
|
490
550
|
|
491
551
|
results = nil
|
@@ -501,9 +561,8 @@ module Bolt
|
|
501
561
|
when 'command'
|
502
562
|
executor.run_command(targets, options[:object], executor_opts)
|
503
563
|
when 'script'
|
504
|
-
|
505
|
-
|
506
|
-
executor.run_script(targets, script, options[:leftovers], executor_opts)
|
564
|
+
script_path = find_file(options[:object])
|
565
|
+
executor.run_script(targets, script_path, options[:leftovers], executor_opts)
|
507
566
|
when 'task'
|
508
567
|
pal.run_task(options[:object],
|
509
568
|
targets,
|
@@ -576,7 +635,18 @@ module Bolt
|
|
576
635
|
|
577
636
|
def list_targets
|
578
637
|
inventoryfile = config.inventoryfile || config.default_inventoryfile
|
638
|
+
outputter.print_targets(group_targets_by_source, inventoryfile)
|
639
|
+
end
|
640
|
+
|
641
|
+
def show_targets
|
642
|
+
inventoryfile = config.inventoryfile || config.default_inventoryfile
|
643
|
+
outputter.print_target_info(group_targets_by_source, inventoryfile)
|
644
|
+
end
|
579
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
|
580
650
|
# Retrieve the known group and target names. This needs to be done before
|
581
651
|
# updating targets, as that will add adhoc targets to the inventory.
|
582
652
|
known_names = inventory.target_names
|
@@ -587,17 +657,7 @@ module Bolt
|
|
587
657
|
known_names.include?(target.name)
|
588
658
|
end
|
589
659
|
|
590
|
-
|
591
|
-
inventory: inventory_targets,
|
592
|
-
adhoc: adhoc_targets
|
593
|
-
}
|
594
|
-
|
595
|
-
outputter.print_targets(target_list, inventoryfile)
|
596
|
-
end
|
597
|
-
|
598
|
-
def show_targets
|
599
|
-
update_targets(options)
|
600
|
-
outputter.print_target_info(options[:targets])
|
660
|
+
{ inventory: inventory_targets, adhoc: adhoc_targets }
|
601
661
|
end
|
602
662
|
|
603
663
|
def list_groups
|
@@ -610,7 +670,7 @@ module Bolt
|
|
610
670
|
if plan_arguments['nodes'] || plan_arguments['targets']
|
611
671
|
key = plan_arguments.include?('nodes') ? 'nodes' : 'targets'
|
612
672
|
raise Bolt::CLIError,
|
613
|
-
"A plan's '#{key}' parameter
|
673
|
+
"A plan's '#{key}' parameter can be specified using the --#{key} option, but in that " \
|
614
674
|
"case it must not be specified as a separate #{key}=<value> parameter nor included " \
|
615
675
|
"in the JSON data passed in the --params option"
|
616
676
|
end
|
@@ -633,7 +693,11 @@ module Bolt
|
|
633
693
|
plan_context = { plan_name: plan_name,
|
634
694
|
params: plan_arguments }
|
635
695
|
|
636
|
-
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)
|
637
701
|
if %w[human rainbow].include?(options.fetch(:format, 'human'))
|
638
702
|
executor.subscribe(outputter)
|
639
703
|
else
|
@@ -669,7 +733,11 @@ module Bolt
|
|
669
733
|
Bolt::Logger.warn("empty_manifest", message)
|
670
734
|
end
|
671
735
|
|
672
|
-
executor = Bolt::Executor.new(config.concurrency,
|
736
|
+
executor = Bolt::Executor.new(config.concurrency,
|
737
|
+
analytics,
|
738
|
+
noop,
|
739
|
+
config.modified_concurrency,
|
740
|
+
config.future)
|
673
741
|
executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
|
674
742
|
executor.subscribe(log_outputter)
|
675
743
|
# apply logging looks like plan logging, so tell the outputter we're in a
|
@@ -754,15 +822,10 @@ module Bolt
|
|
754
822
|
#
|
755
823
|
def assert_project_file(project)
|
756
824
|
unless project.project_file?
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
else
|
762
|
-
command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
|
763
|
-
"Could not find project configuration file #{project.project_file}, unable "\
|
764
|
-
"to install modules. To create a Bolt project, run '#{command}'."
|
765
|
-
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}'."
|
766
829
|
|
767
830
|
raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
|
768
831
|
end
|
@@ -840,6 +903,28 @@ module Bolt
|
|
840
903
|
Bolt::Util.validate_file(type, path, allow_dir)
|
841
904
|
end
|
842
905
|
|
906
|
+
# Returns the path to a file. If the path is an absolute or relative to
|
907
|
+
# a file, and the file exists, returns the path as-is. Otherwise, checks if
|
908
|
+
# the path is a Puppet file path and looks for the file in a module's files
|
909
|
+
# directory.
|
910
|
+
#
|
911
|
+
def find_file(path)
|
912
|
+
unless File.exist?(path) || Pathname.new(path).absolute?
|
913
|
+
modulepath = Bolt::Config::Modulepath.new(config.modulepath)
|
914
|
+
modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
|
915
|
+
mod, file = path.split(File::SEPARATOR, 2)
|
916
|
+
|
917
|
+
if modules[mod]
|
918
|
+
@logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
|
919
|
+
path = File.join(modules[mod].path, 'files', file)
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
Bolt::Util.validate_file('script', path)
|
924
|
+
|
925
|
+
path
|
926
|
+
end
|
927
|
+
|
843
928
|
def rerun
|
844
929
|
@rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
|
845
930
|
end
|
@@ -858,7 +943,7 @@ module Bolt
|
|
858
943
|
|
859
944
|
def analytics
|
860
945
|
@analytics ||= begin
|
861
|
-
client = Bolt::Analytics.build_client
|
946
|
+
client = Bolt::Analytics.build_client(config.analytics)
|
862
947
|
client.bundled_content = bundled_content
|
863
948
|
client
|
864
949
|
end
|
@@ -868,7 +953,7 @@ module Bolt
|
|
868
953
|
# If the bundled content directory is empty, Bolt is likely installed as a gem.
|
869
954
|
if ENV['BOLT_GEM'].nil? && incomplete_install?
|
870
955
|
msg = <<~MSG.chomp
|
871
|
-
Bolt
|
956
|
+
Bolt might be installed as a gem. To use Bolt reliably and with all of its
|
872
957
|
dependencies, uninstall the 'bolt' gem and install Bolt as a package:
|
873
958
|
https://puppet.com/docs/bolt/latest/bolt_installing.html
|
874
959
|
|
@@ -899,7 +984,8 @@ module Bolt
|
|
899
984
|
# Gem installs include the aggregate, canary, and puppetdb_fact modules, while
|
900
985
|
# package installs include modules listed in the Bolt repo Puppetfile
|
901
986
|
def incomplete_install?
|
902
|
-
|
987
|
+
builtin_module_list = %w[aggregate canary puppetdb_fact secure_env_vars puppet_connect]
|
988
|
+
(Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - builtin_module_list).empty?
|
903
989
|
end
|
904
990
|
|
905
991
|
# Mimicks the output from Outputter::Human#fatal_error. This should be used to print
|