bolt 3.15.0 → 3.18.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 +7 -7
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -4
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -4
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -4
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +1 -4
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +1 -4
- data/lib/bolt/analytics.rb +4 -21
- data/lib/bolt/application.rb +627 -0
- data/lib/bolt/applicator.rb +7 -6
- data/lib/bolt/apply_inventory.rb +3 -3
- data/lib/bolt/apply_result.rb +2 -2
- data/lib/bolt/bolt_option_parser.rb +27 -3
- data/lib/bolt/catalog.rb +10 -11
- data/lib/bolt/cli.rb +607 -809
- data/lib/bolt/config/modulepath.rb +1 -1
- data/lib/bolt/config/options.rb +32 -13
- data/lib/bolt/config/transport/base.rb +4 -4
- data/lib/bolt/config/transport/docker.rb +2 -2
- data/lib/bolt/config/transport/local.rb +2 -2
- data/lib/bolt/config/transport/lxd.rb +2 -2
- data/lib/bolt/config/transport/orch.rb +2 -2
- data/lib/bolt/config/transport/podman.rb +2 -2
- data/lib/bolt/config/transport/remote.rb +2 -2
- data/lib/bolt/config/transport/ssh.rb +2 -2
- data/lib/bolt/config/transport/winrm.rb +2 -2
- data/lib/bolt/config.rb +5 -5
- data/lib/bolt/container_result.rb +2 -2
- data/lib/bolt/error.rb +1 -1
- data/lib/bolt/executor.rb +17 -14
- data/lib/bolt/fiber_executor.rb +9 -5
- data/lib/bolt/inventory/group.rb +4 -4
- data/lib/bolt/inventory/inventory.rb +64 -41
- data/lib/bolt/inventory/options.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/inventory.rb +10 -17
- data/lib/bolt/module_installer/installer.rb +2 -2
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +1 -1
- data/lib/bolt/module_installer/puppetfile/git_module.rb +1 -1
- data/lib/bolt/module_installer/puppetfile/module.rb +1 -1
- data/lib/bolt/module_installer/puppetfile.rb +27 -13
- data/lib/bolt/module_installer/resolver.rb +3 -3
- data/lib/bolt/module_installer/specs/forge_spec.rb +1 -1
- data/lib/bolt/module_installer/specs/git_spec.rb +1 -1
- data/lib/bolt/module_installer/specs.rb +3 -3
- data/lib/bolt/module_installer.rb +6 -6
- data/lib/bolt/node/errors.rb +1 -1
- data/lib/bolt/node/output.rb +1 -1
- data/lib/bolt/outputter/human.rb +85 -34
- data/lib/bolt/outputter/json.rb +63 -38
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +1 -1
- data/lib/bolt/outputter.rb +4 -4
- data/lib/bolt/pal/logging.rb +1 -1
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/pal/yaml_plan/loader.rb +2 -2
- data/lib/bolt/pal/yaml_plan/step.rb +11 -11
- data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
- data/lib/bolt/pal/yaml_plan.rb +2 -2
- data/lib/bolt/pal.rb +11 -11
- data/lib/bolt/plan_creator.rb +88 -29
- data/lib/bolt/plan_future.rb +11 -6
- data/lib/bolt/plan_result.rb +3 -3
- data/lib/bolt/plugin/cache.rb +2 -2
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin.rb +18 -24
- data/lib/bolt/project.rb +4 -11
- data/lib/bolt/project_manager/config_migrator.rb +1 -1
- data/lib/bolt/project_manager/inventory_migrator.rb +1 -1
- data/lib/bolt/project_manager/migrator.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +5 -5
- data/lib/bolt/project_manager.rb +23 -4
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetdb.rb +3 -3
- data/lib/bolt/result.rb +1 -1
- data/lib/bolt/result_set.rb +2 -1
- data/lib/bolt/shell/bash.rb +12 -2
- data/lib/bolt/shell/powershell.rb +9 -2
- data/lib/bolt/shell.rb +2 -2
- data/lib/bolt/target.rb +2 -2
- data/lib/bolt/transport/base.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +1 -1
- data/lib/bolt/transport/docker.rb +2 -2
- data/lib/bolt/transport/local/connection.rb +19 -3
- data/lib/bolt/transport/local.rb +3 -3
- data/lib/bolt/transport/lxd/connection.rb +1 -1
- data/lib/bolt/transport/lxd.rb +4 -4
- data/lib/bolt/transport/orch/connection.rb +13 -1
- data/lib/bolt/transport/orch.rb +2 -2
- data/lib/bolt/transport/podman/connection.rb +1 -1
- data/lib/bolt/transport/podman.rb +2 -2
- data/lib/bolt/transport/remote.rb +2 -2
- data/lib/bolt/transport/simple.rb +3 -3
- data/lib/bolt/transport/ssh/connection.rb +3 -3
- data/lib/bolt/transport/ssh.rb +5 -5
- data/lib/bolt/transport/winrm/connection.rb +2 -2
- data/lib/bolt/transport/winrm.rb +3 -3
- data/lib/bolt/util.rb +8 -31
- data/lib/bolt/validator.rb +1 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -0
- data/lib/bolt_server/schemas/action-apply.json +32 -0
- data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
- data/lib/bolt_server/transport_app.rb +129 -26
- data/lib/bolt_spec/bolt_context.rb +7 -5
- data/lib/bolt_spec/plans/mock_executor.rb +3 -3
- data/lib/bolt_spec/run.rb +1 -1
- data/libexec/bolt_catalog +1 -1
- metadata +7 -5
- data/lib/bolt/secret.rb +0 -37
data/lib/bolt/cli.rb
CHANGED
@@ -9,29 +9,30 @@ require 'json'
|
|
9
9
|
require 'io/console'
|
10
10
|
require 'logging'
|
11
11
|
require 'optparse'
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
require 'bolt/version'
|
12
|
+
require_relative '../bolt/analytics'
|
13
|
+
require_relative '../bolt/application'
|
14
|
+
require_relative '../bolt/bolt_option_parser'
|
15
|
+
require_relative '../bolt/config'
|
16
|
+
require_relative '../bolt/error'
|
17
|
+
require_relative '../bolt/executor'
|
18
|
+
require_relative '../bolt/inventory'
|
19
|
+
require_relative '../bolt/logger'
|
20
|
+
require_relative '../bolt/module_installer'
|
21
|
+
require_relative '../bolt/outputter'
|
22
|
+
require_relative '../bolt/pal'
|
23
|
+
require_relative '../bolt/plugin'
|
24
|
+
require_relative '../bolt/project_manager'
|
25
|
+
require_relative '../bolt/puppetdb'
|
26
|
+
require_relative '../bolt/rerun'
|
27
|
+
require_relative '../bolt/target'
|
28
|
+
require_relative '../bolt/version'
|
30
29
|
|
31
30
|
module Bolt
|
32
31
|
class CLIExit < StandardError; end
|
33
32
|
|
34
33
|
class CLI
|
34
|
+
attr_reader :outputter, :rerun
|
35
|
+
|
35
36
|
COMMANDS = {
|
36
37
|
'apply' => %w[],
|
37
38
|
'command' => %w[run],
|
@@ -51,22 +52,23 @@ module Bolt
|
|
51
52
|
|
52
53
|
TARGETING_OPTIONS = %i[query rerun targets].freeze
|
53
54
|
|
54
|
-
|
55
|
+
SUCCESS = 0
|
56
|
+
FAILURE = 1
|
55
57
|
|
56
58
|
def initialize(argv)
|
57
59
|
Bolt::Logger.initialize_logging
|
58
60
|
@logger = Bolt::Logger.logger(self)
|
59
|
-
@argv
|
60
|
-
@options = {}
|
61
|
-
end
|
62
|
-
|
63
|
-
# Only call after @config has been initialized.
|
64
|
-
def inventory
|
65
|
-
@inventory ||= Bolt::Inventory.from_config(config, plugins)
|
61
|
+
@argv = argv
|
66
62
|
end
|
67
|
-
private :inventory
|
68
63
|
|
69
|
-
|
64
|
+
# TODO: Move this to the parser.
|
65
|
+
#
|
66
|
+
# Query whether the help text needs to be displayed.
|
67
|
+
#
|
68
|
+
# @param remaining [Array] Remaining arguments after parsing the command.
|
69
|
+
# @param options [Hash] The CLI options.
|
70
|
+
#
|
71
|
+
private def help?(options, remaining)
|
70
72
|
# Set the subcommand
|
71
73
|
options[:subcommand] = remaining.shift
|
72
74
|
|
@@ -84,24 +86,90 @@ module Bolt
|
|
84
86
|
|
85
87
|
options[:help]
|
86
88
|
end
|
87
|
-
private :help?
|
88
89
|
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
90
|
+
# TODO: Move most of this to the parser.
|
91
|
+
#
|
92
|
+
# Parse the command and validate options. All errors that are raised here
|
93
|
+
# are not handled by the outputter, as it relies on config being loaded.
|
92
94
|
#
|
93
|
-
# This separation is needed since the Bolt::Outputter class that normally handles
|
94
|
-
# printing errors relies on config being loaded. All setup that happens before
|
95
|
-
# config is loaded will have errors printed directly to stdout, while all errors
|
96
|
-
# raised after config is loaded are handled by the outputter.
|
97
95
|
def parse
|
98
|
-
|
99
|
-
|
100
|
-
|
96
|
+
with_error_handling do
|
97
|
+
options = {}
|
98
|
+
parser = BoltOptionParser.new(options)
|
99
|
+
|
100
|
+
# This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
|
101
|
+
remaining = parser.permute(@argv) unless @argv.empty?
|
102
|
+
|
103
|
+
if @argv.empty? || help?(options, remaining)
|
104
|
+
# If the subcommand is not enabled, display the default
|
105
|
+
# help text
|
106
|
+
options[:subcommand] = nil unless COMMANDS.include?(options[:subcommand])
|
107
|
+
|
108
|
+
if Bolt::Util.first_run?
|
109
|
+
FileUtils.touch(Bolt::Util.first_runs_free)
|
110
|
+
|
111
|
+
if options[:subcommand].nil? && $stdout.isatty
|
112
|
+
welcome_message
|
113
|
+
raise Bolt::CLIExit
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Update the parser for the subcommand (or lack thereof)
|
118
|
+
parser.update
|
119
|
+
puts parser.help
|
120
|
+
raise Bolt::CLIExit
|
121
|
+
end
|
122
|
+
|
123
|
+
if options[:version]
|
124
|
+
puts Bolt::VERSION
|
125
|
+
raise Bolt::CLIExit
|
126
|
+
end
|
127
|
+
|
128
|
+
options[:object] = remaining.shift
|
129
|
+
|
130
|
+
# Handle reading a command from a file
|
131
|
+
if options[:subcommand] == 'command' && options[:object]
|
132
|
+
options[:object] = Bolt::Util.get_arg_input(options[:object])
|
133
|
+
end
|
134
|
+
|
135
|
+
# Only parse params for task or plan
|
136
|
+
if %w[task plan].include?(options[:subcommand])
|
137
|
+
params, remaining = remaining.partition { |s| s =~ /.+=/ }
|
138
|
+
if options[:params]
|
139
|
+
unless params.empty?
|
140
|
+
raise Bolt::CLIError,
|
141
|
+
"Parameters must be specified through either the --params " \
|
142
|
+
"option or param=value pairs, not both"
|
143
|
+
end
|
144
|
+
options[:params_parsed] = true
|
145
|
+
elsif params.any?
|
146
|
+
options[:params_parsed] = false
|
147
|
+
options[:params] = Hash[params.map { |a| a.split('=', 2) }]
|
148
|
+
else
|
149
|
+
options[:params_parsed] = true
|
150
|
+
options[:params] = {}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
options[:leftovers] = remaining
|
154
|
+
|
155
|
+
# Default to verbose for everything except plans
|
156
|
+
unless options.key?(:verbose)
|
157
|
+
options[:verbose] = options[:subcommand] != 'plan'
|
158
|
+
end
|
159
|
+
|
160
|
+
validate(options)
|
161
|
+
validate_ps_version
|
162
|
+
|
163
|
+
options
|
164
|
+
end
|
101
165
|
end
|
102
166
|
|
103
|
-
#
|
104
|
-
|
167
|
+
# TODO: Move this to the parser.
|
168
|
+
#
|
169
|
+
# Print a welcome message when users first install Bolt and run `bolt`,
|
170
|
+
# `bolt help` or `bolt --help`.
|
171
|
+
#
|
172
|
+
private def welcome_message
|
105
173
|
bolt = <<~BOLT
|
106
174
|
`.::-`
|
107
175
|
`.-:///////-.`
|
@@ -142,151 +210,15 @@ module Bolt
|
|
142
210
|
$stdout.print message
|
143
211
|
end
|
144
212
|
|
145
|
-
#
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
options[:subcommand] = nil unless COMMANDS.include?(options[:subcommand])
|
155
|
-
|
156
|
-
if Bolt::Util.first_run?
|
157
|
-
FileUtils.touch(Bolt::Util.first_runs_free)
|
158
|
-
|
159
|
-
if options[:subcommand].nil? && $stdout.isatty
|
160
|
-
welcome_message
|
161
|
-
raise Bolt::CLIExit
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
# Update the parser for the subcommand (or lack thereof)
|
166
|
-
parser.update
|
167
|
-
puts parser.help
|
168
|
-
raise Bolt::CLIExit
|
169
|
-
end
|
170
|
-
|
171
|
-
options[:object] = remaining.shift
|
172
|
-
|
173
|
-
# Handle reading a command from a file
|
174
|
-
if options[:subcommand] == 'command' && options[:object]
|
175
|
-
options[:object] = Bolt::Util.get_arg_input(options[:object])
|
176
|
-
end
|
177
|
-
|
178
|
-
# Only parse task_options for task or plan
|
179
|
-
if %w[task plan].include?(options[:subcommand])
|
180
|
-
task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
|
181
|
-
if options[:task_options]
|
182
|
-
unless task_options.empty?
|
183
|
-
raise Bolt::CLIError,
|
184
|
-
"Parameters must be specified through either the --params " \
|
185
|
-
"option or param=value pairs, not both"
|
186
|
-
end
|
187
|
-
options[:params_parsed] = true
|
188
|
-
elsif task_options.any?
|
189
|
-
options[:params_parsed] = false
|
190
|
-
options[:task_options] = Hash[task_options.map { |a| a.split('=', 2) }]
|
191
|
-
else
|
192
|
-
options[:params_parsed] = true
|
193
|
-
options[:task_options] = {}
|
194
|
-
end
|
195
|
-
end
|
196
|
-
options[:leftovers] = remaining
|
197
|
-
|
198
|
-
# Default to verbose for everything except plans
|
199
|
-
unless options.key?(:verbose)
|
200
|
-
options[:verbose] = options[:subcommand] != 'plan'
|
201
|
-
end
|
202
|
-
|
203
|
-
validate(options)
|
204
|
-
rescue Bolt::Error => e
|
205
|
-
fatal_error(e)
|
206
|
-
raise e
|
207
|
-
end
|
208
|
-
|
209
|
-
# Loads the project and configuration. All errors that are raised here are not
|
210
|
-
# handled by the outputter, as it relies on config being loaded.
|
211
|
-
def load_config
|
212
|
-
project = if ENV['BOLT_PROJECT']
|
213
|
-
Bolt::Project.create_project(ENV['BOLT_PROJECT'], 'environment')
|
214
|
-
elsif options[:project]
|
215
|
-
dir = Pathname.new(options[:project])
|
216
|
-
if (dir + Bolt::Project::BOLTDIR_NAME).directory?
|
217
|
-
Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
|
218
|
-
else
|
219
|
-
Bolt::Project.create_project(dir)
|
220
|
-
end
|
221
|
-
else
|
222
|
-
Bolt::Project.find_boltdir(Dir.pwd)
|
223
|
-
end
|
224
|
-
@config = Bolt::Config.from_project(project, options)
|
225
|
-
rescue Bolt::Error => e
|
226
|
-
fatal_error(e)
|
227
|
-
raise e
|
228
|
-
end
|
229
|
-
|
230
|
-
# Completes the setup process by configuring Bolt and log messages
|
231
|
-
def finalize_setup
|
232
|
-
Bolt::Logger.configure(config.log, config.color, config.disable_warnings)
|
233
|
-
Bolt::Logger.stream = config.stream
|
234
|
-
Bolt::Logger.analytics = analytics
|
235
|
-
Bolt::Logger.flush_queue
|
236
|
-
|
237
|
-
# Logger must be configured before checking path case and project file, otherwise logs will not display
|
238
|
-
config.check_path_case('modulepath', config.modulepath)
|
239
|
-
config.project.check_deprecated_file
|
240
|
-
|
241
|
-
if options[:clear_cache]
|
242
|
-
FileUtils.rm(config.project.plugin_cache_file) if File.exist?(config.project.plugin_cache_file)
|
243
|
-
FileUtils.rm(config.project.task_cache_file) if File.exist?(config.project.task_cache_file)
|
244
|
-
FileUtils.rm(config.project.plan_cache_file) if File.exist?(config.project.plan_cache_file)
|
245
|
-
end
|
246
|
-
|
247
|
-
warn_inventory_overrides_cli(options)
|
248
|
-
validate_ps_version
|
249
|
-
|
250
|
-
options
|
251
|
-
rescue Bolt::Error => e
|
252
|
-
outputter.fatal_error(e)
|
253
|
-
raise e
|
254
|
-
end
|
255
|
-
|
256
|
-
private def validate_ps_version
|
257
|
-
if Bolt::Util.powershell?
|
258
|
-
command = "powershell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy "\
|
259
|
-
"Bypass -Command $PSVersionTable.PSVersion.Major"
|
260
|
-
stdout, _stderr, _status = Open3.capture3(command)
|
261
|
-
|
262
|
-
return unless !stdout.empty? && stdout.to_i < 3
|
263
|
-
|
264
|
-
msg = "Detected PowerShell 2 on controller. PowerShell 2 is unsupported."
|
265
|
-
Bolt::Logger.deprecation_warning("powershell_2_controller", msg)
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def update_targets(options)
|
270
|
-
target_opts = options.keys.select { |opt| TARGETING_OPTIONS.include?(opt) }
|
271
|
-
target_string = "'--targets', '--rerun', or '--query'"
|
272
|
-
if target_opts.length > 1
|
273
|
-
raise Bolt::CLIError, "Only one targeting option #{target_string} can be specified"
|
274
|
-
elsif target_opts.empty? && options[:subcommand] != 'plan'
|
275
|
-
raise Bolt::CLIError, "Command requires a targeting option: #{target_string}"
|
276
|
-
end
|
277
|
-
|
278
|
-
targets = if options[:query]
|
279
|
-
query_puppetdb_nodes(options[:query])
|
280
|
-
elsif options[:rerun]
|
281
|
-
rerun.get_targets(options[:rerun])
|
282
|
-
else
|
283
|
-
options[:targets] || []
|
284
|
-
end
|
285
|
-
options[:target_args] = targets
|
286
|
-
options[:targets] = inventory.get_targets(targets)
|
287
|
-
end
|
288
|
-
|
289
|
-
def validate(options)
|
213
|
+
# TODO: Move this to the parser.
|
214
|
+
#
|
215
|
+
# Validate the command. Ensure that the subcommand and action are
|
216
|
+
# recognized, all required arguments are specified, and only supported
|
217
|
+
# command-line options are used.
|
218
|
+
#
|
219
|
+
# @param options [Hash] The CLI options.
|
220
|
+
#
|
221
|
+
private def validate(options)
|
290
222
|
unless COMMANDS.include?(options[:subcommand])
|
291
223
|
command = Bolt::Util.powershell? ? 'Get-Command -Module PuppetBolt' : 'bolt help'
|
292
224
|
raise Bolt::CLIError,
|
@@ -351,12 +283,34 @@ module Bolt
|
|
351
283
|
raise Bolt::CLIError, "Must specify a module name."
|
352
284
|
end
|
353
285
|
|
286
|
+
if options[:action] == 'convert' && !options[:object]
|
287
|
+
raise Bolt::CLIError, "Must specify a plan."
|
288
|
+
end
|
289
|
+
|
354
290
|
if options[:subcommand] == 'module' && options[:action] == 'install' && options[:object]
|
355
291
|
command = Bolt::Util.powershell? ? 'Add-BoltModule -Module' : 'bolt module add'
|
356
292
|
raise Bolt::CLIError, "Invalid argument '#{options[:object]}'. To add a new module to "\
|
357
293
|
"the project, run '#{command} #{options[:object]}'."
|
358
294
|
end
|
359
295
|
|
296
|
+
if %w[download upload].include?(options[:action])
|
297
|
+
raise Bolt::CLIError, "Must specify a source" unless options[:object]
|
298
|
+
|
299
|
+
if options[:leftovers].empty?
|
300
|
+
raise Bolt::CLIError, "Must specify a destination"
|
301
|
+
elsif options[:leftovers].size > 1
|
302
|
+
raise Bolt::CLIError, "Unknown arguments #{options[:leftovers].drop(1).join(', ')}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
if options[:subcommand] == 'group' && options[:object]
|
307
|
+
raise Bolt::CLIError, "Unknown argument #{options[:object]}"
|
308
|
+
end
|
309
|
+
|
310
|
+
if options[:action] == 'generate-types' && options[:object]
|
311
|
+
raise Bolt::CLIError, "Unknown argument #{options[:object]}"
|
312
|
+
end
|
313
|
+
|
360
314
|
if !%w[file script lookup].include?(options[:subcommand]) &&
|
361
315
|
!options[:leftovers].empty?
|
362
316
|
raise Bolt::CLIError,
|
@@ -381,728 +335,572 @@ module Bolt
|
|
381
335
|
"Option '--env-var' can only be specified when running a command or script"
|
382
336
|
end
|
383
337
|
end
|
384
|
-
end
|
385
|
-
|
386
|
-
def handle_parser_errors
|
387
|
-
yield
|
388
|
-
rescue OptionParser::MissingArgument => e
|
389
|
-
raise Bolt::CLIError, "Option '#{e.args.first}' needs a parameter"
|
390
|
-
rescue OptionParser::InvalidArgument => e
|
391
|
-
raise Bolt::CLIError, "Invalid parameter specified for option '#{e.args.first}': #{e.args[1]}"
|
392
|
-
rescue OptionParser::InvalidOption, OptionParser::AmbiguousOption => e
|
393
|
-
raise Bolt::CLIError, "Unknown argument '#{e.args.first}'"
|
394
|
-
end
|
395
|
-
|
396
|
-
def puppetdb_client
|
397
|
-
plugins.puppetdb_client
|
398
|
-
end
|
399
338
|
|
400
|
-
|
401
|
-
@plugins ||= Bolt::Plugin.setup(config, pal, analytics)
|
339
|
+
validate_targeting_options(options)
|
402
340
|
end
|
403
341
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
config.inventoryfile
|
413
|
-
elsif File.exist?(config.default_inventoryfile)
|
414
|
-
config.default_inventoryfile
|
415
|
-
end
|
342
|
+
# Validates that only one targeting option is provided and that commands
|
343
|
+
# requiring a targeting option received one.
|
344
|
+
#
|
345
|
+
# @param options [Hash] The CLI options.
|
346
|
+
#
|
347
|
+
private def validate_targeting_options(options)
|
348
|
+
target_opts = options.slice(*TARGETING_OPTIONS)
|
349
|
+
target_string = "'--targets', '--rerun', or '--query'"
|
416
350
|
|
417
|
-
|
418
|
-
|
351
|
+
if target_opts.length > 1
|
352
|
+
raise Bolt::CLIError, "Only one targeting option can be specified: #{target_string}"
|
419
353
|
end
|
420
354
|
|
421
|
-
|
422
|
-
|
423
|
-
|
355
|
+
return if %w[guide module plan project secret].include?(options[:subcommand]) ||
|
356
|
+
%w[convert new show].include?(options[:action]) ||
|
357
|
+
options[:plan_hierarchy]
|
424
358
|
|
425
|
-
if
|
426
|
-
Bolt::
|
427
|
-
"cli_overrides",
|
428
|
-
"CLI arguments #{conflicting_options.to_a} might be overridden by Inventory: #{inventory_source}"
|
429
|
-
)
|
359
|
+
if target_opts.empty?
|
360
|
+
raise Bolt::CLIError, "Command requires a targeting option: #{target_string}"
|
430
361
|
end
|
431
362
|
end
|
432
363
|
|
364
|
+
# Execute a Bolt command. The +options+ hash includes the subcommand and
|
365
|
+
# action to be run, as well as any additional arguments and options for the
|
366
|
+
# command.
|
367
|
+
#
|
368
|
+
# @param options [Hash] The CLI options.
|
369
|
+
#
|
433
370
|
def execute(options)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
end
|
371
|
+
with_signal_handling do
|
372
|
+
with_error_handling do
|
373
|
+
# TODO: Separate from options hash and pass as own args.
|
374
|
+
command = options[:subcommand]
|
375
|
+
action = options[:action]
|
376
|
+
|
377
|
+
#
|
378
|
+
# INITIALIZE CORE CLASSES
|
379
|
+
#
|
380
|
+
|
381
|
+
project = if ENV['BOLT_PROJECT']
|
382
|
+
Bolt::Project.create_project(ENV['BOLT_PROJECT'], 'environment')
|
383
|
+
elsif options[:project]
|
384
|
+
dir = Pathname.new(options[:project])
|
385
|
+
if (dir + Bolt::Project::BOLTDIR_NAME).directory?
|
386
|
+
Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
|
387
|
+
else
|
388
|
+
Bolt::Project.create_project(dir)
|
389
|
+
end
|
390
|
+
else
|
391
|
+
Bolt::Project.find_boltdir(Dir.pwd)
|
392
|
+
end
|
457
393
|
|
458
|
-
|
459
|
-
output_format: config.format,
|
460
|
-
# For continuity
|
461
|
-
boltdir_type: config.project.type
|
462
|
-
}.merge!(analytics.plan_counts(config.project.plans_path))
|
394
|
+
config = Bolt::Config.from_project(project, options)
|
463
395
|
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
end
|
396
|
+
@outputter = Bolt::Outputter.for_format(
|
397
|
+
config.format,
|
398
|
+
config.color,
|
399
|
+
options[:verbose],
|
400
|
+
config.trace,
|
401
|
+
config.spinner
|
402
|
+
)
|
472
403
|
|
473
|
-
|
404
|
+
@rerun = Bolt::Rerun.new(config.rerunfile, config.save_rerun)
|
474
405
|
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
show_task(options[:object])
|
481
|
-
else
|
482
|
-
list_tasks
|
406
|
+
# TODO: Subscribe this to the executor.
|
407
|
+
analytics = begin
|
408
|
+
client = Bolt::Analytics.build_client(config.analytics)
|
409
|
+
client.bundled_content = bundled_content(options)
|
410
|
+
client
|
483
411
|
end
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
412
|
+
|
413
|
+
Bolt::Logger.configure(config.log, config.color, config.disable_warnings)
|
414
|
+
Bolt::Logger.stream = config.stream
|
415
|
+
Bolt::Logger.analytics = analytics
|
416
|
+
Bolt::Logger.flush_queue
|
417
|
+
|
418
|
+
executor = Bolt::Executor.new(
|
419
|
+
config.concurrency,
|
420
|
+
analytics,
|
421
|
+
options[:noop],
|
422
|
+
config.modified_concurrency,
|
423
|
+
config.future
|
424
|
+
)
|
425
|
+
|
426
|
+
pal = Bolt::PAL.new(
|
427
|
+
Bolt::Config::Modulepath.new(config.modulepath),
|
428
|
+
config.hiera_config,
|
429
|
+
config.project.resource_types,
|
430
|
+
config.compile_concurrency,
|
431
|
+
config.trusted_external,
|
432
|
+
config.apply_settings,
|
433
|
+
config.project
|
434
|
+
)
|
435
|
+
|
436
|
+
plugins = Bolt::Plugin.new(config, pal, analytics)
|
437
|
+
|
438
|
+
inventory = Bolt::Inventory.from_config(config, plugins)
|
439
|
+
|
440
|
+
log_outputter = Bolt::Outputter::Logger.new(options[:verbose], config.trace)
|
441
|
+
|
442
|
+
#
|
443
|
+
# FINALIZING SETUP
|
444
|
+
#
|
445
|
+
|
446
|
+
check_gem_install
|
447
|
+
warn_inventory_overrides_cli(config, options)
|
448
|
+
submit_screen_view(analytics, config, inventory, options)
|
449
|
+
options[:targets] = process_target_list(plugins.puppetdb_client, @rerun, options)
|
450
|
+
|
451
|
+
# TODO: Fix casing issue in Windows.
|
452
|
+
config.check_path_case('modulepath', config.modulepath)
|
453
|
+
|
454
|
+
if options[:clear_cache]
|
455
|
+
FileUtils.rm(config.project.plugin_cache_file) if File.exist?(config.project.plugin_cache_file)
|
456
|
+
FileUtils.rm(config.project.task_cache_file) if File.exist?(config.project.task_cache_file)
|
457
|
+
FileUtils.rm(config.project.plan_cache_file) if File.exist?(config.project.plan_cache_file)
|
489
458
|
end
|
490
|
-
|
491
|
-
|
492
|
-
|
459
|
+
|
460
|
+
case command
|
461
|
+
when 'apply', 'lookup'
|
462
|
+
if %w[human rainbow].include?(config.format)
|
463
|
+
executor.subscribe(outputter)
|
464
|
+
end
|
465
|
+
when 'plan'
|
466
|
+
if %w[human rainbow].include?(config.format)
|
467
|
+
executor.subscribe(outputter)
|
468
|
+
else
|
469
|
+
executor.subscribe(outputter, %i[message verbose])
|
470
|
+
end
|
493
471
|
else
|
494
|
-
|
472
|
+
executor.subscribe(outputter)
|
495
473
|
end
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
474
|
+
|
475
|
+
executor.subscribe(log_outputter)
|
476
|
+
|
477
|
+
# TODO: Figure out where this should really go. It doesn't seem to
|
478
|
+
# make sense in the application, since the params should already
|
479
|
+
# be data when they reach that point.
|
480
|
+
if %w[plan task].include?(command) && action == 'run'
|
481
|
+
options[:params] = parse_params(
|
482
|
+
command,
|
483
|
+
options[:object],
|
484
|
+
pal,
|
485
|
+
**options.slice(:params, :params_parsed)
|
486
|
+
)
|
503
487
|
end
|
504
|
-
|
505
|
-
|
488
|
+
|
489
|
+
application = Bolt::Application.new(
|
490
|
+
analytics: analytics,
|
491
|
+
config: config,
|
492
|
+
executor: executor,
|
493
|
+
inventory: inventory,
|
494
|
+
pal: pal,
|
495
|
+
plugins: plugins
|
496
|
+
)
|
497
|
+
|
498
|
+
process_command(application, command, action, options)
|
499
|
+
ensure
|
500
|
+
analytics&.finish
|
506
501
|
end
|
507
|
-
return 0
|
508
|
-
when 'convert'
|
509
|
-
pal.convert_plan(options[:object])
|
510
|
-
return 0
|
511
502
|
end
|
503
|
+
end
|
512
504
|
|
513
|
-
|
505
|
+
# Process the command.
|
506
|
+
#
|
507
|
+
# @param app [Bolt::Application] The application.
|
508
|
+
# @param command [String] The command.
|
509
|
+
# @param action [String, NilClass] The action.
|
510
|
+
# @param options [Hash] The CLI options.
|
511
|
+
#
|
512
|
+
private def process_command(app, command, action, options)
|
513
|
+
case command
|
514
|
+
when 'apply'
|
515
|
+
results = outputter.spin do
|
516
|
+
app.apply(options[:object], options[:targets], **options.slice(:code, :noop))
|
517
|
+
end
|
518
|
+
rerun.update(results)
|
519
|
+
app.shutdown
|
520
|
+
outputter.print_apply_result(results)
|
521
|
+
results.ok? ? SUCCESS : FAILURE
|
514
522
|
|
515
|
-
|
516
|
-
|
517
|
-
|
523
|
+
when 'command'
|
524
|
+
outputter.print_head
|
525
|
+
results = app.run_command(options[:object], options[:targets], **options.slice(:env_vars))
|
526
|
+
rerun.update(results)
|
527
|
+
app.shutdown
|
528
|
+
outputter.print_summary(results, results.elapsed_time)
|
529
|
+
results.ok? ? SUCCESS : FAILURE
|
530
|
+
|
531
|
+
when 'file'
|
532
|
+
case action
|
533
|
+
when 'download'
|
534
|
+
outputter.print_head
|
535
|
+
results = app.download_file(options[:object], options[:leftovers].first, options[:targets])
|
536
|
+
rerun.update(results)
|
537
|
+
app.shutdown
|
538
|
+
outputter.print_summary(results, results.elapsed_time)
|
539
|
+
results.ok? ? SUCCESS : FAILURE
|
540
|
+
when 'upload'
|
541
|
+
outputter.print_head
|
542
|
+
results = app.upload_file(options[:object], options[:leftovers].first, options[:targets])
|
543
|
+
rerun.update(results)
|
544
|
+
app.shutdown
|
545
|
+
outputter.print_summary(results, results.elapsed_time)
|
546
|
+
results.ok? ? SUCCESS : FAILURE
|
547
|
+
end
|
548
|
+
|
549
|
+
when 'group'
|
550
|
+
outputter.print_groups(**app.list_groups)
|
551
|
+
SUCCESS
|
518
552
|
|
519
|
-
case options[:subcommand]
|
520
553
|
when 'guide'
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
end
|
526
|
-
when 'project'
|
527
|
-
case options[:action]
|
528
|
-
when 'init'
|
529
|
-
code = Bolt::ProjectManager.new(config, outputter, pal)
|
530
|
-
.create(Dir.pwd, options[:object], options[:modules])
|
531
|
-
when 'migrate'
|
532
|
-
code = Bolt::ProjectManager.new(config, outputter, pal).migrate
|
554
|
+
if options[:object]
|
555
|
+
outputter.print_guide(**app.show_guide(options[:object]))
|
556
|
+
else
|
557
|
+
outputter.print_topics(**app.list_guides)
|
533
558
|
end
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
559
|
+
SUCCESS
|
560
|
+
|
561
|
+
when 'inventory'
|
562
|
+
targets = app.show_inventory(options[:targets])
|
563
|
+
.merge(flag: !options[:targets].nil?)
|
564
|
+
if options[:detail]
|
565
|
+
outputter.print_target_info(**targets)
|
566
|
+
else
|
567
|
+
outputter.print_targets(**targets)
|
541
568
|
end
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
569
|
+
SUCCESS
|
570
|
+
|
571
|
+
when 'lookup'
|
572
|
+
options[:vars] = parse_vars(options[:leftovers])
|
573
|
+
if options[:plan_hierarchy]
|
574
|
+
outputter.print_plan_lookup(app.plan_lookup(options[:object], **options.slice(:vars)))
|
575
|
+
SUCCESS
|
576
|
+
else
|
577
|
+
results = outputter.spin do
|
578
|
+
app.lookup(options[:object], options[:targets], **options.slice(:vars))
|
579
|
+
end
|
580
|
+
rerun.update(results)
|
581
|
+
app.shutdown
|
582
|
+
outputter.print_result_set(results)
|
583
|
+
results.ok? ? SUCCESS : FAILURE
|
555
584
|
end
|
585
|
+
|
556
586
|
when 'module'
|
557
|
-
case
|
587
|
+
case action
|
558
588
|
when 'add'
|
559
|
-
|
560
|
-
|
561
|
-
code = install_project_modules(config.project, config.module_install, options[:force], options[:resolve])
|
589
|
+
ok = outputter.spin { app.add_module(options[:object], outputter) }
|
590
|
+
ok ? SUCCESS : FAILURE
|
562
591
|
when 'generate-types'
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
592
|
+
app.generate_types
|
593
|
+
SUCCESS
|
594
|
+
when 'install'
|
595
|
+
ok = outputter.spin { app.install_modules(outputter, **options.slice(:force, :resolve)) }
|
596
|
+
ok ? SUCCESS : FAILURE
|
597
|
+
when 'show'
|
598
|
+
if options[:object]
|
599
|
+
outputter.print_module_info(**app.show_module(options[:object]))
|
600
|
+
else
|
601
|
+
outputter.print_module_list(app.list_modules)
|
602
|
+
end
|
603
|
+
SUCCESS
|
571
604
|
end
|
572
|
-
code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
|
573
|
-
else
|
574
|
-
executor = Bolt::Executor.new(config.concurrency,
|
575
|
-
analytics,
|
576
|
-
options[:noop],
|
577
|
-
config.modified_concurrency,
|
578
|
-
config.future)
|
579
|
-
targets = options[:targets]
|
580
|
-
|
581
|
-
results = nil
|
582
|
-
outputter.print_head
|
583
605
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
if src.nil?
|
608
|
-
raise Bolt::CLIError, "A source path must be specified"
|
609
|
-
end
|
610
|
-
|
611
|
-
if dest.nil?
|
612
|
-
raise Bolt::CLIError, "A destination path must be specified"
|
613
|
-
end
|
614
|
-
|
615
|
-
case options[:action]
|
616
|
-
when 'download'
|
617
|
-
dest = File.expand_path(dest, Dir.pwd)
|
618
|
-
executor.download_file(targets, src, dest, executor_opts)
|
619
|
-
when 'upload'
|
620
|
-
src_path = find_file(src, executor.future&.fetch('file_paths', false))
|
621
|
-
validate_file('source file', src_path, true)
|
622
|
-
executor.upload_file(targets, src_path, dest, executor_opts)
|
623
|
-
end
|
624
|
-
end
|
606
|
+
when 'plan'
|
607
|
+
case action
|
608
|
+
when 'convert'
|
609
|
+
app.convert_plan(options[:object])
|
610
|
+
SUCCESS
|
611
|
+
when 'new'
|
612
|
+
result = app.new_plan(options[:object], **options.slice(:puppet, :plan_script))
|
613
|
+
outputter.print_new_plan(**result)
|
614
|
+
SUCCESS
|
615
|
+
when 'run'
|
616
|
+
result = app.run_plan(options[:object], options[:targets], **options.slice(:params))
|
617
|
+
rerun.update(result)
|
618
|
+
app.shutdown
|
619
|
+
outputter.print_plan_result(result)
|
620
|
+
result.ok? ? SUCCESS : FAILURE
|
621
|
+
when 'show'
|
622
|
+
if options[:object]
|
623
|
+
outputter.print_plan_info(app.show_plan(options[:object]))
|
624
|
+
else
|
625
|
+
outputter.print_plans(**app.list_plans(**options.slice(:filter)))
|
626
|
+
end
|
627
|
+
SUCCESS
|
625
628
|
end
|
626
629
|
|
627
|
-
|
628
|
-
|
630
|
+
when 'plugin'
|
631
|
+
outputter.print_plugin_list(**app.list_plugins)
|
632
|
+
SUCCESS
|
629
633
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
Signal.trap :INT, handler if handler
|
640
|
-
analytics&.finish
|
641
|
-
end
|
642
|
-
|
643
|
-
def show_task(task_name)
|
644
|
-
outputter.print_task_info(pal.get_task(task_name))
|
645
|
-
end
|
646
|
-
|
647
|
-
# Filters a list of content by matching substring.
|
648
|
-
#
|
649
|
-
private def filter_content(content, filter)
|
650
|
-
return content unless content && filter
|
651
|
-
content.select { |name,| name.include?(filter) }
|
652
|
-
end
|
653
|
-
|
654
|
-
def list_tasks
|
655
|
-
tasks = filter_content(pal.list_tasks_with_cache(filter_content: true), options[:filter])
|
656
|
-
outputter.print_tasks(tasks, pal.user_modulepath)
|
657
|
-
end
|
658
|
-
|
659
|
-
def show_plan(plan_name)
|
660
|
-
outputter.print_plan_info(pal.get_plan_info(plan_name))
|
661
|
-
end
|
662
|
-
|
663
|
-
def list_plans
|
664
|
-
plans = filter_content(pal.list_plans_with_cache(filter_content: true), options[:filter])
|
665
|
-
outputter.print_plans(plans, pal.user_modulepath)
|
666
|
-
end
|
634
|
+
when 'project'
|
635
|
+
case action
|
636
|
+
when 'init'
|
637
|
+
app.create_project(options[:object], outputter, **options.slice(:modules))
|
638
|
+
SUCCESS
|
639
|
+
when 'migrate'
|
640
|
+
app.migrate_project(outputter)
|
641
|
+
SUCCESS
|
642
|
+
end
|
667
643
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
644
|
+
when 'script'
|
645
|
+
outputter.print_head
|
646
|
+
opts = options.slice(:env_vars).merge(arguments: options[:leftovers])
|
647
|
+
results = app.run_script(options[:object], options[:targets], **opts)
|
648
|
+
rerun.update(results)
|
649
|
+
app.shutdown
|
650
|
+
outputter.print_summary(results, results.elapsed_time)
|
651
|
+
results.ok? ? SUCCESS : FAILURE
|
674
652
|
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
653
|
+
when 'secret'
|
654
|
+
case action
|
655
|
+
when 'createkeys'
|
656
|
+
result = app.create_secret_keys(**options.slice(:force, :plugin))
|
657
|
+
outputter.print_message(result)
|
658
|
+
SUCCESS
|
659
|
+
when 'decrypt'
|
660
|
+
result = app.decrypt_secret(options[:object], **options.slice(:plugin))
|
661
|
+
outputter.print_message(result)
|
662
|
+
SUCCESS
|
663
|
+
when 'encrypt'
|
664
|
+
result = app.encrypt_secret(options[:object], **options.slice(:plugin))
|
665
|
+
outputter.print_message(result)
|
666
|
+
SUCCESS
|
667
|
+
end
|
682
668
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
669
|
+
when 'task'
|
670
|
+
case action
|
671
|
+
when 'run'
|
672
|
+
outputter.print_head
|
673
|
+
results = app.run_task(options[:object], options[:targets], **options.slice(:params))
|
674
|
+
rerun.update(results)
|
675
|
+
app.shutdown
|
676
|
+
outputter.print_summary(results, results.elapsed_time)
|
677
|
+
results.ok? ? SUCCESS : FAILURE
|
678
|
+
when 'show'
|
679
|
+
if options[:object]
|
680
|
+
outputter.print_task_info(**app.show_task(options[:object]))
|
681
|
+
else
|
682
|
+
outputter.print_tasks(**app.list_tasks(**options.slice(:filter)))
|
683
|
+
end
|
684
|
+
SUCCESS
|
685
|
+
end
|
688
686
|
end
|
689
|
-
|
690
|
-
outputter.print_target_info(
|
691
|
-
group_targets_by_source,
|
692
|
-
inventory.source,
|
693
|
-
config.default_inventoryfile,
|
694
|
-
target_flag
|
695
|
-
)
|
696
687
|
end
|
697
688
|
|
698
|
-
#
|
699
|
-
#
|
689
|
+
# Process the target list by turning a PuppetDB query or rerun mode into a
|
690
|
+
# list of target names.
|
700
691
|
#
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
692
|
+
# @param pdb_client [Bolt::PuppetDB::Client] The PuppetDB client.
|
693
|
+
# @param rerun [Bolt::Rerun] The Rerun instance.
|
694
|
+
# @param options [Hash] The CLI options.
|
695
|
+
# @return [Hash] The target list.
|
696
|
+
#
|
697
|
+
private def process_target_list(pdb_client, rerun, options)
|
698
|
+
if options[:query]
|
699
|
+
pdb_client.query_certnames(options[:query])
|
700
|
+
elsif options[:rerun]
|
701
|
+
rerun.get_targets(options[:rerun])
|
702
|
+
elsif options[:targets]
|
703
|
+
options[:targets]
|
710
704
|
end
|
711
|
-
|
712
|
-
{ inventory: inventory_targets, adhoc: adhoc_targets }
|
713
|
-
end
|
714
|
-
|
715
|
-
def list_groups
|
716
|
-
outputter.print_groups(inventory.group_names.sort, inventory.source, config.default_inventoryfile)
|
717
705
|
end
|
718
706
|
|
719
|
-
#
|
720
|
-
# provided variable values for interpolations
|
707
|
+
# List content that ships with Bolt.
|
721
708
|
#
|
722
|
-
|
723
|
-
result = pal.plan_hierarchy_lookup(key, plan_vars: plan_vars)
|
724
|
-
outputter.print_plan_lookup(result)
|
725
|
-
0
|
726
|
-
end
|
727
|
-
|
728
|
-
# Looks up a value with Hiera, using targets as the contexts to perform the
|
729
|
-
# look ups in. This should return the same value as a lookup in an apply block.
|
709
|
+
# @param options [Hash] The CLI options.
|
730
710
|
#
|
731
|
-
def
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
executor.subscribe(outputter) if config.format == 'human'
|
741
|
-
executor.subscribe(log_outputter)
|
742
|
-
executor.publish_event(type: :plan_start, plan: nil)
|
743
|
-
|
744
|
-
results = outputter.spin do
|
745
|
-
pal.lookup(
|
746
|
-
key,
|
747
|
-
targets,
|
748
|
-
inventory,
|
749
|
-
executor,
|
750
|
-
plan_vars: plan_vars
|
751
|
-
)
|
752
|
-
end
|
753
|
-
|
754
|
-
executor.shutdown
|
755
|
-
outputter.print_result_set(results)
|
756
|
-
|
757
|
-
results.ok ? 0 : 1
|
758
|
-
end
|
759
|
-
|
760
|
-
def run_plan(plan_name, plan_arguments, nodes, options)
|
761
|
-
unless nodes.empty?
|
762
|
-
if plan_arguments['nodes'] || plan_arguments['targets']
|
763
|
-
key = plan_arguments.include?('nodes') ? 'nodes' : 'targets'
|
764
|
-
raise Bolt::CLIError,
|
765
|
-
"A plan's '#{key}' parameter can be specified using the --#{key} option, but in that " \
|
766
|
-
"case it must not be specified as a separate #{key}=<value> parameter nor included " \
|
767
|
-
"in the JSON data passed in the --params option"
|
711
|
+
private def bundled_content(options)
|
712
|
+
# We only need to enumerate bundled content when running a task or plan
|
713
|
+
content = { 'Plan' => [],
|
714
|
+
'Task' => [],
|
715
|
+
'Plugin' => Bolt::Plugin::BUILTIN_PLUGINS }
|
716
|
+
if %w[plan task].include?(options[:subcommand]) && options[:action] == 'run'
|
717
|
+
default_content = Bolt::PAL.new(Bolt::Config::Modulepath.new([]), nil, nil)
|
718
|
+
content['Plan'] = default_content.list_plans.each_with_object([]) do |iter, col|
|
719
|
+
col << iter&.first
|
768
720
|
end
|
769
|
-
|
770
|
-
|
771
|
-
target_param = plan_params.dig('targets', 'type') =~ /TargetSpec/
|
772
|
-
node_param = plan_params.include?('nodes')
|
773
|
-
|
774
|
-
if node_param && target_param
|
775
|
-
msg = "Plan parameters include both 'nodes' and 'targets' with type 'TargetSpec', " \
|
776
|
-
"neither will populated with the value for --nodes or --targets."
|
777
|
-
Bolt::Logger.warn("nodes_targets_parameters", msg)
|
778
|
-
elsif node_param
|
779
|
-
plan_arguments['nodes'] = nodes.join(',')
|
780
|
-
elsif target_param
|
781
|
-
plan_arguments['targets'] = nodes.join(',')
|
721
|
+
content['Task'] = default_content.list_tasks.each_with_object([]) do |iter, col|
|
722
|
+
col << iter&.first
|
782
723
|
end
|
783
724
|
end
|
784
725
|
|
785
|
-
|
786
|
-
params: plan_arguments }
|
787
|
-
|
788
|
-
executor = Bolt::Executor.new(config.concurrency,
|
789
|
-
analytics,
|
790
|
-
options[:noop],
|
791
|
-
config.modified_concurrency,
|
792
|
-
config.future)
|
793
|
-
if %w[human rainbow].include?(options.fetch(:format, 'human'))
|
794
|
-
executor.subscribe(outputter)
|
795
|
-
else
|
796
|
-
# Only subscribe to out module events for JSON outputter
|
797
|
-
executor.subscribe(outputter, %i[message verbose])
|
798
|
-
end
|
799
|
-
|
800
|
-
executor.subscribe(log_outputter)
|
801
|
-
executor.start_plan(plan_context)
|
802
|
-
result = pal.run_plan(plan_name, plan_arguments, executor, inventory, puppetdb_client)
|
803
|
-
|
804
|
-
# If a non-bolt exception bubbles up the plan won't get finished
|
805
|
-
executor.finish_plan(result)
|
806
|
-
executor.shutdown
|
807
|
-
rerun.update(result)
|
808
|
-
|
809
|
-
outputter.print_plan_result(result)
|
810
|
-
result.ok? ? 0 : 1
|
726
|
+
content
|
811
727
|
end
|
812
728
|
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
"Definitions must be declared for their resources to be applied. You can read more "\
|
822
|
-
"about defining and declaring classes and types in the Puppet documentation at "\
|
823
|
-
"https://puppet.com/docs/puppet/latest/lang_classes.html and "\
|
824
|
-
"https://puppet.com/docs/puppet/latest/lang_defined_types.html"
|
825
|
-
Bolt::Logger.warn("empty_manifest", message)
|
826
|
-
end
|
827
|
-
|
828
|
-
executor = Bolt::Executor.new(config.concurrency,
|
829
|
-
analytics,
|
830
|
-
noop,
|
831
|
-
config.modified_concurrency,
|
832
|
-
config.future)
|
833
|
-
executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
|
834
|
-
executor.subscribe(log_outputter)
|
835
|
-
# apply logging looks like plan logging, so tell the outputter we're in a
|
836
|
-
# plan even though we're not
|
837
|
-
executor.publish_event(type: :plan_start, plan: nil)
|
838
|
-
|
839
|
-
results = nil
|
840
|
-
elapsed_time = Benchmark.realtime do
|
841
|
-
apply_prep_results = pal.in_plan_compiler(executor, inventory, puppetdb_client) do |compiler|
|
842
|
-
compiler.call_function('apply_prep', targets, '_catch_errors' => true)
|
843
|
-
end
|
729
|
+
# Check and warn if Bolt is installed as a gem.
|
730
|
+
#
|
731
|
+
private def check_gem_install
|
732
|
+
if ENV['BOLT_GEM'].nil? && incomplete_install?
|
733
|
+
msg = <<~MSG.chomp
|
734
|
+
Bolt might be installed as a gem. To use Bolt reliably and with all of its
|
735
|
+
dependencies, uninstall the 'bolt' gem and install Bolt as a package:
|
736
|
+
https://puppet.com/docs/bolt/latest/bolt_installing.html
|
844
737
|
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
end
|
738
|
+
If you meant to install Bolt as a gem and want to disable this warning,
|
739
|
+
set the BOLT_GEM environment variable.
|
740
|
+
MSG
|
849
741
|
|
850
|
-
|
742
|
+
Bolt::Logger.warn("gem_install", msg)
|
851
743
|
end
|
852
|
-
|
853
|
-
executor.shutdown
|
854
|
-
outputter.print_apply_result(results, elapsed_time)
|
855
|
-
rerun.update(results)
|
856
|
-
|
857
|
-
results.ok ? 0 : 1
|
858
|
-
end
|
859
|
-
|
860
|
-
def list_modules
|
861
|
-
outputter.print_module_list(pal.list_modules)
|
862
|
-
end
|
863
|
-
|
864
|
-
def show_module(name)
|
865
|
-
outputter.print_module_info(**pal.show_module(name))
|
866
|
-
end
|
867
|
-
|
868
|
-
def list_plugins
|
869
|
-
outputter.print_plugin_list(plugins.list_plugins, pal.user_modulepath)
|
870
|
-
end
|
871
|
-
|
872
|
-
def generate_types
|
873
|
-
# generate_types will surface a nice error with helpful message if it fails
|
874
|
-
pal.generate_types(cache: true)
|
875
|
-
0
|
876
744
|
end
|
877
745
|
|
878
|
-
#
|
746
|
+
# Print a fatal error. Print using the outputter if it's configured.
|
747
|
+
# Otherwise, mock the output by printing directly to stdout.
|
879
748
|
#
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
if
|
884
|
-
outputter.
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
end
|
890
|
-
|
891
|
-
installer = Bolt::ModuleInstaller.new(outputter, pal)
|
892
|
-
|
893
|
-
ok = outputter.spin do
|
894
|
-
installer.install(project.modules,
|
895
|
-
project.puppetfile,
|
896
|
-
project.managed_moduledir,
|
897
|
-
config,
|
898
|
-
force: force,
|
899
|
-
resolve: resolve)
|
749
|
+
# @param error [StandardError] The error to print.
|
750
|
+
#
|
751
|
+
private def fatal_error(error)
|
752
|
+
if @outputter
|
753
|
+
@outputter.fatal_error(error)
|
754
|
+
elsif $stdout.isatty
|
755
|
+
$stdout.puts("\033[31m#{error.message}\033[0m")
|
756
|
+
else
|
757
|
+
$stdout.puts(error.message)
|
900
758
|
end
|
901
|
-
|
902
|
-
ok ? 0 : 1
|
903
759
|
end
|
904
760
|
|
905
|
-
#
|
761
|
+
# Query whether Bolt is installed as a gem or package by checking if all
|
762
|
+
# built-in modules are installed.
|
906
763
|
#
|
907
|
-
def
|
908
|
-
|
909
|
-
|
910
|
-
installer = Bolt::ModuleInstaller.new(outputter, pal)
|
911
|
-
|
912
|
-
ok = outputter.spin do
|
913
|
-
installer.add(name,
|
914
|
-
project.modules,
|
915
|
-
project.puppetfile,
|
916
|
-
project.managed_moduledir,
|
917
|
-
project.project_file,
|
918
|
-
config)
|
919
|
-
end
|
920
|
-
|
921
|
-
ok ? 0 : 1
|
764
|
+
private def incomplete_install?
|
765
|
+
builtin_module_list = %w[aggregate canary puppetdb_fact secure_env_vars puppet_connect]
|
766
|
+
(Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - builtin_module_list).empty?
|
922
767
|
end
|
923
768
|
|
924
|
-
#
|
769
|
+
# Parse parameters for tasks and plans.
|
925
770
|
#
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
|
771
|
+
# @param options [Hash] Options from the calling method.
|
772
|
+
#
|
773
|
+
private def parse_params(command, object, pal, params: nil, params_parsed: nil)
|
774
|
+
if params
|
775
|
+
params_parsed ? params : pal.parse_params(command, object, params)
|
776
|
+
else
|
777
|
+
{}
|
934
778
|
end
|
935
779
|
end
|
936
780
|
|
937
|
-
#
|
781
|
+
# Parse variables for lookups.
|
938
782
|
#
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
end
|
945
|
-
|
946
|
-
ok ? 0 : 1
|
783
|
+
# @param vars [Array, NilClass] Unparsed variables.
|
784
|
+
#
|
785
|
+
private def parse_vars(vars)
|
786
|
+
return unless vars
|
787
|
+
Hash[vars.map { |a| a.split('=', 2) }]
|
947
788
|
end
|
948
789
|
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
790
|
+
# TODO: See if this can be moved to Bolt::Analytics.
|
791
|
+
#
|
792
|
+
# Submit a screen view to the analytics client.
|
793
|
+
#
|
794
|
+
# @param analytics [Bolt::Analytics] The analytics client.
|
795
|
+
# @param config [Bolt::Config] The config.
|
796
|
+
# @param inventory [Bolt::Inventory] The inventory.
|
797
|
+
# @param options [Hash] The CLI options.
|
798
|
+
#
|
799
|
+
private def submit_screen_view(analytics, config, inventory, options)
|
800
|
+
screen = "#{options[:subcommand]}_#{options[:action]}"
|
958
801
|
|
959
|
-
|
960
|
-
|
961
|
-
@guides ||= begin
|
962
|
-
root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
|
963
|
-
files = Dir.children(root_path).sort
|
964
|
-
|
965
|
-
files.each_with_object({}) do |file, guides|
|
966
|
-
next if file !~ /\.(yaml|yml)\z/
|
967
|
-
# The ".*" here removes any suffix
|
968
|
-
topic = File.basename(file, ".*")
|
969
|
-
guides[topic] = File.join(root_path, file)
|
970
|
-
end
|
971
|
-
rescue SystemCallError => e
|
972
|
-
raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
|
802
|
+
if options[:action] == 'show' && options[:object]
|
803
|
+
screen += '_object'
|
973
804
|
end
|
974
|
-
end
|
975
805
|
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
analytics.event('Guide', 'known_topic', label: topic)
|
986
|
-
|
987
|
-
begin
|
988
|
-
guide = Bolt::Util.read_yaml_hash(guides[topic], 'guide')
|
989
|
-
rescue SystemCallError => e
|
990
|
-
raise Bolt::FileError("#{e.message}: unable to load guide page", filepath)
|
991
|
-
end
|
806
|
+
pp_count, yaml_count = if File.exist?(config.project.plans_path)
|
807
|
+
%w[pp yaml].map do |extension|
|
808
|
+
Find.find(config.project.plans_path.to_s)
|
809
|
+
.grep(/.*\.#{extension}/)
|
810
|
+
.length
|
811
|
+
end
|
812
|
+
else
|
813
|
+
[0, 0]
|
814
|
+
end
|
992
815
|
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
outputter.print_guide(**Bolt::Util.symbolize_top_level_keys(guide))
|
1000
|
-
else
|
1001
|
-
analytics.event('Guide', 'unknown_topic', label: topic)
|
1002
|
-
outputter.print_message("Did not find guide for topic '#{topic}'.\n\n")
|
1003
|
-
list_topics
|
1004
|
-
end
|
1005
|
-
0
|
1006
|
-
end
|
816
|
+
screen_view_fields = {
|
817
|
+
output_format: config.format,
|
818
|
+
boltdir_type: config.project.type,
|
819
|
+
puppet_plan_count: pp_count,
|
820
|
+
yaml_plan_count: yaml_count
|
821
|
+
}
|
1007
822
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
823
|
+
if options.key?(:targets)
|
824
|
+
screen_view_fields.merge!(
|
825
|
+
target_nodes: options[:targets].count,
|
826
|
+
inventory_nodes: inventory.node_names.count,
|
827
|
+
inventory_groups: inventory.group_names.count,
|
828
|
+
inventory_version: inventory.version
|
829
|
+
)
|
1011
830
|
end
|
1012
831
|
|
1013
|
-
|
832
|
+
analytics.screen_view(screen, **screen_view_fields)
|
1014
833
|
end
|
1015
834
|
|
1016
|
-
#
|
1017
|
-
#
|
1018
|
-
# the path is a Puppet file path and looks for the file in a module's files
|
1019
|
-
# directory.
|
835
|
+
# Issue a deprecation warning if the user is running an unsupported version
|
836
|
+
# of PowerShell on the controller.
|
1020
837
|
#
|
1021
|
-
def
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
if modules[mod]
|
1028
|
-
@logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
|
1029
|
-
found = Bolt::Util.find_file_in_module(modules[mod].path, file || "", future_file_paths)
|
1030
|
-
path = found.nil? ? File.join(modules[mod].path, 'files', file) : found
|
1031
|
-
end
|
1032
|
-
path
|
1033
|
-
end
|
838
|
+
private def validate_ps_version
|
839
|
+
if Bolt::Util.powershell?
|
840
|
+
command = "powershell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy "\
|
841
|
+
"Bypass -Command $PSVersionTable.PSVersion.Major"
|
842
|
+
stdout, _stderr, _status = Open3.capture3(command)
|
1034
843
|
|
1035
|
-
|
1036
|
-
@rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
|
1037
|
-
end
|
844
|
+
return unless !stdout.empty? && stdout.to_i < 3
|
1038
845
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
options[:verbose],
|
1043
|
-
config.trace,
|
1044
|
-
config.spinner)
|
846
|
+
msg = "Detected PowerShell 2 on controller. PowerShell 2 is unsupported."
|
847
|
+
Bolt::Logger.deprecation_warning("powershell_2_controller", msg)
|
848
|
+
end
|
1045
849
|
end
|
1046
850
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
851
|
+
# Warn the user that transport configuration options set from the command
|
852
|
+
# line may be overridden by transport configuration set in the inventory.
|
853
|
+
#
|
854
|
+
# @param opts [Hash] The CLI options.
|
855
|
+
#
|
856
|
+
private def warn_inventory_overrides_cli(config, opts)
|
857
|
+
inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
|
858
|
+
Bolt::Inventory::ENVIRONMENT_VAR
|
859
|
+
elsif config.inventoryfile
|
860
|
+
config.inventoryfile
|
861
|
+
elsif File.exist?(config.default_inventoryfile)
|
862
|
+
config.default_inventoryfile
|
863
|
+
end
|
1050
864
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
client = Bolt::Analytics.build_client(config.analytics)
|
1054
|
-
client.bundled_content = bundled_content
|
1055
|
-
client
|
865
|
+
inventory_cli_opts = %i[authentication escalation transports].each_with_object([]) do |key, acc|
|
866
|
+
acc.concat(Bolt::BoltOptionParser::OPTIONS[key])
|
1056
867
|
end
|
1057
|
-
end
|
1058
868
|
|
1059
|
-
|
1060
|
-
# If the bundled content directory is empty, Bolt is likely installed as a gem.
|
1061
|
-
if ENV['BOLT_GEM'].nil? && incomplete_install?
|
1062
|
-
msg = <<~MSG.chomp
|
1063
|
-
Bolt might be installed as a gem. To use Bolt reliably and with all of its
|
1064
|
-
dependencies, uninstall the 'bolt' gem and install Bolt as a package:
|
1065
|
-
https://puppet.com/docs/bolt/latest/bolt_installing.html
|
1066
|
-
|
1067
|
-
If you meant to install Bolt as a gem and want to disable this warning,
|
1068
|
-
set the BOLT_GEM environment variable.
|
1069
|
-
MSG
|
869
|
+
inventory_cli_opts.concat(%w[no-host-key-check no-ssl no-ssl-verify no-tty])
|
1070
870
|
|
1071
|
-
|
1072
|
-
end
|
871
|
+
conflicting_options = Set.new(opts.keys.map(&:to_s)).intersection(inventory_cli_opts)
|
1073
872
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
default_content = Bolt::PAL.new(Bolt::Config::Modulepath.new([]), nil, nil)
|
1080
|
-
content['Plan'] = default_content.list_plans.each_with_object([]) do |iter, col|
|
1081
|
-
col << iter&.first
|
1082
|
-
end
|
1083
|
-
content['Task'] = default_content.list_tasks.each_with_object([]) do |iter, col|
|
1084
|
-
col << iter&.first
|
1085
|
-
end
|
873
|
+
if inventory_source && conflicting_options.any?
|
874
|
+
Bolt::Logger.warn(
|
875
|
+
"cli_overrides",
|
876
|
+
"CLI arguments #{conflicting_options.to_a} might be overridden by Inventory: #{inventory_source}"
|
877
|
+
)
|
1086
878
|
end
|
1087
|
-
|
1088
|
-
content
|
1089
879
|
end
|
1090
880
|
|
1091
|
-
#
|
1092
|
-
#
|
1093
|
-
def
|
1094
|
-
|
1095
|
-
|
881
|
+
# Handle and print errors.
|
882
|
+
#
|
883
|
+
private def with_error_handling
|
884
|
+
yield
|
885
|
+
rescue Bolt::Error => e
|
886
|
+
fatal_error(e)
|
887
|
+
raise e
|
1096
888
|
end
|
1097
889
|
|
1098
|
-
#
|
1099
|
-
#
|
1100
|
-
def
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
890
|
+
# Handle signals.
|
891
|
+
#
|
892
|
+
private def with_signal_handling
|
893
|
+
handler = Signal.trap :INT do |signo|
|
894
|
+
Bolt::Logger.logger(self).info(
|
895
|
+
"Exiting after receiving SIG#{Signal.signame(signo)} signal. "\
|
896
|
+
"There might be processes left executing on some targets."
|
897
|
+
)
|
898
|
+
exit!
|
1105
899
|
end
|
900
|
+
|
901
|
+
yield
|
902
|
+
ensure
|
903
|
+
Signal.trap :INT, handler if handler
|
1106
904
|
end
|
1107
905
|
end
|
1108
906
|
end
|