bolt 2.23.0 → 2.27.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 +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
- data/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +11 -7
- data/lib/bolt/applicator.rb +11 -10
- data/lib/bolt/bolt_option_parser.rb +75 -13
- data/lib/bolt/catalog.rb +4 -2
- data/lib/bolt/cli.rb +156 -176
- data/lib/bolt/config.rb +55 -25
- data/lib/bolt/config/options.rb +28 -6
- data/lib/bolt/executor.rb +5 -3
- data/lib/bolt/inventory.rb +8 -1
- data/lib/bolt/inventory/group.rb +4 -4
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/logger.rb +12 -6
- data/lib/bolt/outputter/human.rb +10 -0
- data/lib/bolt/outputter/json.rb +11 -0
- data/lib/bolt/outputter/logger.rb +3 -3
- data/lib/bolt/outputter/rainbow.rb +15 -0
- data/lib/bolt/pal.rb +23 -12
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +63 -17
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +160 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +66 -0
- data/lib/bolt/r10k_log_proxy.rb +1 -1
- data/lib/bolt/rerun.rb +2 -2
- data/lib/bolt/result.rb +23 -0
- data/lib/bolt/shell.rb +1 -1
- data/lib/bolt/shell/bash.rb +7 -7
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +10 -10
- data/lib/bolt/transport/local/connection.rb +3 -3
- data/lib/bolt/transport/orch.rb +3 -3
- data/lib/bolt/transport/ssh.rb +1 -1
- data/lib/bolt/transport/ssh/connection.rb +6 -6
- data/lib/bolt/transport/ssh/exec_connection.rb +5 -5
- data/lib/bolt/transport/winrm.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +9 -9
- data/lib/bolt/util.rb +2 -2
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +2 -2
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/file_cache.rb +1 -1
- data/lib/bolt_server/transport_app.rb +189 -14
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/run.rb +3 -0
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 851cd71cf3bbdd91a522f123db238205ca21875c7d1c9bf1eefe4850399ffb5d
|
4
|
+
data.tar.gz: 4ec2ca689f05d37ccbc1cd3962edae53dfcda79c0a58facd9a2f228af5df914c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d595d36833a70860c7c997fff4cc55ce682dd3b3b9c4fc5e7b36067cbb73a665e65d473097993963f8409eb5a06a25b5b834de9dff6f42ad8e244972ab0ac4e
|
7
|
+
data.tar.gz: d136cd117711e3a103480b7cae52661e8ad3beef5023552f3bbaa8235e8f795410e13a1955290c7313fb6f7fced1485b275b8430a0d5c81297e11e1ff911f458
|
data/Puppetfile
CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
|
|
6
6
|
|
7
7
|
# Core modules used by 'apply'
|
8
8
|
mod 'puppetlabs-service', '1.3.0'
|
9
|
-
mod 'puppetlabs-puppet_agent', '
|
9
|
+
mod 'puppetlabs-puppet_agent', '4.1.1'
|
10
10
|
mod 'puppetlabs-facts', '1.0.0'
|
11
11
|
|
12
12
|
# Core types and providers for Puppet 6
|
@@ -9,11 +9,12 @@ Puppet::DataTypes.create_type('Result') do
|
|
9
9
|
functions => {
|
10
10
|
error => Callable[[], Optional[Error]],
|
11
11
|
message => Callable[[], Optional[String]],
|
12
|
+
sensitive => Callable[[], Optional[Sensitive[Data]]],
|
12
13
|
action => Callable[[], String],
|
13
14
|
status => Callable[[], String],
|
14
15
|
to_data => Callable[[], Hash],
|
15
16
|
ok => Callable[[], Boolean],
|
16
|
-
'[]' => Callable[[String[1]], Data]
|
17
|
+
'[]' => Callable[[String[1]], Variant[Data, Sensitive[Data]]]
|
17
18
|
}
|
18
19
|
PUPPET
|
19
20
|
|
@@ -96,7 +96,7 @@ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFun
|
|
96
96
|
|
97
97
|
# Paths expand relative to the default downloads directory for the project
|
98
98
|
# e.g. ~/.puppetlabs/bolt/downloads/
|
99
|
-
destination = Puppet.lookup(:
|
99
|
+
destination = Puppet.lookup(:bolt_project).downloads + destination
|
100
100
|
|
101
101
|
# If the destination directory already exists, delete any existing contents
|
102
102
|
if Dir.exist?(destination)
|
@@ -25,7 +25,7 @@ Puppet::Functions.create_function(:'dir::children', Puppet::Functions::InternalF
|
|
25
25
|
full_mod_path = File.join(mod_path, subpath || '') if mod_path
|
26
26
|
|
27
27
|
# Expand relative to the project directory if path is relative
|
28
|
-
project = Puppet.lookup(:
|
28
|
+
project = Puppet.lookup(:bolt_project)
|
29
29
|
pathname = Pathname.new(dirname)
|
30
30
|
full_dir = pathname.absolute? ? dirname : File.expand_path(File.join(project.path, dirname))
|
31
31
|
|
data/exe/bolt
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
TOPIC
|
2
|
+
inventory
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
The inventory describes the targets that you run Bolt commands on, along
|
6
|
+
with any data and configuration for the targets. Targets in an inventory can
|
7
|
+
belong to one or more groups, allowing you to share data and configuration
|
8
|
+
across multiple targets and to specify multiple targets for your Bolt
|
9
|
+
commands without the need to list each target individually.
|
10
|
+
|
11
|
+
In most cases, Bolt loads the inventory from an inventory file in your Bolt
|
12
|
+
project. The inventory file is a YAML file named 'inventory.yaml'. Because
|
13
|
+
Bolt loads the inventory file from a Bolt project, you must have an existing
|
14
|
+
project configuration file named 'bolt-project.yaml' alongside the inventory
|
15
|
+
file.
|
16
|
+
|
17
|
+
DOCUMENTATION
|
18
|
+
https://pup.pt/bolt-inventory
|
19
|
+
https://pup.pt/bolt-inventory-reference
|
data/guides/project.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
TOPIC
|
2
|
+
project
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
A Bolt project is a directory that serves as the launching point for Bolt
|
6
|
+
and allows you to create a shareable orchestration application. Projects
|
7
|
+
typically include a project configuration file, an inventory file, and any
|
8
|
+
content you use in your project workflow, such as tasks and plans.
|
9
|
+
|
10
|
+
When you run Bolt, it runs in the context of a project. If the directory you
|
11
|
+
run Bolt from is not a project, Bolt attempts to find a project by
|
12
|
+
traversing the parent directories. If Bolt is unable to find a project, it
|
13
|
+
runs from the default project, located at '~/.puppetlabs/bolt'.
|
14
|
+
|
15
|
+
A directory is only considered a Bolt project when it has a project
|
16
|
+
configuration file named 'bolt-project.yaml'. Bolt doesn't load project data
|
17
|
+
and content, including inventory files, unless the data and content are part
|
18
|
+
of a project.
|
19
|
+
|
20
|
+
DOCUMENTATION
|
21
|
+
https://pup.pt/bolt-projects
|
22
|
+
https://pup.pt/bolt-project-reference
|
data/lib/bolt/analytics.rb
CHANGED
@@ -27,7 +27,7 @@ module Bolt
|
|
27
27
|
}.freeze
|
28
28
|
|
29
29
|
def self.build_client
|
30
|
-
logger =
|
30
|
+
logger = Bolt::Logger.logger(self)
|
31
31
|
begin
|
32
32
|
config_file = config_path(logger)
|
33
33
|
config = load_config(config_file, logger)
|
@@ -93,6 +93,10 @@ module Bolt
|
|
93
93
|
def self.write_config(filename, config)
|
94
94
|
FileUtils.mkdir_p(File.dirname(filename))
|
95
95
|
File.write(filename, config.to_yaml)
|
96
|
+
rescue StandardError => e
|
97
|
+
Bolt::Logger.warn_once('unwriteable_file', "Could not write analytics configuration to #{filename}.")
|
98
|
+
# This will get caught by build_client and create a NoopClient
|
99
|
+
raise e
|
96
100
|
end
|
97
101
|
|
98
102
|
class Client
|
@@ -106,7 +110,7 @@ module Bolt
|
|
106
110
|
require 'httpclient'
|
107
111
|
require 'locale'
|
108
112
|
|
109
|
-
@logger =
|
113
|
+
@logger = Bolt::Logger.logger(self)
|
110
114
|
@http = HTTPClient.new
|
111
115
|
@user_id = user_id
|
112
116
|
@executor = Concurrent.global_io_executor
|
@@ -161,9 +165,9 @@ module Bolt
|
|
161
165
|
# Handle analytics submission in the background to avoid blocking the
|
162
166
|
# app or polluting the log with errors
|
163
167
|
Concurrent::Future.execute(executor: @executor) do
|
164
|
-
@logger.
|
168
|
+
@logger.trace "Submitting analytics: #{JSON.pretty_generate(params)}"
|
165
169
|
@http.post(TRACKING_URL, params)
|
166
|
-
@logger.
|
170
|
+
@logger.trace "Completed analytics submission"
|
167
171
|
end
|
168
172
|
end
|
169
173
|
|
@@ -210,18 +214,18 @@ module Bolt
|
|
210
214
|
attr_accessor :bundled_content
|
211
215
|
|
212
216
|
def initialize
|
213
|
-
@logger =
|
217
|
+
@logger = Bolt::Logger.logger(self)
|
214
218
|
@bundled_content = []
|
215
219
|
end
|
216
220
|
|
217
221
|
def screen_view(screen, **_kwargs)
|
218
|
-
@logger.
|
222
|
+
@logger.trace "Skipping submission of '#{screen}' screenview because analytics is disabled"
|
219
223
|
end
|
220
224
|
|
221
225
|
def report_bundled_content(mode, name); end
|
222
226
|
|
223
227
|
def event(category, action, **_kwargs)
|
224
|
-
@logger.
|
228
|
+
@logger.trace "Skipping submission of '#{category} #{action}' event because analytics is disabled"
|
225
229
|
end
|
226
230
|
|
227
231
|
def finish; end
|
data/lib/bolt/applicator.rb
CHANGED
@@ -27,8 +27,8 @@ module Bolt
|
|
27
27
|
@hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
|
28
28
|
@apply_settings = apply_settings || {}
|
29
29
|
|
30
|
-
@pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
|
31
|
-
@logger =
|
30
|
+
@pool = Concurrent::ThreadPoolExecutor.new(name: 'apply', max_threads: max_compiles)
|
31
|
+
@logger = Bolt::Logger.logger(self)
|
32
32
|
end
|
33
33
|
|
34
34
|
private def libexec
|
@@ -75,23 +75,24 @@ module Bolt
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
def compile(target,
|
78
|
+
def compile(target, scope)
|
79
79
|
# This simplified Puppet node object is what .local uses to determine the
|
80
80
|
# certname of the target
|
81
81
|
node = Puppet::Node.from_data_hash('name' => target.name,
|
82
82
|
'parameters' => { 'clientcert' => target.name })
|
83
83
|
trusted = Puppet::Context::TrustedInformation.local(node)
|
84
|
-
|
84
|
+
target_data = {
|
85
85
|
name: target.name,
|
86
86
|
facts: @inventory.facts(target).merge('bolt' => true),
|
87
87
|
variables: @inventory.vars(target),
|
88
88
|
trusted: trusted.to_h
|
89
89
|
}
|
90
|
+
catalog_request = scope.merge(target: target_data)
|
90
91
|
|
91
92
|
bolt_catalog_exe = File.join(libexec, 'bolt_catalog')
|
92
93
|
old_path = ENV['PATH']
|
93
94
|
ENV['PATH'] = "#{RbConfig::CONFIG['bindir']}#{File::PATH_SEPARATOR}#{old_path}"
|
94
|
-
out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data:
|
95
|
+
out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_request.to_json)
|
95
96
|
ENV['PATH'] = old_path
|
96
97
|
|
97
98
|
# If bolt_catalog does not return valid JSON, we should print stderr to
|
@@ -183,17 +184,16 @@ module Bolt
|
|
183
184
|
type_by_reference: true,
|
184
185
|
local_reference: true)
|
185
186
|
|
186
|
-
bolt_project = @project if @project&.name
|
187
187
|
scope = {
|
188
188
|
code_ast: ast,
|
189
189
|
modulepath: @modulepath,
|
190
|
-
project:
|
190
|
+
project: @project.to_h,
|
191
191
|
pdb_config: @pdb_client.config.to_hash,
|
192
192
|
hiera_config: @hiera_config,
|
193
193
|
plan_vars: plan_vars,
|
194
194
|
# This data isn't available on the target config hash
|
195
195
|
config: @inventory.transport_data_get
|
196
|
-
}
|
196
|
+
}.freeze
|
197
197
|
description = options[:description] || 'apply catalog'
|
198
198
|
|
199
199
|
required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
|
@@ -217,6 +217,7 @@ module Bolt
|
|
217
217
|
r = @executor.log_action(description, targets) do
|
218
218
|
futures = targets.map do |target|
|
219
219
|
Concurrent::Future.execute(executor: @pool) do
|
220
|
+
Thread.current[:name] ||= Thread.current.name
|
220
221
|
@executor.with_node_logging("Compiling manifest block", [target]) do
|
221
222
|
compile(target, scope)
|
222
223
|
end
|
@@ -300,7 +301,7 @@ module Bolt
|
|
300
301
|
|
301
302
|
files.each do |file|
|
302
303
|
tar_path = Pathname.new(file).relative_path_from(parent)
|
303
|
-
@logger.
|
304
|
+
@logger.trace("Packing plugin #{file} to #{tar_path}")
|
304
305
|
stat = File.stat(file)
|
305
306
|
content = File.binread(file)
|
306
307
|
output.tar.add_file_simple(
|
@@ -314,7 +315,7 @@ module Bolt
|
|
314
315
|
end
|
315
316
|
|
316
317
|
duration = Time.now - start_time
|
317
|
-
@logger.
|
318
|
+
@logger.trace("Packed plugins in #{duration * 1000} ms")
|
318
319
|
|
319
320
|
output.close
|
320
321
|
Base64.encode64(sio.string)
|
@@ -61,6 +61,18 @@ module Bolt
|
|
61
61
|
{ flags: OPTIONS[:global],
|
62
62
|
banner: GROUP_HELP }
|
63
63
|
end
|
64
|
+
when 'guide'
|
65
|
+
{ flags: OPTIONS[:global] + %w[format],
|
66
|
+
banner: GUIDE_HELP }
|
67
|
+
when 'module'
|
68
|
+
case action
|
69
|
+
when 'install'
|
70
|
+
{ flags: OPTIONS[:global] + %w[configfile force project],
|
71
|
+
banner: MODULE_INSTALL_HELP }
|
72
|
+
else
|
73
|
+
{ flags: OPTIONS[:global],
|
74
|
+
banner: MODULE_HELP }
|
75
|
+
end
|
64
76
|
when 'plan'
|
65
77
|
case action
|
66
78
|
when 'convert'
|
@@ -85,7 +97,7 @@ module Bolt
|
|
85
97
|
{ flags: OPTIONS[:global] + %w[modules],
|
86
98
|
banner: PROJECT_INIT_HELP }
|
87
99
|
when 'migrate'
|
88
|
-
{ flags: OPTIONS[:global] + %w[inventoryfile
|
100
|
+
{ flags: OPTIONS[:global] + %w[inventoryfile project configfile],
|
89
101
|
banner: PROJECT_MIGRATE_HELP }
|
90
102
|
else
|
91
103
|
{ flags: OPTIONS[:global],
|
@@ -164,6 +176,7 @@ module Bolt
|
|
164
176
|
command Run a command remotely
|
165
177
|
file Copy files between the controller and targets
|
166
178
|
group Show the list of groups in the inventory
|
179
|
+
guide View guides for Bolt concepts and features
|
167
180
|
inventory Show the list of targets an action would run on
|
168
181
|
plan Convert, create, show, and run Bolt plans
|
169
182
|
project Create and migrate Bolt projects
|
@@ -171,6 +184,9 @@ module Bolt
|
|
171
184
|
script Upload a local script and run it remotely
|
172
185
|
secret Create encryption keys and encrypt and decrypt values
|
173
186
|
task Show and run Bolt tasks
|
187
|
+
|
188
|
+
GUIDES
|
189
|
+
For a list of guides on Bolt's concepts and features, run 'bolt guide'.
|
174
190
|
HELP
|
175
191
|
|
176
192
|
APPLY_HELP = <<~HELP
|
@@ -289,6 +305,26 @@ module Bolt
|
|
289
305
|
Show the list of groups in the inventory.
|
290
306
|
HELP
|
291
307
|
|
308
|
+
GUIDE_HELP = <<~HELP
|
309
|
+
NAME
|
310
|
+
guide
|
311
|
+
|
312
|
+
USAGE
|
313
|
+
bolt guide [topic] [options]
|
314
|
+
|
315
|
+
DESCRIPTION
|
316
|
+
View guides for Bolt's concepts and features.
|
317
|
+
|
318
|
+
Omitting a topic will display a list of available guides,
|
319
|
+
while providing a topic will display the relevant guide.
|
320
|
+
|
321
|
+
EXAMPLES
|
322
|
+
View a list of available guides
|
323
|
+
bolt guide
|
324
|
+
View the 'project' guide page
|
325
|
+
bolt guide project
|
326
|
+
HELP
|
327
|
+
|
292
328
|
INVENTORY_HELP = <<~HELP
|
293
329
|
NAME
|
294
330
|
inventory
|
@@ -314,6 +350,35 @@ module Bolt
|
|
314
350
|
Show the list of targets an action would run on.
|
315
351
|
HELP
|
316
352
|
|
353
|
+
MODULE_HELP = <<~HELP
|
354
|
+
NAME
|
355
|
+
module
|
356
|
+
|
357
|
+
USAGE
|
358
|
+
bolt module <action> [options]
|
359
|
+
|
360
|
+
DESCRIPTION
|
361
|
+
Install the project's modules
|
362
|
+
|
363
|
+
ACTIONS
|
364
|
+
install Install the project's modules
|
365
|
+
HELP
|
366
|
+
|
367
|
+
MODULE_INSTALL_HELP = <<~HELP
|
368
|
+
NAME
|
369
|
+
install
|
370
|
+
|
371
|
+
USAGE
|
372
|
+
bolt module install [options]
|
373
|
+
|
374
|
+
DESCRIPTION
|
375
|
+
Install the project's modules.
|
376
|
+
|
377
|
+
Module declarations are loaded from the project's configuration
|
378
|
+
file. Bolt will automatically resolve all module dependencies,
|
379
|
+
generate a Puppetfile, and install the modules.
|
380
|
+
HELP
|
381
|
+
|
317
382
|
PLAN_HELP = <<~HELP
|
318
383
|
NAME
|
319
384
|
plan
|
@@ -339,11 +404,11 @@ module Bolt
|
|
339
404
|
bolt plan convert <path> [options]
|
340
405
|
|
341
406
|
DESCRIPTION
|
342
|
-
Convert a YAML plan to a
|
407
|
+
Convert a YAML plan to a Puppet language plan and print the converted plan to stdout.
|
343
408
|
|
344
409
|
Converting a YAML plan may result in a plan that is syntactically
|
345
410
|
correct but has different behavior. Always verify a converted plan's
|
346
|
-
functionality.
|
411
|
+
functionality. Note that the converted plan is not written to a file.
|
347
412
|
|
348
413
|
EXAMPLES
|
349
414
|
bolt plan convert path/to/plan/myplan.yaml
|
@@ -394,9 +459,9 @@ module Bolt
|
|
394
459
|
the plan, including a list of available parameters.
|
395
460
|
|
396
461
|
EXAMPLES
|
397
|
-
Display a list of available
|
462
|
+
Display a list of available plans
|
398
463
|
bolt plan show
|
399
|
-
Display documentation for the
|
464
|
+
Display documentation for the aggregate::count plan
|
400
465
|
bolt plan show aggregate::count
|
401
466
|
HELP
|
402
467
|
|
@@ -444,10 +509,7 @@ module Bolt
|
|
444
509
|
bolt project migrate [options]
|
445
510
|
|
446
511
|
DESCRIPTION
|
447
|
-
Migrate a Bolt project to the latest version.
|
448
|
-
|
449
|
-
Loads a Bolt project's inventory file and migrates it to the latest version. The
|
450
|
-
inventory file is modified in place and will not preserve comments or formatting.
|
512
|
+
Migrate a Bolt project to use current best practices and the latest version of configuration files.
|
451
513
|
HELP
|
452
514
|
|
453
515
|
PUPPETFILE_HELP = <<~HELP
|
@@ -804,7 +866,7 @@ module Bolt
|
|
804
866
|
"This option is experimental.") do |exec|
|
805
867
|
@options[:'copy-command'] = exec
|
806
868
|
end
|
807
|
-
define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
|
869
|
+
define('--connect-timeout TIMEOUT', Integer, 'Connection timeout in seconds (defaults vary)') do |timeout|
|
808
870
|
@options[:'connect-timeout'] = timeout
|
809
871
|
end
|
810
872
|
define('--[no-]tty', 'Request a pseudo TTY on targets that support it') do |tty|
|
@@ -840,9 +902,9 @@ module Bolt
|
|
840
902
|
define('--modules MODULES',
|
841
903
|
'A comma-separated list of modules to install from the Puppet Forge',
|
842
904
|
'when initializing a project. Resolves and installs all dependencies.') do |modules|
|
843
|
-
@options[:modules] = modules.split(',')
|
905
|
+
@options[:modules] = modules.split(',').map { |mod| { 'name' => mod } }
|
844
906
|
end
|
845
|
-
define('--force', '
|
907
|
+
define('--force', 'Force a destructive action') do |_force|
|
846
908
|
@options[:force] = true
|
847
909
|
end
|
848
910
|
|
@@ -863,7 +925,7 @@ module Bolt
|
|
863
925
|
end
|
864
926
|
define('--log-level LEVEL',
|
865
927
|
"Set the log level for the console. Available options are",
|
866
|
-
"debug, info,
|
928
|
+
"trace, debug, info, warn, error, fatal, any.") do |level|
|
867
929
|
@options[:log] = { 'console' => { 'level' => level } }
|
868
930
|
end
|
869
931
|
define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
|
data/lib/bolt/catalog.rb
CHANGED
@@ -57,8 +57,10 @@ module Bolt
|
|
57
57
|
|
58
58
|
def compile_catalog(request)
|
59
59
|
pdb_client = Bolt::PuppetDB::Client.new(Bolt::PuppetDB::Config.new(request['pdb_config']))
|
60
|
-
project = request['project']
|
61
|
-
bolt_project = Struct.new(:name, :path).new(project['name'],
|
60
|
+
project = request['project']
|
61
|
+
bolt_project = Struct.new(:name, :path, :load_as_module?).new(project['name'],
|
62
|
+
project['path'],
|
63
|
+
project['load_as_module?'])
|
62
64
|
inv = Bolt::ApplyInventory.new(request['config'])
|
63
65
|
puppet_overrides = {
|
64
66
|
bolt_pdb_client: pdb_client,
|
data/lib/bolt/cli.rb
CHANGED
@@ -20,6 +20,7 @@ require 'bolt/logger'
|
|
20
20
|
require 'bolt/outputter'
|
21
21
|
require 'bolt/puppetdb'
|
22
22
|
require 'bolt/plugin'
|
23
|
+
require 'bolt/project_migrate'
|
23
24
|
require 'bolt/pal'
|
24
25
|
require 'bolt/target'
|
25
26
|
require 'bolt/version'
|
@@ -38,13 +39,15 @@ module Bolt
|
|
38
39
|
'inventory' => %w[show],
|
39
40
|
'group' => %w[show],
|
40
41
|
'project' => %w[init migrate],
|
41
|
-
'apply' => %w[]
|
42
|
+
'apply' => %w[],
|
43
|
+
'guide' => %w[],
|
44
|
+
'module' => %w[install] }.freeze
|
42
45
|
|
43
46
|
attr_reader :config, :options
|
44
47
|
|
45
48
|
def initialize(argv)
|
46
49
|
Bolt::Logger.initialize_logging
|
47
|
-
@logger =
|
50
|
+
@logger = Bolt::Logger.logger(self)
|
48
51
|
@argv = argv
|
49
52
|
@options = {}
|
50
53
|
end
|
@@ -77,7 +80,7 @@ module Bolt
|
|
77
80
|
|
78
81
|
# Wrapper method that is called by the Bolt executable. Parses the command and
|
79
82
|
# then loads the project and config. Once config is loaded, it completes the
|
80
|
-
# setup process by configuring Bolt and
|
83
|
+
# setup process by configuring Bolt and logging messages.
|
81
84
|
#
|
82
85
|
# This separation is needed since the Bolt::Outputter class that normally handles
|
83
86
|
# printing errors relies on config being loaded. All setup that happens before
|
@@ -165,20 +168,17 @@ module Bolt
|
|
165
168
|
raise e
|
166
169
|
end
|
167
170
|
|
168
|
-
# Completes the setup process by configuring Bolt and
|
171
|
+
# Completes the setup process by configuring Bolt and log messages
|
169
172
|
def finalize_setup
|
170
173
|
Bolt::Logger.configure(config.log, config.color)
|
171
174
|
Bolt::Logger.analytics = analytics
|
172
175
|
|
173
|
-
# Logger must be configured before checking path case and project file, otherwise
|
176
|
+
# Logger must be configured before checking path case and project file, otherwise logs will not display
|
174
177
|
config.check_path_case('modulepath', config.modulepath)
|
175
178
|
config.project.check_deprecated_file
|
176
179
|
|
177
|
-
# Log
|
178
|
-
|
179
|
-
|
180
|
-
# Display warnings created during parser and config initialization
|
181
|
-
config.warnings.each { |warning| @logger.warn(warning[:msg]) }
|
180
|
+
# Log messages created during parser and config initialization
|
181
|
+
config.logs.each { |log| @logger.send(log.keys[0], log.values[0]) }
|
182
182
|
@parser_deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
|
183
183
|
config.deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
|
184
184
|
|
@@ -211,10 +211,14 @@ module Bolt
|
|
211
211
|
end
|
212
212
|
|
213
213
|
def validate(options)
|
214
|
-
unless
|
214
|
+
# Disables the 'module' subcommand unless the module feature flag is set.
|
215
|
+
commands = COMMANDS.dup
|
216
|
+
commands.delete('module') unless ENV['BOLT_MODULE_FEATURE']
|
217
|
+
|
218
|
+
unless commands.include?(options[:subcommand])
|
215
219
|
raise Bolt::CLIError,
|
216
220
|
"Expected subcommand '#{options[:subcommand]}' to be one of " \
|
217
|
-
"#{
|
221
|
+
"#{commands.keys.join(', ')}"
|
218
222
|
end
|
219
223
|
|
220
224
|
actions = COMMANDS[options[:subcommand]]
|
@@ -354,7 +358,7 @@ module Bolt
|
|
354
358
|
# Initialize inventory and targets. Errors here are better to catch early.
|
355
359
|
# options[:target_args] will contain a string/array version of the targetting options this is passed to plans
|
356
360
|
# options[:targets] will contain a resolved set of Target objects
|
357
|
-
unless %w[project puppetfile secret].include?(options[:subcommand]) ||
|
361
|
+
unless %w[guide module project puppetfile secret].include?(options[:subcommand]) ||
|
358
362
|
%w[convert new show].include?(options[:action])
|
359
363
|
update_targets(options)
|
360
364
|
end
|
@@ -411,7 +415,7 @@ module Bolt
|
|
411
415
|
list_modules
|
412
416
|
return 0
|
413
417
|
when 'convert'
|
414
|
-
convert_plan(options[:object])
|
418
|
+
pal.convert_plan(options[:object])
|
415
419
|
return 0
|
416
420
|
end
|
417
421
|
|
@@ -422,12 +426,20 @@ module Bolt
|
|
422
426
|
end
|
423
427
|
|
424
428
|
case options[:subcommand]
|
429
|
+
when 'guide'
|
430
|
+
code = if options[:object]
|
431
|
+
show_guide(options[:object])
|
432
|
+
else
|
433
|
+
list_topics
|
434
|
+
end
|
425
435
|
when 'project'
|
426
436
|
case options[:action]
|
427
437
|
when 'init'
|
428
438
|
code = initialize_project
|
429
439
|
when 'migrate'
|
430
|
-
|
440
|
+
inv = config.inventoryfile
|
441
|
+
path = config.project.path
|
442
|
+
code = Bolt::ProjectMigrate.new(path, outputter, inv).migrate_project
|
431
443
|
end
|
432
444
|
when 'plan'
|
433
445
|
case options[:action]
|
@@ -436,12 +448,17 @@ module Bolt
|
|
436
448
|
when 'run'
|
437
449
|
code = run_plan(options[:object], options[:task_options], options[:target_args], options)
|
438
450
|
end
|
451
|
+
when 'module'
|
452
|
+
case options[:action]
|
453
|
+
when 'install'
|
454
|
+
code = install_project_modules
|
455
|
+
end
|
439
456
|
when 'puppetfile'
|
440
457
|
case options[:action]
|
441
458
|
when 'generate-types'
|
442
459
|
code = generate_types
|
443
460
|
when 'install'
|
444
|
-
code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath)
|
461
|
+
code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath.first)
|
445
462
|
end
|
446
463
|
when 'secret'
|
447
464
|
code = Bolt::Secret.execute(plugins, outputter, options)
|
@@ -791,36 +808,38 @@ module Bolt
|
|
791
808
|
old_config = project + 'bolt.yaml'
|
792
809
|
config = project + 'bolt-project.yaml'
|
793
810
|
puppetfile = project + 'Puppetfile'
|
794
|
-
|
811
|
+
moduledir = project + 'modules'
|
795
812
|
|
796
|
-
#
|
797
|
-
#
|
798
|
-
#
|
799
|
-
#
|
800
|
-
#
|
813
|
+
# Warn the user if the project directory already exists. We don't error
|
814
|
+
# here since users might not have installed any modules yet. If both
|
815
|
+
# bolt.yaml and bolt-project.yaml exist, this will just warn about
|
816
|
+
# bolt-project.yaml and subsequent Bolt actions will warn about both files
|
817
|
+
# existing.
|
818
|
+
if config.exist?
|
819
|
+
@logger.warn "Found existing project directory at #{project}. Skipping file creation."
|
820
|
+
elsif old_config.exist?
|
821
|
+
@logger.warn "Found existing #{old_config.basename} at #{project}. "\
|
822
|
+
"#{old_config.basename} is deprecated, please rename to #{config.basename}."
|
823
|
+
end
|
824
|
+
|
825
|
+
# If modules were specified, first check if there is already a Puppetfile
|
826
|
+
# at the project directory, erroring if there is. If there is no
|
827
|
+
# Puppetfile, install the specified modules. The module installer will
|
828
|
+
# resolve dependencies, generate a Puppetfile, and install the modules.
|
801
829
|
if options[:modules]
|
802
830
|
if puppetfile.exist?
|
803
831
|
raise Bolt::CLIError,
|
804
|
-
"Found existing Puppetfile at #{puppetfile}, unable to initialize
|
805
|
-
"
|
806
|
-
else
|
807
|
-
puppetfile_specs = resolve_puppetfile_specs
|
832
|
+
"Found existing Puppetfile at #{puppetfile}, unable to initialize "\
|
833
|
+
"project with modules."
|
808
834
|
end
|
835
|
+
|
836
|
+
install_modules(puppetfile, {}, moduledir, options[:modules])
|
809
837
|
end
|
810
838
|
|
811
|
-
#
|
812
|
-
#
|
813
|
-
#
|
814
|
-
|
815
|
-
# both files existing
|
816
|
-
if config.exist?
|
817
|
-
@logger.warn "Found existing project directory at #{project}. Skipping file creation."
|
818
|
-
# This won't get called if bolt-project.yaml exists
|
819
|
-
elsif old_config.exist?
|
820
|
-
@logger.warn "Found existing #{old_config.basename} at #{project}. "\
|
821
|
-
"#{old_config.basename} is deprecated, please rename to #{config.basename}."
|
822
|
-
# Bless the project directory as a...wait for it...project
|
823
|
-
else
|
839
|
+
# If either bolt.yaml or bolt-project.yaml exist, the user has already
|
840
|
+
# been warned and we can just finish project creation. Otherwise, create a
|
841
|
+
# bolt-project.yaml with the project name in it.
|
842
|
+
unless config.exist? || old_config.exist?
|
824
843
|
begin
|
825
844
|
content = { 'name' => name }
|
826
845
|
File.write(config.to_path, content.to_yaml)
|
@@ -830,152 +849,82 @@ module Bolt
|
|
830
849
|
end
|
831
850
|
end
|
832
851
|
|
833
|
-
# Write the generated Puppetfile to the fancy new project
|
834
|
-
if puppetfile_specs
|
835
|
-
File.write(puppetfile, puppetfile_specs.join("\n"))
|
836
|
-
outputter.print_message "Successfully created Puppetfile at #{puppetfile}"
|
837
|
-
# Install the modules from our shiny new Puppetfile
|
838
|
-
if install_puppetfile({}, puppetfile, modulepath)
|
839
|
-
outputter.print_message "Successfully installed #{options[:modules].join(', ')}"
|
840
|
-
else
|
841
|
-
raise Bolt::CLIError, "Could not install #{options[:modules].join(', ')}"
|
842
|
-
end
|
843
|
-
end
|
844
|
-
|
845
852
|
0
|
846
853
|
end
|
847
854
|
|
848
|
-
#
|
849
|
-
#
|
850
|
-
def
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
options[:modules].each do |mod_name|
|
856
|
-
model.add_module(
|
857
|
-
PuppetfileResolver::Puppetfile::ForgeModule.new(mod_name).tap { |mod| mod.version = :latest }
|
858
|
-
)
|
859
|
-
end
|
860
|
-
|
861
|
-
# Make sure the Puppetfile model is valid
|
862
|
-
unless model.valid?
|
863
|
-
raise Bolt::ValidationError,
|
864
|
-
"Unable to resolve dependencies for #{options[:modules].join(', ')}"
|
855
|
+
# Installs modules declared in the project configuration file.
|
856
|
+
#
|
857
|
+
def install_project_modules
|
858
|
+
if config.project.modules.nil?
|
859
|
+
outputter.print_message "Project configuration file '#{config.project.project_file}' "\
|
860
|
+
"does not specify any module dependencies. Nothing to do."
|
861
|
+
return 0
|
865
862
|
end
|
866
863
|
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
cache: nil,
|
873
|
-
ui: nil,
|
874
|
-
module_paths: [],
|
875
|
-
allow_missing_modules: true
|
864
|
+
install_modules(
|
865
|
+
config.puppetfile,
|
866
|
+
config.puppetfile_config,
|
867
|
+
config.project.path + '.modules',
|
868
|
+
config.project.modules
|
876
869
|
)
|
870
|
+
end
|
877
871
|
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
872
|
+
# Installs modules declared in the project configuration file.
|
873
|
+
#
|
874
|
+
def install_modules(puppetfile_path, config, moduledir, modules)
|
875
|
+
require 'bolt/puppetfile'
|
876
|
+
require 'bolt/puppetfile/installer'
|
877
|
+
|
878
|
+
puppetfile = Bolt::Puppetfile.new(modules)
|
879
|
+
|
880
|
+
# If the Puppetfile exists, check if it includes specs for each declared
|
881
|
+
# module, erroring if there are any missing. Otherwise, resolve the
|
882
|
+
# module dependencies and write a new Puppetfile. Users can forcibly
|
883
|
+
# overwrite an existing Puppetfile with the '--force' option.
|
884
|
+
if puppetfile_path.exist? && !options[:force]
|
885
|
+
outputter.print_message "Parsing existing Puppetfile at #{puppetfile_path}"
|
886
|
+
existing = Bolt::Puppetfile.parse(puppetfile_path)
|
887
|
+
|
888
|
+
unless existing.modules.superset? puppetfile.modules
|
889
|
+
missing_modules = puppetfile.modules - existing.modules
|
890
|
+
|
891
|
+
raise Bolt::Error.new(
|
892
|
+
"Puppetfile #{puppetfile_path} is missing specifications for modules: "\
|
893
|
+
"#{missing_modules.map(&:title).join(', ')}. This may not be a Puppetfile "\
|
894
|
+
"managed by Bolt. To forcibly overwrite the Puppetfile, run with the "\
|
895
|
+
"'--force' option.",
|
896
|
+
'bolt/missing-module-specs'
|
897
|
+
)
|
886
898
|
end
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
"Unknown module name#{plural} #{names.join(', ')}"
|
893
|
-
end
|
894
|
-
|
895
|
-
# Filter the dependency graph to only include module specifications
|
896
|
-
spec_graph = result.specifications.select do |_name, spec|
|
897
|
-
spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
|
898
|
-
end
|
899
|
-
|
900
|
-
# Map specification models to a Puppetfile specification
|
901
|
-
spec_graph.values.map do |spec|
|
902
|
-
"mod '#{spec.owner}-#{spec.name}', '#{spec.version}'"
|
899
|
+
else
|
900
|
+
outputter.print_message "Resolving module dependencies, this may take a moment"
|
901
|
+
puppetfile.resolve
|
902
|
+
outputter.print_message "Writing Puppetfile at #{puppetfile_path}"
|
903
|
+
puppetfile.write(puppetfile_path, force: true)
|
903
904
|
end
|
904
|
-
end
|
905
905
|
|
906
|
-
|
907
|
-
|
908
|
-
data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
|
906
|
+
outputter.print_message "Syncing modules from #{puppetfile_path} to #{moduledir}"
|
907
|
+
ok = Bolt::Puppetfile::Installer.new(config).install(puppetfile_path, moduledir)
|
909
908
|
|
910
|
-
|
911
|
-
|
912
|
-
migrated = migrate_group(data)
|
913
|
-
|
914
|
-
ok = File.write(inventory_file, data.to_yaml) if migrated
|
915
|
-
|
916
|
-
result = if migrated && ok
|
917
|
-
"Successfully migrated Bolt project to latest version."
|
918
|
-
elsif !migrated
|
919
|
-
"Bolt project already on latest version. Nothing to do."
|
920
|
-
else
|
921
|
-
"Could not migrate Bolt project to latest version."
|
922
|
-
end
|
923
|
-
outputter.print_message result
|
909
|
+
# Automatically generate types after installing modules.
|
910
|
+
pal.generate_types
|
924
911
|
|
912
|
+
outputter.print_puppetfile_result(ok, puppetfile_path, moduledir)
|
925
913
|
ok ? 0 : 1
|
926
914
|
end
|
927
915
|
|
928
|
-
#
|
929
|
-
#
|
930
|
-
|
931
|
-
|
932
|
-
migrated = false
|
933
|
-
if group.key?('nodes')
|
934
|
-
migrated = true
|
935
|
-
targets = group['nodes'].map do |target|
|
936
|
-
target['uri'] = target.delete('name') if target.is_a?(Hash)
|
937
|
-
target
|
938
|
-
end
|
939
|
-
group.delete('nodes')
|
940
|
-
group['targets'] = targets
|
941
|
-
end
|
942
|
-
(group['groups'] || []).each do |subgroup|
|
943
|
-
migrated_group = migrate_group(subgroup)
|
944
|
-
migrated ||= migrated_group
|
945
|
-
end
|
946
|
-
migrated
|
947
|
-
end
|
948
|
-
|
949
|
-
def install_puppetfile(config, puppetfile, modulepath)
|
950
|
-
require 'r10k/cli'
|
951
|
-
require 'bolt/r10k_log_proxy'
|
952
|
-
|
953
|
-
if puppetfile.exist?
|
954
|
-
moduledir = modulepath.first.to_s
|
955
|
-
r10k_opts = {
|
956
|
-
root: puppetfile.dirname.to_s,
|
957
|
-
puppetfile: puppetfile.to_s,
|
958
|
-
moduledir: moduledir
|
959
|
-
}
|
960
|
-
|
961
|
-
settings = R10K::Settings.global_settings.evaluate(config)
|
962
|
-
R10K::Initializers::GlobalInitializer.new(settings).call
|
963
|
-
install_action = R10K::Action::Puppetfile::Install.new(r10k_opts, nil)
|
916
|
+
# Loads a Puppetfile and installs its modules.
|
917
|
+
#
|
918
|
+
def install_puppetfile(config, puppetfile, moduledir)
|
919
|
+
require 'bolt/puppetfile/installer'
|
964
920
|
|
965
|
-
|
966
|
-
R10K::Logging.instance_variable_set(:@outputter, Bolt::R10KLogProxy.new)
|
921
|
+
ok = Bolt::Puppetfile::Installer.new(config).install(puppetfile, moduledir)
|
967
922
|
|
968
|
-
|
969
|
-
|
970
|
-
# Automatically generate types after installing modules
|
971
|
-
pal.generate_types
|
923
|
+
# Automatically generate types after installing modules.
|
924
|
+
pal.generate_types
|
972
925
|
|
973
|
-
|
974
|
-
|
975
|
-
raise Bolt::FileError.new("Could not find a Puppetfile at #{puppetfile}", puppetfile)
|
976
|
-
end
|
977
|
-
rescue R10K::Error => e
|
978
|
-
raise PuppetfileError, e
|
926
|
+
outputter.print_puppetfile_result(ok, puppetfile, moduledir)
|
927
|
+
ok ? 0 : 1
|
979
928
|
end
|
980
929
|
|
981
930
|
def pal
|
@@ -988,8 +937,46 @@ module Bolt
|
|
988
937
|
config.project)
|
989
938
|
end
|
990
939
|
|
991
|
-
|
992
|
-
|
940
|
+
# Collects the list of Bolt guides and maps them to their topics.
|
941
|
+
def guides
|
942
|
+
@guides ||= begin
|
943
|
+
root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
|
944
|
+
files = Dir.children(root_path).sort
|
945
|
+
|
946
|
+
files.each_with_object({}) do |file, guides|
|
947
|
+
next if file !~ /\.txt\z/
|
948
|
+
topic = File.basename(file, '.txt')
|
949
|
+
guides[topic] = File.join(root_path, file)
|
950
|
+
end
|
951
|
+
rescue SystemCallError => e
|
952
|
+
raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
956
|
+
# Display the list of available Bolt guides.
|
957
|
+
def list_topics
|
958
|
+
outputter.print_topics(guides.keys)
|
959
|
+
0
|
960
|
+
end
|
961
|
+
|
962
|
+
# Display a specific Bolt guide.
|
963
|
+
def show_guide(topic)
|
964
|
+
if guides[topic]
|
965
|
+
analytics.event('Guide', 'known_topic', label: topic)
|
966
|
+
|
967
|
+
begin
|
968
|
+
guide = File.read(guides[topic])
|
969
|
+
rescue SystemCallError => e
|
970
|
+
raise Bolt::FileError("#{e.message}: unable to load guide page", filepath)
|
971
|
+
end
|
972
|
+
|
973
|
+
outputter.print_guide(guide, topic)
|
974
|
+
else
|
975
|
+
analytics.event('Guide', 'unknown_topic', label: topic)
|
976
|
+
outputter.print_message("Did not find guide for topic '#{topic}'.\n\n")
|
977
|
+
list_topics
|
978
|
+
end
|
979
|
+
0
|
993
980
|
end
|
994
981
|
|
995
982
|
def validate_file(type, path, allow_dir = false)
|
@@ -1052,13 +1039,6 @@ module Bolt
|
|
1052
1039
|
content
|
1053
1040
|
end
|
1054
1041
|
|
1055
|
-
def config_loaded
|
1056
|
-
msg = <<~MSG.chomp
|
1057
|
-
Loaded configuration from: '#{config.config_files.join("', '")}'
|
1058
|
-
MSG
|
1059
|
-
@logger.debug(msg)
|
1060
|
-
end
|
1061
|
-
|
1062
1042
|
# Gem installs include the aggregate, canary, and puppetdb_fact modules, while
|
1063
1043
|
# package installs include modules listed in the Bolt repo Puppetfile
|
1064
1044
|
def incomplete_install?
|