bolt 2.28.0 → 2.33.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +15 -14
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
- data/guides/logging.txt +18 -0
- data/guides/module.txt +19 -0
- data/guides/modulepath.txt +25 -0
- data/lib/bolt/bolt_option_parser.rb +50 -28
- data/lib/bolt/catalog.rb +1 -1
- data/lib/bolt/cli.rb +159 -112
- data/lib/bolt/config.rb +13 -1
- data/lib/bolt/config/modulepath.rb +30 -0
- data/lib/bolt/config/options.rb +38 -9
- data/lib/bolt/config/transport/options.rb +2 -2
- data/lib/bolt/error.rb +4 -0
- data/lib/bolt/executor.rb +13 -13
- data/lib/bolt/inventory.rb +10 -9
- data/lib/bolt/logger.rb +26 -19
- data/lib/bolt/module_installer.rb +198 -0
- data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
- data/lib/bolt/module_installer/puppetfile.rb +117 -0
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
- data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
- data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
- data/lib/bolt/module_installer/resolver.rb +76 -0
- data/lib/bolt/module_installer/specs.rb +93 -0
- data/lib/bolt/module_installer/specs/forge_spec.rb +84 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +178 -0
- data/lib/bolt/outputter.rb +2 -45
- data/lib/bolt/outputter/human.rb +78 -18
- data/lib/bolt/outputter/json.rb +22 -7
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/pal.rb +55 -45
- data/lib/bolt/pal/yaml_plan.rb +4 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
- data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
- data/lib/bolt/plugin.rb +1 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/project.rb +32 -22
- data/lib/bolt/project_migrator.rb +80 -0
- data/lib/bolt/project_migrator/base.rb +39 -0
- data/lib/bolt/project_migrator/config.rb +67 -0
- data/lib/bolt/project_migrator/inventory.rb +67 -0
- data/lib/bolt/project_migrator/modules.rb +200 -0
- data/lib/bolt/result.rb +23 -11
- data/lib/bolt/shell/bash.rb +15 -9
- data/lib/bolt/shell/powershell.rb +11 -6
- data/lib/bolt/transport/base.rb +18 -18
- data/lib/bolt/transport/docker.rb +23 -6
- data/lib/bolt/transport/orch.rb +23 -14
- data/lib/bolt/transport/remote.rb +2 -2
- data/lib/bolt/transport/simple.rb +6 -6
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/util.rb +41 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/acl.rb +2 -2
- data/lib/bolt_server/base_config.rb +3 -3
- data/lib/bolt_server/schemas/partials/task.json +17 -2
- data/lib/bolt_server/transport_app.rb +93 -13
- data/lib/bolt_spec/bolt_context.rb +4 -2
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +6 -6
- data/lib/bolt_spec/run.rb +1 -1
- metadata +31 -12
- data/lib/bolt/project_migrate.rb +0 -138
- data/lib/bolt/puppetfile.rb +0 -160
- data/lib/bolt/puppetfile/module.rb +0 -66
- data/lib/bolt_server/pe/pal.rb +0 -67
- data/modules/secure_env_vars/plans/init.pp +0 -20
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/config'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Config
|
7
|
+
class Modulepath
|
8
|
+
BOLTLIB_PATH = File.expand_path('../../../bolt-modules', __dir__)
|
9
|
+
MODULES_PATH = File.expand_path('../../../modules', __dir__)
|
10
|
+
|
11
|
+
# The user_modulepath only includes the original modulepath and is used during pluginsync.
|
12
|
+
# We don't want to pluginsync any of the content from BOLT_MODULES since that content
|
13
|
+
# includes core modules that can conflict with modules installed with an agent.
|
14
|
+
attr_reader :user_modulepath
|
15
|
+
|
16
|
+
def initialize(user_modulepath, boltlib_path: BOLTLIB_PATH, builtin_content_path: MODULES_PATH)
|
17
|
+
@user_modulepath = Array(user_modulepath).flatten
|
18
|
+
@boltlib_path = Array(boltlib_path).flatten
|
19
|
+
@builtin_content_path = Array(builtin_content_path).flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
# The full_modulepath includes both the BOLTLIB
|
23
|
+
# path and the MODULES_PATH to ensure bolt functions and
|
24
|
+
# built-in content are available in the compliler
|
25
|
+
def full_modulepath
|
26
|
+
@boltlib_path + @user_modulepath + @builtin_content_path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/bolt/config/options.rb
CHANGED
@@ -233,17 +233,46 @@ module Bolt
|
|
233
233
|
"install` command.",
|
234
234
|
type: Array,
|
235
235
|
items: {
|
236
|
-
type: Hash,
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
236
|
+
type: [Hash, String],
|
237
|
+
oneOf: [
|
238
|
+
{
|
239
|
+
required: ["name"],
|
240
|
+
properties: {
|
241
|
+
"name" => {
|
242
|
+
description: "The name of the module.",
|
243
|
+
type: String
|
244
|
+
},
|
245
|
+
"version_requirement" => {
|
246
|
+
description: "The version requirement for the module. Accepts a specific version (1.2.3), version "\
|
247
|
+
"shorthand (1.2.x), or a version range (>= 1.2.0).",
|
248
|
+
type: String
|
249
|
+
}
|
250
|
+
}
|
251
|
+
},
|
252
|
+
{
|
253
|
+
required: %w[git ref],
|
254
|
+
properties: {
|
255
|
+
"git" => {
|
256
|
+
description: "The URL to the public git repository.",
|
257
|
+
type: String
|
258
|
+
},
|
259
|
+
"ref" => {
|
260
|
+
description: "The git reference to check out. Can be either a branch, tag, or commit SHA.",
|
261
|
+
type: String
|
262
|
+
}
|
263
|
+
}
|
242
264
|
}
|
243
|
-
|
265
|
+
]
|
244
266
|
},
|
245
267
|
_plugin: false,
|
246
|
-
_example: [
|
268
|
+
_example: [
|
269
|
+
"puppetlabs-facts",
|
270
|
+
{ "name" => "puppetlabs-mysql" },
|
271
|
+
{ "name" => "puppetlabs-apache", "version_requirement" => "5.5.0" },
|
272
|
+
{ "name" => "puppetlabs-puppetdb", "version_requirement" => "7.x" },
|
273
|
+
{ "name" => "puppetlabs-firewall", "version_requirement" => ">= 1.0.0 < 3.0.0" },
|
274
|
+
{ "git" => "https://github.com/puppetlabs/puppetlabs-apt", "ref" => "7.6.0" }
|
275
|
+
]
|
247
276
|
},
|
248
277
|
"name" => {
|
249
278
|
description: "The name of the Bolt project. When this option is configured, the project is considered a "\
|
@@ -318,7 +347,7 @@ module Bolt
|
|
318
347
|
"server_urls" => {
|
319
348
|
description: "An array containing the PuppetDB host to connect to. Include the protocol `https` "\
|
320
349
|
"and the port, which is usually `8081`. For example, "\
|
321
|
-
"`https://my-
|
350
|
+
"`https://my-puppetdb-server.com:8081`.",
|
322
351
|
type: Array,
|
323
352
|
_example: ["https://puppet.example.com:8081"]
|
324
353
|
},
|
@@ -357,7 +357,7 @@ module Bolt
|
|
357
357
|
description: "The URL of the host used for API requests.",
|
358
358
|
format: "uri",
|
359
359
|
_plugin: true,
|
360
|
-
_example: "https://api.example.com"
|
360
|
+
_example: "https://api.example.com:<port>"
|
361
361
|
},
|
362
362
|
"shell-command" => {
|
363
363
|
type: String,
|
@@ -374,7 +374,7 @@ module Bolt
|
|
374
374
|
},
|
375
375
|
"ssh-command" => {
|
376
376
|
type: [Array, String],
|
377
|
-
description: "The command and
|
377
|
+
description: "The command and options to use when SSHing. This option is used when you need support for "\
|
378
378
|
"features or algorithms that are not supported by the net-ssh Ruby library. **This option "\
|
379
379
|
"is experimental.** You can read more about this option in [Native SSH "\
|
380
380
|
"transport](experimental_features.md#native-ssh-transport).",
|
data/lib/bolt/error.rb
CHANGED
data/lib/bolt/executor.rb
CHANGED
@@ -227,7 +227,7 @@ module Bolt
|
|
227
227
|
data[:resource_mean] = sum / resource_counts.length
|
228
228
|
end
|
229
229
|
|
230
|
-
@analytics&.event('Apply', 'ast', data)
|
230
|
+
@analytics&.event('Apply', 'ast', **data)
|
231
231
|
end
|
232
232
|
|
233
233
|
def report_yaml_plan(plan)
|
@@ -253,33 +253,33 @@ module Bolt
|
|
253
253
|
result
|
254
254
|
end
|
255
255
|
|
256
|
-
def run_command(targets, command, options = {})
|
256
|
+
def run_command(targets, command, options = {}, position = [])
|
257
257
|
description = options.fetch(:description, "command '#{command}'")
|
258
258
|
log_action(description, targets) do
|
259
259
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
260
260
|
|
261
261
|
batch_execute(targets) do |transport, batch|
|
262
262
|
with_node_logging("Running command '#{command}'", batch) do
|
263
|
-
transport.batch_command(batch, command, options, &method(:publish_event))
|
263
|
+
transport.batch_command(batch, command, options, position, &method(:publish_event))
|
264
264
|
end
|
265
265
|
end
|
266
266
|
end
|
267
267
|
end
|
268
268
|
|
269
|
-
def run_script(targets, script, arguments, options = {})
|
269
|
+
def run_script(targets, script, arguments, options = {}, position = [])
|
270
270
|
description = options.fetch(:description, "script #{script}")
|
271
271
|
log_action(description, targets) do
|
272
272
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
273
273
|
|
274
274
|
batch_execute(targets) do |transport, batch|
|
275
275
|
with_node_logging("Running script #{script} with '#{arguments.to_json}'", batch) do
|
276
|
-
transport.batch_script(batch, script, arguments, options, &method(:publish_event))
|
276
|
+
transport.batch_script(batch, script, arguments, options, position, &method(:publish_event))
|
277
277
|
end
|
278
278
|
end
|
279
279
|
end
|
280
280
|
end
|
281
281
|
|
282
|
-
def run_task(targets, task, arguments, options = {})
|
282
|
+
def run_task(targets, task, arguments, options = {}, position = [])
|
283
283
|
description = options.fetch(:description, "task #{task.name}")
|
284
284
|
log_action(description, targets) do
|
285
285
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
@@ -287,13 +287,13 @@ module Bolt
|
|
287
287
|
|
288
288
|
batch_execute(targets) do |transport, batch|
|
289
289
|
with_node_logging("Running task #{task.name} with '#{arguments.to_json}'", batch) do
|
290
|
-
transport.batch_task(batch, task, arguments, options, &method(:publish_event))
|
290
|
+
transport.batch_task(batch, task, arguments, options, position, &method(:publish_event))
|
291
291
|
end
|
292
292
|
end
|
293
293
|
end
|
294
294
|
end
|
295
295
|
|
296
|
-
def run_task_with(target_mapping, task, options = {})
|
296
|
+
def run_task_with(target_mapping, task, options = {}, position = [])
|
297
297
|
targets = target_mapping.keys
|
298
298
|
description = options.fetch(:description, "task #{task.name}")
|
299
299
|
|
@@ -303,26 +303,26 @@ module Bolt
|
|
303
303
|
|
304
304
|
batch_execute(targets) do |transport, batch|
|
305
305
|
with_node_logging("Running task #{task.name}'", batch) do
|
306
|
-
transport.batch_task_with(batch, task, target_mapping, options, &method(:publish_event))
|
306
|
+
transport.batch_task_with(batch, task, target_mapping, options, position, &method(:publish_event))
|
307
307
|
end
|
308
308
|
end
|
309
309
|
end
|
310
310
|
end
|
311
311
|
|
312
|
-
def upload_file(targets, source, destination, options = {})
|
312
|
+
def upload_file(targets, source, destination, options = {}, position = [])
|
313
313
|
description = options.fetch(:description, "file upload from #{source} to #{destination}")
|
314
314
|
log_action(description, targets) do
|
315
315
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
316
316
|
|
317
317
|
batch_execute(targets) do |transport, batch|
|
318
318
|
with_node_logging("Uploading file #{source} to #{destination}", batch) do
|
319
|
-
transport.batch_upload(batch, source, destination, options, &method(:publish_event))
|
319
|
+
transport.batch_upload(batch, source, destination, options, position, &method(:publish_event))
|
320
320
|
end
|
321
321
|
end
|
322
322
|
end
|
323
323
|
end
|
324
324
|
|
325
|
-
def download_file(targets, source, destination, options = {})
|
325
|
+
def download_file(targets, source, destination, options = {}, position = [])
|
326
326
|
description = options.fetch(:description, "file download from #{source} to #{destination}")
|
327
327
|
|
328
328
|
begin
|
@@ -337,7 +337,7 @@ module Bolt
|
|
337
337
|
|
338
338
|
batch_execute(targets) do |transport, batch|
|
339
339
|
with_node_logging("Downloading file #{source} to #{destination}", batch) do
|
340
|
-
transport.batch_download(batch, source, destination, options, &method(:publish_event))
|
340
|
+
transport.batch_download(batch, source, destination, options, position, &method(:publish_event))
|
341
341
|
end
|
342
342
|
end
|
343
343
|
end
|
data/lib/bolt/inventory.rb
CHANGED
@@ -56,16 +56,17 @@ module Bolt
|
|
56
56
|
rescue Psych::Exception
|
57
57
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
|
58
58
|
end
|
59
|
+
elsif config.inventoryfile
|
60
|
+
data = Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
|
61
|
+
logger.debug("Loaded inventory from #{config.inventoryfile}")
|
59
62
|
else
|
60
|
-
data =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# This avoids rubocop complaining about identical conditionals
|
68
|
-
logger.debug("Loaded inventory from #{config.inventoryfile}") if config.inventoryfile
|
63
|
+
data = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
64
|
+
|
65
|
+
if config.default_inventoryfile.exist?
|
66
|
+
logger.debug("Loaded inventory from #{config.default_inventoryfile}")
|
67
|
+
else
|
68
|
+
logger.debug("Tried to load inventory from #{config.default_inventoryfile}, but the file does not exist")
|
69
|
+
end
|
69
70
|
end
|
70
71
|
|
71
72
|
# Resolve plugin references from transport config
|
data/lib/bolt/logger.rb
CHANGED
@@ -4,6 +4,10 @@ require 'logging'
|
|
4
4
|
|
5
5
|
module Bolt
|
6
6
|
module Logger
|
7
|
+
LEVELS = %w[trace debug info notice warn error fatal].freeze
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@warnings = Set.new
|
10
|
+
|
7
11
|
# This method provides a single point-of-entry to setup logging for both
|
8
12
|
# the CLI and for tests. This is necessary because we define custom log
|
9
13
|
# levels which create corresponding methods on the logger instances;
|
@@ -11,20 +15,25 @@ module Bolt
|
|
11
15
|
# will fail.
|
12
16
|
def self.initialize_logging
|
13
17
|
# Initialization isn't idempotent and will result in warnings about const
|
14
|
-
# redefs, so skip it if it's
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
# redefs, so skip it if the log levels we expect are present. If it's
|
19
|
+
# already been initialized with an insufficient set of levels, go ahead
|
20
|
+
# and call init anyway or we'll have failures when calling log methods
|
21
|
+
# for missing levels.
|
22
|
+
unless levels & LEVELS == LEVELS
|
23
|
+
Logging.init(*LEVELS)
|
24
|
+
end
|
25
|
+
|
26
|
+
# As above, only create the color scheme if we haven't already created it.
|
27
|
+
unless Logging.color_scheme('bolt')
|
28
|
+
Logging.color_scheme(
|
29
|
+
'bolt',
|
30
|
+
lines: {
|
31
|
+
warn: :yellow,
|
32
|
+
error: :red,
|
33
|
+
fatal: %i[white on_red]
|
34
|
+
}
|
35
|
+
)
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
30
39
|
def self.configure(destinations, color)
|
@@ -115,14 +124,12 @@ module Bolt
|
|
115
124
|
end
|
116
125
|
|
117
126
|
def self.warn_once(type, msg)
|
118
|
-
@mutex.synchronize
|
119
|
-
@warnings ||= []
|
127
|
+
@mutex.synchronize do
|
120
128
|
@logger ||= Bolt::Logger.logger(self)
|
121
|
-
|
129
|
+
if @warnings.add?(type)
|
122
130
|
@logger.warn(msg)
|
123
|
-
@warnings << type
|
124
131
|
end
|
125
|
-
|
132
|
+
end
|
126
133
|
end
|
127
134
|
|
128
135
|
def self.deprecation_warning(type, msg)
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
require 'bolt/logger'
|
5
|
+
require 'bolt/module_installer/installer'
|
6
|
+
require 'bolt/module_installer/puppetfile'
|
7
|
+
require 'bolt/module_installer/resolver'
|
8
|
+
require 'bolt/module_installer/specs'
|
9
|
+
|
10
|
+
module Bolt
|
11
|
+
class ModuleInstaller
|
12
|
+
def initialize(outputter, pal)
|
13
|
+
@outputter = outputter
|
14
|
+
@pal = pal
|
15
|
+
@logger = Bolt::Logger.logger(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a single module to the project.
|
19
|
+
#
|
20
|
+
def add(name, specs, puppetfile_path, moduledir, config_path)
|
21
|
+
project_specs = Specs.new(specs)
|
22
|
+
|
23
|
+
# Exit early if project config already includes a spec with this name.
|
24
|
+
if project_specs.include?(name)
|
25
|
+
@outputter.print_message(
|
26
|
+
"Project configuration file #{config_path} already includes specification with name "\
|
27
|
+
"#{name}. Nothing to do."
|
28
|
+
)
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
|
32
|
+
@outputter.print_message("Adding module #{name} to project\n\n")
|
33
|
+
|
34
|
+
# Generate the specs to resolve from. If a Puppetfile exists, parse it and
|
35
|
+
# convert the modules to specs. Otherwise, use the project specs.
|
36
|
+
resolve_specs = if puppetfile_path.exist?
|
37
|
+
existing_puppetfile = Puppetfile.parse(puppetfile_path)
|
38
|
+
existing_puppetfile.assert_satisfies(project_specs)
|
39
|
+
Specs.from_puppetfile(existing_puppetfile)
|
40
|
+
else
|
41
|
+
project_specs
|
42
|
+
end
|
43
|
+
|
44
|
+
# Resolve module dependencies. Attempt to first resolve with resolve
|
45
|
+
# specss. If that fails, fall back to resolving from project specs.
|
46
|
+
# This prevents Bolt from modifying installed modules unless there is
|
47
|
+
# a version conflict.
|
48
|
+
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
49
|
+
|
50
|
+
begin
|
51
|
+
resolve_specs.add_specs('name' => name)
|
52
|
+
puppetfile = Resolver.new.resolve(resolve_specs)
|
53
|
+
rescue Bolt::Error
|
54
|
+
project_specs.add_specs('name' => name)
|
55
|
+
puppetfile = Resolver.new.resolve(project_specs)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
59
|
+
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
60
|
+
|
61
|
+
# Add the module to the project configuration.
|
62
|
+
@outputter.print_action_step("Updating project configuration file at #{config_path}")
|
63
|
+
|
64
|
+
data = Bolt::Util.read_yaml_hash(config_path, 'project')
|
65
|
+
data['modules'] ||= []
|
66
|
+
data['modules'] << name
|
67
|
+
|
68
|
+
begin
|
69
|
+
File.write(config_path, data.to_yaml)
|
70
|
+
rescue SystemCallError => e
|
71
|
+
raise Bolt::FileError.new(
|
72
|
+
"Unable to update project configuration file: #{e.message}",
|
73
|
+
config
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write the Puppetfile.
|
78
|
+
@outputter.print_action_step("Writing Puppetfile at #{puppetfile_path}")
|
79
|
+
puppetfile.write(puppetfile_path, moduledir)
|
80
|
+
|
81
|
+
# Install the modules.
|
82
|
+
install_puppetfile(puppetfile_path, moduledir)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
86
|
+
#
|
87
|
+
def print_puppetfile_diff(old, new)
|
88
|
+
# Build hashes mapping the module name to the module object. This makes it
|
89
|
+
# a little easier to determine which modules have been added, removed, or
|
90
|
+
# modified.
|
91
|
+
old = (old&.modules || []).each_with_object({}) do |mod, acc|
|
92
|
+
next unless mod.type == :forge
|
93
|
+
acc[mod.full_name] = mod
|
94
|
+
end
|
95
|
+
|
96
|
+
new = new.modules.each_with_object({}) do |mod, acc|
|
97
|
+
next unless mod.type == :forge
|
98
|
+
acc[mod.full_name] = mod
|
99
|
+
end
|
100
|
+
|
101
|
+
# New modules are those present in new but not in old.
|
102
|
+
added = new.reject { |full_name, _mod| old.include?(full_name) }.values
|
103
|
+
|
104
|
+
if added.any?
|
105
|
+
diff = "Adding the following modules:\n"
|
106
|
+
added.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
107
|
+
@outputter.print_action_step(diff)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Upgraded modules are those that have a newer version in new than old.
|
111
|
+
upgraded = new.select do |full_name, mod|
|
112
|
+
if old.include?(full_name)
|
113
|
+
mod.version > old[full_name].version
|
114
|
+
end
|
115
|
+
end.keys
|
116
|
+
|
117
|
+
if upgraded.any?
|
118
|
+
diff = "Upgrading the following modules:\n"
|
119
|
+
upgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
120
|
+
@outputter.print_action_step(diff)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Downgraded modules are those that have an older version in new than old.
|
124
|
+
downgraded = new.select do |full_name, mod|
|
125
|
+
if old.include?(full_name)
|
126
|
+
mod.version < old[full_name].version
|
127
|
+
end
|
128
|
+
end.keys
|
129
|
+
|
130
|
+
if downgraded.any?
|
131
|
+
diff = "Downgrading the following modules: \n"
|
132
|
+
downgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
133
|
+
@outputter.print_action_step(diff)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Removed modules are those present in old but not in new.
|
137
|
+
removed = old.reject { |full_name, _mod| new.include?(full_name) }.values
|
138
|
+
|
139
|
+
if removed.any?
|
140
|
+
diff = "Removing the following modules:\n"
|
141
|
+
removed.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
142
|
+
@outputter.print_action_step(diff)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Installs a project's module dependencies.
|
147
|
+
#
|
148
|
+
def install(specs, path, moduledir, force: false, resolve: true)
|
149
|
+
@outputter.print_message("Installing project modules\n\n")
|
150
|
+
|
151
|
+
if resolve != false
|
152
|
+
specs = Specs.new(specs)
|
153
|
+
|
154
|
+
# If forcibly installing or if there is no Puppetfile, resolve
|
155
|
+
# and write a Puppetfile.
|
156
|
+
if force || !path.exist?
|
157
|
+
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
158
|
+
puppetfile = Resolver.new.resolve(specs)
|
159
|
+
|
160
|
+
# We get here either through 'bolt module install' which uses the
|
161
|
+
# managed modulepath (which isn't configurable) or through bolt
|
162
|
+
# project init --modules, which uses the default modulepath. This
|
163
|
+
# should be safe to assume that if `.modules/` is the moduledir the
|
164
|
+
# user is using the new workflow
|
165
|
+
@outputter.print_action_step("Writing Puppetfile at #{path}")
|
166
|
+
if moduledir.basename.to_s == '.modules'
|
167
|
+
puppetfile.write(path, moduledir)
|
168
|
+
else
|
169
|
+
puppetfile.write(path)
|
170
|
+
end
|
171
|
+
# If not forcibly installing and there is a Puppetfile, assert
|
172
|
+
# that it satisfies the specs.
|
173
|
+
else
|
174
|
+
puppetfile = Puppetfile.parse(path)
|
175
|
+
puppetfile.assert_satisfies(specs)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Install the modules.
|
180
|
+
install_puppetfile(path, moduledir)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Installs the Puppetfile and generates types.
|
184
|
+
#
|
185
|
+
def install_puppetfile(path, moduledir, config = {})
|
186
|
+
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
187
|
+
ok = Installer.new(config).install(path, moduledir)
|
188
|
+
|
189
|
+
# Automatically generate types after installing modules
|
190
|
+
@outputter.print_action_step("Generating type references")
|
191
|
+
@pal.generate_types
|
192
|
+
|
193
|
+
@outputter.print_puppetfile_result(ok, path, moduledir)
|
194
|
+
|
195
|
+
ok
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|