bolt 2.31.0 → 2.35.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/catch_errors.rb +1 -3
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
- 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 +6 -1
- data/lib/bolt/cli.rb +70 -144
- data/lib/bolt/config/options.rb +35 -17
- data/lib/bolt/config/transport/options.rb +1 -1
- data/lib/bolt/error.rb +37 -3
- data/lib/bolt/executor.rb +111 -13
- data/lib/bolt/inventory/group.rb +2 -1
- data/lib/bolt/module_installer.rb +71 -115
- 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 +85 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
- data/lib/bolt/outputter.rb +0 -47
- data/lib/bolt/outputter/human.rb +23 -11
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/pal.rb +48 -30
- data/lib/bolt/pal/yaml_plan.rb +11 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
- data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
- data/lib/bolt/plan_creator.rb +160 -0
- data/lib/bolt/plugin.rb +1 -1
- data/lib/bolt/project.rb +5 -10
- data/lib/bolt/project_migrator/config.rb +2 -1
- data/lib/bolt/project_migrator/inventory.rb +2 -2
- data/lib/bolt/project_migrator/modules.rb +10 -8
- data/lib/bolt/puppetdb/client.rb +3 -2
- data/lib/bolt/puppetdb/config.rb +8 -6
- data/lib/bolt/result.rb +23 -11
- data/lib/bolt/shell/bash.rb +11 -6
- data/lib/bolt/shell/powershell.rb +12 -7
- data/lib/bolt/task/run.rb +1 -1
- data/lib/bolt/transport/base.rb +18 -18
- data/lib/bolt/transport/docker.rb +23 -6
- data/lib/bolt/transport/orch.rb +23 -19
- data/lib/bolt/transport/orch/connection.rb +10 -3
- data/lib/bolt/transport/remote.rb +3 -3
- data/lib/bolt/transport/simple.rb +6 -6
- data/lib/bolt/util.rb +5 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt/yarn.rb +23 -0
- data/lib/bolt_server/file_cache.rb +2 -0
- data/lib/bolt_server/schemas/partials/task.json +17 -2
- data/lib/bolt_server/transport_app.rb +38 -7
- 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 +9 -6
- metadata +25 -8
- data/lib/bolt/puppetfile.rb +0 -149
- data/lib/bolt/puppetfile/module.rb +0 -93
- data/modules/secure_env_vars/plans/init.pp +0 -20
data/lib/bolt/error.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/util'
|
4
|
+
|
3
5
|
module Bolt
|
4
6
|
class Error < RuntimeError
|
5
7
|
attr_reader :kind, :details, :issue_code, :error_code
|
@@ -24,6 +26,10 @@ module Bolt
|
|
24
26
|
h
|
25
27
|
end
|
26
28
|
|
29
|
+
def add_filelineno(details)
|
30
|
+
@details.merge!(details) unless @details['file']
|
31
|
+
end
|
32
|
+
|
27
33
|
def to_json(opts = nil)
|
28
34
|
to_h.to_json(opts)
|
29
35
|
end
|
@@ -33,13 +39,17 @@ module Bolt
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def self.unknown_task(task)
|
36
|
-
|
37
|
-
|
42
|
+
command = Bolt::Util.powershell? ? "Get-BoltTask" : "bolt task show"
|
43
|
+
new(
|
44
|
+
"Could not find a task named '#{task}'. For a list of available tasks, run '#{command}'.",
|
45
|
+
'bolt/unknown-task'
|
46
|
+
)
|
38
47
|
end
|
39
48
|
|
40
49
|
def self.unknown_plan(plan)
|
50
|
+
command = Bolt::Util.powershell? ? "Get-BoltPlan" : "bolt plan show"
|
41
51
|
new(
|
42
|
-
"Could not find a plan named
|
52
|
+
"Could not find a plan named '#{plan}'. For a list of available plans, run '#{command}'.",
|
43
53
|
'bolt/unknown-plan'
|
44
54
|
)
|
45
55
|
end
|
@@ -80,6 +90,20 @@ module Bolt
|
|
80
90
|
end
|
81
91
|
end
|
82
92
|
|
93
|
+
class ParallelFailure < Bolt::Error
|
94
|
+
def initialize(results, failed_indices)
|
95
|
+
details = {
|
96
|
+
'action' => 'parallelize',
|
97
|
+
'failed_indices' => failed_indices,
|
98
|
+
'results' => results
|
99
|
+
}
|
100
|
+
message = "Plan aborted: parallel block failed on #{failed_indices.length} target"
|
101
|
+
message += "s" unless failed_indices.length == 1
|
102
|
+
super(message, 'bolt/parallel-failure', details)
|
103
|
+
@error_code = 2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
83
107
|
class PlanFailure < Error
|
84
108
|
def initialize(*args)
|
85
109
|
super(*args)
|
@@ -121,6 +145,16 @@ module Bolt
|
|
121
145
|
end
|
122
146
|
end
|
123
147
|
|
148
|
+
class InvalidParallelResult < Error
|
149
|
+
def initialize(result_str, file, line)
|
150
|
+
super("Parallel block returned an invalid result: #{result_str}",
|
151
|
+
'bolt/invalid-plan-result',
|
152
|
+
{ 'file' => file,
|
153
|
+
'line' => line,
|
154
|
+
'result_string' => result_str })
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
124
158
|
class ValidationError < Bolt::Error
|
125
159
|
def initialize(msg)
|
126
160
|
super(msg, 'bolt/validation-error')
|
data/lib/bolt/executor.rb
CHANGED
@@ -17,6 +17,7 @@ require 'bolt/transport/orch'
|
|
17
17
|
require 'bolt/transport/local'
|
18
18
|
require 'bolt/transport/docker'
|
19
19
|
require 'bolt/transport/remote'
|
20
|
+
require 'bolt/yarn'
|
20
21
|
|
21
22
|
module Bolt
|
22
23
|
TRANSPORTS = {
|
@@ -29,7 +30,7 @@ module Bolt
|
|
29
30
|
}.freeze
|
30
31
|
|
31
32
|
class Executor
|
32
|
-
attr_reader :noop, :transports
|
33
|
+
attr_reader :noop, :transports, :in_parallel
|
33
34
|
attr_accessor :run_as
|
34
35
|
|
35
36
|
def initialize(concurrency = 1,
|
@@ -60,6 +61,7 @@ module Bolt
|
|
60
61
|
|
61
62
|
@noop = noop
|
62
63
|
@run_as = nil
|
64
|
+
@in_parallel = false
|
63
65
|
@pool = if concurrency > 0
|
64
66
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
65
67
|
else
|
@@ -84,6 +86,14 @@ module Bolt
|
|
84
86
|
self
|
85
87
|
end
|
86
88
|
|
89
|
+
def unsubscribe(subscriber, types = nil)
|
90
|
+
if types.nil? || types.sort == @subscribers[subscriber]&.sort
|
91
|
+
@subscribers.delete(subscriber)
|
92
|
+
elsif @subscribers[subscriber].is_a?(Array)
|
93
|
+
@subscribers[subscriber] = @subscribers[subscriber] - types
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
87
97
|
def publish_event(event)
|
88
98
|
@subscribers.each do |subscriber, types|
|
89
99
|
# If types isn't set or if the subscriber is subscribed to
|
@@ -253,33 +263,33 @@ module Bolt
|
|
253
263
|
result
|
254
264
|
end
|
255
265
|
|
256
|
-
def run_command(targets, command, options = {})
|
266
|
+
def run_command(targets, command, options = {}, position = [])
|
257
267
|
description = options.fetch(:description, "command '#{command}'")
|
258
268
|
log_action(description, targets) do
|
259
269
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
260
270
|
|
261
271
|
batch_execute(targets) do |transport, batch|
|
262
272
|
with_node_logging("Running command '#{command}'", batch) do
|
263
|
-
transport.batch_command(batch, command, options, &method(:publish_event))
|
273
|
+
transport.batch_command(batch, command, options, position, &method(:publish_event))
|
264
274
|
end
|
265
275
|
end
|
266
276
|
end
|
267
277
|
end
|
268
278
|
|
269
|
-
def run_script(targets, script, arguments, options = {})
|
279
|
+
def run_script(targets, script, arguments, options = {}, position = [])
|
270
280
|
description = options.fetch(:description, "script #{script}")
|
271
281
|
log_action(description, targets) do
|
272
282
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
273
283
|
|
274
284
|
batch_execute(targets) do |transport, batch|
|
275
285
|
with_node_logging("Running script #{script} with '#{arguments.to_json}'", batch) do
|
276
|
-
transport.batch_script(batch, script, arguments, options, &method(:publish_event))
|
286
|
+
transport.batch_script(batch, script, arguments, options, position, &method(:publish_event))
|
277
287
|
end
|
278
288
|
end
|
279
289
|
end
|
280
290
|
end
|
281
291
|
|
282
|
-
def run_task(targets, task, arguments, options = {})
|
292
|
+
def run_task(targets, task, arguments, options = {}, position = [])
|
283
293
|
description = options.fetch(:description, "task #{task.name}")
|
284
294
|
log_action(description, targets) do
|
285
295
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
@@ -287,13 +297,25 @@ module Bolt
|
|
287
297
|
|
288
298
|
batch_execute(targets) do |transport, batch|
|
289
299
|
with_node_logging("Running task #{task.name} with '#{arguments.to_json}'", batch) do
|
290
|
-
transport.batch_task(batch, task, arguments, options, &method(:publish_event))
|
300
|
+
transport.batch_task(batch, task, arguments, options, position, &method(:publish_event))
|
291
301
|
end
|
292
302
|
end
|
293
303
|
end
|
294
304
|
end
|
295
305
|
|
296
|
-
def
|
306
|
+
def run_task_with_minimal_logging(targets, task, arguments, options = {})
|
307
|
+
description = options.fetch(:description, "task #{task.name}")
|
308
|
+
log_action(description, targets) do
|
309
|
+
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
310
|
+
arguments['_task'] = task.name
|
311
|
+
|
312
|
+
batch_execute(targets) do |transport, batch|
|
313
|
+
transport.batch_task(batch, task, arguments, options, [], &method(:publish_event))
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def run_task_with(target_mapping, task, options = {}, position = [])
|
297
319
|
targets = target_mapping.keys
|
298
320
|
description = options.fetch(:description, "task #{task.name}")
|
299
321
|
|
@@ -303,26 +325,26 @@ module Bolt
|
|
303
325
|
|
304
326
|
batch_execute(targets) do |transport, batch|
|
305
327
|
with_node_logging("Running task #{task.name}'", batch) do
|
306
|
-
transport.batch_task_with(batch, task, target_mapping, options, &method(:publish_event))
|
328
|
+
transport.batch_task_with(batch, task, target_mapping, options, position, &method(:publish_event))
|
307
329
|
end
|
308
330
|
end
|
309
331
|
end
|
310
332
|
end
|
311
333
|
|
312
|
-
def upload_file(targets, source, destination, options = {})
|
334
|
+
def upload_file(targets, source, destination, options = {}, position = [])
|
313
335
|
description = options.fetch(:description, "file upload from #{source} to #{destination}")
|
314
336
|
log_action(description, targets) do
|
315
337
|
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
316
338
|
|
317
339
|
batch_execute(targets) do |transport, batch|
|
318
340
|
with_node_logging("Uploading file #{source} to #{destination}", batch) do
|
319
|
-
transport.batch_upload(batch, source, destination, options, &method(:publish_event))
|
341
|
+
transport.batch_upload(batch, source, destination, options, position, &method(:publish_event))
|
320
342
|
end
|
321
343
|
end
|
322
344
|
end
|
323
345
|
end
|
324
346
|
|
325
|
-
def download_file(targets, source, destination, options = {})
|
347
|
+
def download_file(targets, source, destination, options = {}, position = [])
|
326
348
|
description = options.fetch(:description, "file download from #{source} to #{destination}")
|
327
349
|
|
328
350
|
begin
|
@@ -337,7 +359,7 @@ module Bolt
|
|
337
359
|
|
338
360
|
batch_execute(targets) do |transport, batch|
|
339
361
|
with_node_logging("Downloading file #{source} to #{destination}", batch) do
|
340
|
-
transport.batch_download(batch, source, destination, options, &method(:publish_event))
|
362
|
+
transport.batch_download(batch, source, destination, options, position, &method(:publish_event))
|
341
363
|
end
|
342
364
|
end
|
343
365
|
end
|
@@ -347,6 +369,82 @@ module Bolt
|
|
347
369
|
plan.call_by_name_with_scope(scope, params, true)
|
348
370
|
end
|
349
371
|
|
372
|
+
def create_yarn(scope, block, object, index)
|
373
|
+
fiber = Fiber.new do
|
374
|
+
# Create the new scope
|
375
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
376
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
377
|
+
|
378
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
379
|
+
current_scope = scope.effective_symtable(true)
|
380
|
+
until current_scope.nil?
|
381
|
+
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
382
|
+
current_scope = current_scope.parent
|
383
|
+
end
|
384
|
+
newscope.push_ephemerals([local])
|
385
|
+
|
386
|
+
begin
|
387
|
+
result = catch(:return) do
|
388
|
+
args = { block.parameters[0][1].to_s => object }
|
389
|
+
block.closure.call_by_name_with_scope(newscope, args, true)
|
390
|
+
end
|
391
|
+
|
392
|
+
# If we got a return from the block, get it's value
|
393
|
+
# Otherwise the result is the last line from the block
|
394
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
395
|
+
|
396
|
+
# Validate the result is a PlanResult
|
397
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
398
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
399
|
+
end
|
400
|
+
|
401
|
+
result
|
402
|
+
rescue Puppet::PreformattedError => e
|
403
|
+
if e.cause.is_a?(Bolt::Error)
|
404
|
+
e.cause
|
405
|
+
else
|
406
|
+
raise e
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
Bolt::Yarn.new(fiber, index)
|
412
|
+
end
|
413
|
+
|
414
|
+
def handle_event(event)
|
415
|
+
case event[:type]
|
416
|
+
when :node_result
|
417
|
+
@thread_completed = true
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def round_robin(skein)
|
422
|
+
subscribe(self, [:node_result])
|
423
|
+
results = Array.new(skein.length)
|
424
|
+
@in_parallel = true
|
425
|
+
|
426
|
+
until skein.empty?
|
427
|
+
@thread_completed = false
|
428
|
+
r = nil
|
429
|
+
|
430
|
+
skein.each do |yarn|
|
431
|
+
if yarn.alive?
|
432
|
+
r = yarn.resume
|
433
|
+
else
|
434
|
+
results[yarn.index] = yarn.value
|
435
|
+
skein.delete(yarn)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
next unless r == 'unfinished'
|
440
|
+
sleep(0.1) until @thread_completed || skein.empty?
|
441
|
+
end
|
442
|
+
|
443
|
+
@in_parallel = false
|
444
|
+
unsubscribe(self, [:node_result])
|
445
|
+
results
|
446
|
+
end
|
447
|
+
|
350
448
|
class TimeoutError < RuntimeError; end
|
351
449
|
|
352
450
|
def wait_until_available(targets,
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -241,10 +241,11 @@ module Bolt
|
|
241
241
|
end
|
242
242
|
|
243
243
|
if input.key?('nodes')
|
244
|
+
command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
|
244
245
|
msg = <<~MSG.chomp
|
245
246
|
Found 'nodes' key in group #{@name}. This looks like a v1 inventory file, which is
|
246
247
|
no longer supported by Bolt. Migrate to a v2 inventory file automatically using
|
247
|
-
'
|
248
|
+
'#{command}'.
|
248
249
|
MSG
|
249
250
|
raise ValidationError.new(msg, nil)
|
250
251
|
end
|
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'bolt/error'
|
4
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'
|
5
9
|
|
6
10
|
module Bolt
|
7
11
|
class ModuleInstaller
|
@@ -13,45 +17,53 @@ module Bolt
|
|
13
17
|
|
14
18
|
# Adds a single module to the project.
|
15
19
|
#
|
16
|
-
def add(name,
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
new_module = Bolt::Puppetfile::Module.from_hash('name' => name)
|
25
|
-
|
26
|
-
if puppetfile.modules.include?(new_module)
|
27
|
-
@outputter.print_action_step(
|
28
|
-
"Project configuration file #{config_path} already includes module #{new_module}. Nothing to do."
|
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."
|
29
28
|
)
|
30
29
|
return true
|
31
30
|
end
|
32
31
|
|
33
|
-
|
34
|
-
if puppetfile_path.exist?
|
35
|
-
assert_managed_puppetfile(puppetfile, puppetfile_path)
|
36
|
-
existing = Bolt::Puppetfile.parse(puppetfile_path)
|
37
|
-
else
|
38
|
-
existing = Bolt::Puppetfile.new
|
39
|
-
end
|
32
|
+
@outputter.print_message("Adding module #{name} to project\n\n")
|
40
33
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
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
|
45
57
|
|
46
58
|
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
47
|
-
print_puppetfile_diff(
|
59
|
+
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
48
60
|
|
49
61
|
# Add the module to the project configuration.
|
50
62
|
@outputter.print_action_step("Updating project configuration file at #{config_path}")
|
51
63
|
|
52
64
|
data = Bolt::Util.read_yaml_hash(config_path, 'project')
|
53
65
|
data['modules'] ||= []
|
54
|
-
data['modules'] <<
|
66
|
+
data['modules'] << name.tr('-', '/')
|
55
67
|
|
56
68
|
begin
|
57
69
|
File.write(config_path, data.to_yaml)
|
@@ -70,130 +82,97 @@ module Bolt
|
|
70
82
|
install_puppetfile(puppetfile_path, moduledir)
|
71
83
|
end
|
72
84
|
|
73
|
-
# Creates a new Puppetfile that includes the new module and its dependencies.
|
74
|
-
#
|
75
|
-
private def add_new_module_to_puppetfile(new_module, modules, path)
|
76
|
-
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
77
|
-
|
78
|
-
# If there is an existing Puppetfile, add the new module and attempt
|
79
|
-
# to resolve. This will not update the versions of any installed modules.
|
80
|
-
if path.exist?
|
81
|
-
puppetfile = Bolt::Puppetfile.parse(path)
|
82
|
-
puppetfile.add_modules(new_module)
|
83
|
-
|
84
|
-
begin
|
85
|
-
puppetfile.resolve
|
86
|
-
return puppetfile
|
87
|
-
rescue Bolt::Error
|
88
|
-
@logger.debug "Unable to find a version of #{new_module} compatible "\
|
89
|
-
"with installed modules. Attempting to re-resolve modules "\
|
90
|
-
"from project configuration; some versions of installed "\
|
91
|
-
"modules may change."
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# If there is not an existing Puppetfile, or resolving with pinned
|
96
|
-
# modules fails, resolve all of the module declarations with the new
|
97
|
-
# module.
|
98
|
-
puppetfile = Bolt::Puppetfile.new(modules)
|
99
|
-
puppetfile.add_modules(new_module)
|
100
|
-
puppetfile.resolve
|
101
|
-
puppetfile
|
102
|
-
end
|
103
|
-
|
104
85
|
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
105
86
|
#
|
106
87
|
def print_puppetfile_diff(old, new)
|
107
|
-
# Build hashes mapping the module
|
88
|
+
# Build hashes mapping the module name to the module object. This makes it
|
108
89
|
# a little easier to determine which modules have been added, removed, or
|
109
90
|
# modified.
|
110
|
-
old = old
|
111
|
-
|
91
|
+
old = (old&.modules || []).each_with_object({}) do |mod, acc|
|
92
|
+
next unless mod.type == :forge
|
93
|
+
acc[mod.full_name] = mod
|
112
94
|
end
|
113
95
|
|
114
96
|
new = new.modules.each_with_object({}) do |mod, acc|
|
115
|
-
|
97
|
+
next unless mod.type == :forge
|
98
|
+
acc[mod.full_name] = mod
|
116
99
|
end
|
117
100
|
|
118
101
|
# New modules are those present in new but not in old.
|
119
|
-
added = new.reject { |
|
102
|
+
added = new.reject { |full_name, _mod| old.include?(full_name) }.values
|
120
103
|
|
121
104
|
if added.any?
|
122
105
|
diff = "Adding the following modules:\n"
|
123
|
-
added.each { |mod| diff += "#{mod.
|
106
|
+
added.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
124
107
|
@outputter.print_action_step(diff)
|
125
108
|
end
|
126
109
|
|
127
110
|
# Upgraded modules are those that have a newer version in new than old.
|
128
|
-
upgraded = new.select do |
|
129
|
-
if old.include?(
|
130
|
-
|
111
|
+
upgraded = new.select do |full_name, mod|
|
112
|
+
if old.include?(full_name)
|
113
|
+
mod.version > old[full_name].version
|
131
114
|
end
|
132
115
|
end.keys
|
133
116
|
|
134
117
|
if upgraded.any?
|
135
118
|
diff = "Upgrading the following modules:\n"
|
136
|
-
upgraded.each { |
|
119
|
+
upgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
137
120
|
@outputter.print_action_step(diff)
|
138
121
|
end
|
139
122
|
|
140
123
|
# Downgraded modules are those that have an older version in new than old.
|
141
|
-
downgraded = new.select do |
|
142
|
-
if old.include?(
|
143
|
-
|
124
|
+
downgraded = new.select do |full_name, mod|
|
125
|
+
if old.include?(full_name)
|
126
|
+
mod.version < old[full_name].version
|
144
127
|
end
|
145
128
|
end.keys
|
146
129
|
|
147
130
|
if downgraded.any?
|
148
131
|
diff = "Downgrading the following modules: \n"
|
149
|
-
downgraded.each { |
|
132
|
+
downgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
150
133
|
@outputter.print_action_step(diff)
|
151
134
|
end
|
152
135
|
|
153
136
|
# Removed modules are those present in old but not in new.
|
154
|
-
removed = old.reject { |
|
137
|
+
removed = old.reject { |full_name, _mod| new.include?(full_name) }.values
|
155
138
|
|
156
139
|
if removed.any?
|
157
140
|
diff = "Removing the following modules:\n"
|
158
|
-
removed.each { |mod| diff += "#{mod.
|
141
|
+
removed.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
159
142
|
@outputter.print_action_step(diff)
|
160
143
|
end
|
161
144
|
end
|
162
145
|
|
163
146
|
# Installs a project's module dependencies.
|
164
147
|
#
|
165
|
-
def install(
|
166
|
-
require 'bolt/puppetfile'
|
167
|
-
|
148
|
+
def install(specs, path, moduledir, force: false, resolve: true)
|
168
149
|
@outputter.print_message("Installing project modules\n\n")
|
169
150
|
|
170
|
-
puppetfile = Bolt::Puppetfile.new(modules)
|
171
|
-
|
172
|
-
# If the Puppetfile exists, check if it includes specs for each declared
|
173
|
-
# module, erroring if there are any missing. Otherwise, resolve the
|
174
|
-
# module dependencies and write a new Puppetfile. Users can forcibly
|
175
|
-
# overwrite an existing Puppetfile with the '--force' option, or opt to
|
176
|
-
# install the Puppetfile as-is with --no-resolve.
|
177
|
-
#
|
178
|
-
# This is just if resolve is not false (nil should default to true)
|
179
151
|
if resolve != false
|
180
|
-
|
181
|
-
|
182
|
-
|
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?
|
183
157
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
184
|
-
puppetfile.resolve
|
158
|
+
puppetfile = Resolver.new.resolve(specs)
|
185
159
|
|
186
|
-
@outputter.print_action_step("Writing Puppetfile at #{path}")
|
187
160
|
# We get here either through 'bolt module install' which uses the
|
188
161
|
# managed modulepath (which isn't configurable) or through bolt
|
189
162
|
# project init --modules, which uses the default modulepath. This
|
190
163
|
# should be safe to assume that if `.modules/` is the moduledir the
|
191
164
|
# user is using the new workflow
|
165
|
+
@outputter.print_action_step("Writing Puppetfile at #{path}")
|
192
166
|
if moduledir.basename.to_s == '.modules'
|
193
167
|
puppetfile.write(path, moduledir)
|
194
168
|
else
|
195
169
|
puppetfile.write(path)
|
196
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)
|
197
176
|
end
|
198
177
|
end
|
199
178
|
|
@@ -204,39 +183,16 @@ module Bolt
|
|
204
183
|
# Installs the Puppetfile and generates types.
|
205
184
|
#
|
206
185
|
def install_puppetfile(path, moduledir, config = {})
|
207
|
-
require 'bolt/puppetfile/installer'
|
208
|
-
|
209
186
|
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
210
|
-
ok =
|
187
|
+
ok = Installer.new(config).install(path, moduledir)
|
211
188
|
|
212
189
|
# Automatically generate types after installing modules
|
190
|
+
@outputter.print_action_step("Generating type references")
|
213
191
|
@pal.generate_types
|
214
192
|
|
215
193
|
@outputter.print_puppetfile_result(ok, path, moduledir)
|
216
194
|
|
217
195
|
ok
|
218
196
|
end
|
219
|
-
|
220
|
-
# Asserts that an existing Puppetfile is managed by Bolt.
|
221
|
-
#
|
222
|
-
private def assert_managed_puppetfile(puppetfile, path)
|
223
|
-
existing_puppetfile = Bolt::Puppetfile.parse(path)
|
224
|
-
|
225
|
-
unless existing_puppetfile.modules.superset? puppetfile.modules
|
226
|
-
missing_modules = puppetfile.modules - existing_puppetfile.modules
|
227
|
-
|
228
|
-
message = <<~MESSAGE.chomp
|
229
|
-
Puppetfile #{path} is missing specifications for the following
|
230
|
-
module declarations:
|
231
|
-
|
232
|
-
#{missing_modules.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
|
233
|
-
|
234
|
-
This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
|
235
|
-
Puppetfile, run 'bolt module install --force'.
|
236
|
-
MESSAGE
|
237
|
-
|
238
|
-
raise Bolt::Error.new(message, 'bolt/missing-module-specs')
|
239
|
-
end
|
240
|
-
end
|
241
197
|
end
|
242
198
|
end
|