bolt 2.42.0 → 3.3.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 +21 -19
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -5
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
- data/lib/bolt/analytics.rb +3 -2
- data/lib/bolt/applicator.rb +11 -1
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +9 -116
- data/lib/bolt/catalog.rb +10 -29
- data/lib/bolt/cli.rb +90 -154
- data/lib/bolt/config.rb +66 -239
- data/lib/bolt/config/options.rb +79 -102
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/lxd.rb +21 -0
- data/lib/bolt/config/transport/options.rb +9 -2
- data/lib/bolt/config/transport/orch.rb +1 -0
- data/lib/bolt/executor.rb +23 -6
- data/lib/bolt/inventory.rb +1 -1
- data/lib/bolt/inventory/group.rb +7 -4
- data/lib/bolt/logger.rb +123 -11
- data/lib/bolt/module_installer.rb +6 -4
- data/lib/bolt/module_installer/puppetfile.rb +2 -2
- data/lib/bolt/module_installer/resolver.rb +59 -14
- data/lib/bolt/module_installer/specs/forge_spec.rb +10 -4
- data/lib/bolt/module_installer/specs/git_spec.rb +19 -4
- data/lib/bolt/outputter/human.rb +56 -17
- data/lib/bolt/outputter/json.rb +16 -16
- data/lib/bolt/outputter/rainbow.rb +3 -3
- data/lib/bolt/pal.rb +95 -15
- data/lib/bolt/pal/yaml_plan.rb +9 -4
- data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
- data/lib/bolt/pal/yaml_plan/step.rb +91 -52
- data/lib/bolt/pal/yaml_plan/step/command.rb +16 -16
- data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
- data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
- data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
- data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
- data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
- data/lib/bolt/pal/yaml_plan/step/script.rb +32 -17
- data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
- data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
- data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -1
- data/lib/bolt/plan_creator.rb +1 -1
- data/lib/bolt/plugin.rb +2 -2
- data/lib/bolt/plugin/cache.rb +7 -7
- data/lib/bolt/plugin/module.rb +0 -23
- data/lib/bolt/plugin/puppet_connect_data.rb +77 -0
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +54 -81
- data/lib/bolt/project_manager.rb +5 -4
- data/lib/bolt/project_manager/module_migrator.rb +7 -6
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/result.rb +6 -1
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +57 -25
- data/lib/bolt/shell/bash/tmpdir.rb +6 -3
- data/lib/bolt/shell/powershell.rb +33 -10
- data/lib/bolt/shell/powershell/snippets.rb +37 -150
- data/lib/bolt/task.rb +2 -2
- data/lib/bolt/transport/base.rb +0 -9
- data/lib/bolt/transport/docker.rb +1 -125
- data/lib/bolt/transport/docker/connection.rb +86 -161
- data/lib/bolt/transport/local.rb +1 -9
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/lxd/connection.rb +99 -0
- data/lib/bolt/transport/orch/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +2 -2
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/validator.rb +2 -2
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/transport_app.rb +61 -32
- data/lib/bolt_spec/bolt_context.rb +9 -4
- data/lib/bolt_spec/plans.rb +1 -109
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +4 -0
- data/libexec/bolt_catalog +1 -1
- data/modules/aggregate/plans/count.pp +21 -0
- data/modules/aggregate/plans/targets.pp +21 -0
- data/modules/puppet_connect/plans/test_input_data.pp +67 -0
- data/modules/puppetdb_fact/plans/init.pp +10 -0
- metadata +13 -9
- data/modules/aggregate/plans/nodes.pp +0 -36
data/lib/bolt/outputter/human.rb
CHANGED
@@ -32,8 +32,8 @@ module Bolt
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def start_spin
|
35
|
-
return unless @spin && @stream.isatty
|
36
|
-
@
|
35
|
+
return unless @spin && @stream.isatty && !@spinning
|
36
|
+
@spinning = true
|
37
37
|
@spin_thread = Thread.new do
|
38
38
|
loop do
|
39
39
|
sleep(0.1)
|
@@ -43,9 +43,9 @@ module Bolt
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def stop_spin
|
46
|
-
return unless @spin && @stream.isatty
|
46
|
+
return unless @spin && @stream.isatty && @spinning
|
47
|
+
@spinning = false
|
47
48
|
@spin_thread.terminate
|
48
|
-
@spin = false
|
49
49
|
@stream.print("\b")
|
50
50
|
end
|
51
51
|
|
@@ -53,10 +53,21 @@ module Bolt
|
|
53
53
|
string.sub(/\s\z/, '')
|
54
54
|
end
|
55
55
|
|
56
|
+
# Wraps a string to the specified width. Lines only wrap
|
57
|
+
# at whitespace.
|
58
|
+
#
|
56
59
|
def wrap(string, width = 80)
|
60
|
+
return string unless string.is_a?(String)
|
57
61
|
string.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
58
62
|
end
|
59
63
|
|
64
|
+
# Trims a string to a specified width, adding an ellipsis if it's longer.
|
65
|
+
#
|
66
|
+
def truncate(string, width = 80)
|
67
|
+
return string unless string.is_a?(String) && string.length > width
|
68
|
+
string.lines.first[0...width].gsub(/\s\w+\s*$/, '...')
|
69
|
+
end
|
70
|
+
|
60
71
|
def handle_event(event)
|
61
72
|
case event[:type]
|
62
73
|
when :enable_default_output
|
@@ -81,6 +92,10 @@ module Bolt
|
|
81
92
|
print_plan_start(event)
|
82
93
|
when :plan_finish
|
83
94
|
print_plan_finish(event)
|
95
|
+
when :start_spin
|
96
|
+
start_spin
|
97
|
+
when :stop_spin
|
98
|
+
stop_spin
|
84
99
|
end
|
85
100
|
end
|
86
101
|
end
|
@@ -214,11 +229,11 @@ module Bolt
|
|
214
229
|
@stream.puts total_msg
|
215
230
|
end
|
216
231
|
|
217
|
-
def
|
232
|
+
def format_table(results, padding_left = 0, padding_right = 3)
|
218
233
|
# lazy-load expensive gem code
|
219
234
|
require 'terminal-table'
|
220
235
|
|
221
|
-
|
236
|
+
Terminal::Table.new(
|
222
237
|
rows: results,
|
223
238
|
style: {
|
224
239
|
border_x: '',
|
@@ -234,10 +249,22 @@ module Bolt
|
|
234
249
|
|
235
250
|
def print_tasks(tasks, modulepath)
|
236
251
|
command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
252
|
+
|
253
|
+
tasks = tasks.map do |name, description|
|
254
|
+
description = truncate(description, 72)
|
255
|
+
[name, description]
|
256
|
+
end
|
257
|
+
|
258
|
+
@stream.puts colorize(:cyan, 'Tasks')
|
259
|
+
@stream.puts tasks.any? ? format_table(tasks, 2) : indent(2, 'No available tasks')
|
260
|
+
@stream.puts
|
261
|
+
|
262
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
263
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
264
|
+
@stream.puts
|
265
|
+
|
266
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
267
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
|
241
268
|
end
|
242
269
|
|
243
270
|
# @param [Hash] task A hash representing the task
|
@@ -318,10 +345,22 @@ module Bolt
|
|
318
345
|
|
319
346
|
def print_plans(plans, modulepath)
|
320
347
|
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
348
|
+
|
349
|
+
plans = plans.map do |name, description|
|
350
|
+
description = truncate(description, 72)
|
351
|
+
[name, description]
|
352
|
+
end
|
353
|
+
|
354
|
+
@stream.puts colorize(:cyan, 'Plans')
|
355
|
+
@stream.puts plans.any? ? format_table(plans, 2) : indent(2, 'No available plans')
|
356
|
+
@stream.puts
|
357
|
+
|
358
|
+
@stream.puts colorize(:cyan, 'Modulepath')
|
359
|
+
@stream.puts indent(2, modulepath.join(File::PATH_SEPARATOR))
|
360
|
+
@stream.puts
|
361
|
+
|
362
|
+
@stream.puts colorize(:cyan, 'Additional information')
|
363
|
+
@stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
|
325
364
|
end
|
326
365
|
|
327
366
|
def print_topics(topics)
|
@@ -355,7 +394,7 @@ module Bolt
|
|
355
394
|
[m[:name], version]
|
356
395
|
end
|
357
396
|
|
358
|
-
|
397
|
+
@stream.puts format_table(module_info, 2, 1)
|
359
398
|
end
|
360
399
|
|
361
400
|
@stream.write("\n")
|
@@ -370,7 +409,7 @@ module Bolt
|
|
370
409
|
targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
|
371
410
|
|
372
411
|
if targets.any?
|
373
|
-
|
412
|
+
@stream.puts format_table(targets, 0, 2)
|
374
413
|
@stream.puts
|
375
414
|
end
|
376
415
|
|
@@ -388,7 +427,7 @@ module Bolt
|
|
388
427
|
|
389
428
|
def print_target_info(targets)
|
390
429
|
@stream.puts ::JSON.pretty_generate(
|
391
|
-
|
430
|
+
targets: targets.map(&:detail)
|
392
431
|
)
|
393
432
|
count = "#{targets.count} target#{'s' unless targets.count == 1}"
|
394
433
|
@stream.puts colorize(:green, count)
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -95,38 +95,38 @@ module Bolt
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def print_puppetfile_result(success, puppetfile, moduledir)
|
98
|
-
@stream.puts({
|
99
|
-
|
100
|
-
|
98
|
+
@stream.puts({ success: success,
|
99
|
+
puppetfile: puppetfile,
|
100
|
+
moduledir: moduledir.to_s }.to_json)
|
101
101
|
end
|
102
102
|
|
103
103
|
def print_targets(target_list, inventoryfile)
|
104
104
|
@stream.puts ::JSON.pretty_generate(
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
inventory: {
|
106
|
+
targets: target_list[:inventory].map(&:name),
|
107
|
+
count: target_list[:inventory].count,
|
108
|
+
file: inventoryfile.to_s
|
109
109
|
},
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
adhoc: {
|
111
|
+
targets: target_list[:adhoc].map(&:name),
|
112
|
+
count: target_list[:adhoc].count
|
113
113
|
},
|
114
|
-
|
115
|
-
|
114
|
+
targets: target_list.values.flatten.map(&:name),
|
115
|
+
count: target_list.values.flatten.count
|
116
116
|
)
|
117
117
|
end
|
118
118
|
|
119
119
|
def print_target_info(targets)
|
120
120
|
@stream.puts ::JSON.pretty_generate(
|
121
|
-
|
122
|
-
|
121
|
+
targets: targets.map(&:detail),
|
122
|
+
count: targets.count
|
123
123
|
)
|
124
124
|
end
|
125
125
|
|
126
126
|
def print_groups(groups)
|
127
127
|
count = groups.count
|
128
|
-
@stream.puts({
|
129
|
-
|
128
|
+
@stream.puts({ groups: groups,
|
129
|
+
count: count }.to_json)
|
130
130
|
end
|
131
131
|
|
132
132
|
def fatal_error(err)
|
@@ -63,12 +63,12 @@ module Bolt
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def start_spin
|
66
|
-
return unless @spin && @stream.isatty
|
67
|
-
@
|
66
|
+
return unless @spin && @stream.isatty && !@spinning
|
67
|
+
@spinning = true
|
68
68
|
@spin_thread = Thread.new do
|
69
69
|
loop do
|
70
|
-
@stream.print(colorize(:rainbow, @pinwheel.rotate!.first + "\b"))
|
71
70
|
sleep(0.1)
|
71
|
+
@stream.print(colorize(:rainbow, @pinwheel.rotate!.first + "\b"))
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -153,8 +153,10 @@ module Bolt
|
|
153
153
|
Dir.children(path).select { |name| Puppet::Module.is_module_directory?(name, path) }
|
154
154
|
end
|
155
155
|
if modules.include?(project.name)
|
156
|
-
Bolt::Logger.warn_once(
|
157
|
-
|
156
|
+
Bolt::Logger.warn_once(
|
157
|
+
"project_shadows_module",
|
158
|
+
"The project '#{project.name}' shadows an existing module of the same name"
|
159
|
+
)
|
158
160
|
end
|
159
161
|
end
|
160
162
|
|
@@ -213,6 +215,7 @@ module Bolt
|
|
213
215
|
def with_bolt_executor(executor, inventory, pdb_client = nil, applicator = nil, &block)
|
214
216
|
setup
|
215
217
|
opts = {
|
218
|
+
bolt_project: @project,
|
216
219
|
bolt_executor: executor,
|
217
220
|
bolt_inventory: inventory,
|
218
221
|
bolt_pdb_client: pdb_client,
|
@@ -357,19 +360,52 @@ module Bolt
|
|
357
360
|
Bolt::Task.from_task_signature(task)
|
358
361
|
end
|
359
362
|
|
363
|
+
def list_plans_with_cache(filter_content: false)
|
364
|
+
# Don't filter content yet, so that if users update their plan filters
|
365
|
+
# we don't need to refresh the cache
|
366
|
+
plan_names = list_plans(filter_content: false).map(&:first)
|
367
|
+
plan_cache = if @project
|
368
|
+
Bolt::Util.read_optional_json_file(@project.plan_cache_file, 'Plan cache file')
|
369
|
+
else
|
370
|
+
{}
|
371
|
+
end
|
372
|
+
updated = false
|
373
|
+
|
374
|
+
plan_list = plan_names.each_with_object([]) do |plan_name, list|
|
375
|
+
info = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
|
376
|
+
|
377
|
+
# If the plan is a 'local' plan (in the project itself, or the
|
378
|
+
# modules/ directory) then verify it hasn't been updated since we
|
379
|
+
# cached it. If it has been updated, refresh the cache and use the
|
380
|
+
# new data.
|
381
|
+
if info['file'] &&
|
382
|
+
(File.mtime(info.dig('file', 'path')) <=> info.dig('file', 'mtime')) != 0
|
383
|
+
info = get_plan_info(plan_name, with_mtime: true)
|
384
|
+
updated = true
|
385
|
+
plan_cache[plan_name] = info
|
386
|
+
end
|
387
|
+
|
388
|
+
list << [plan_name, info['description']] unless info['private']
|
389
|
+
end
|
390
|
+
|
391
|
+
File.write(@project.plan_cache_file, plan_cache.to_json) if updated
|
392
|
+
|
393
|
+
filter_content ? filter_content(plan_list, @project&.plans) : plan_list
|
394
|
+
end
|
395
|
+
|
360
396
|
def list_plans(filter_content: false)
|
361
397
|
in_bolt_compiler do |compiler|
|
362
398
|
errors = []
|
363
399
|
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
364
400
|
errors.each do |error|
|
365
|
-
|
401
|
+
Bolt::Logger.warn("plan_load_error", error.details['original_error'])
|
366
402
|
end
|
367
403
|
|
368
404
|
filter_content ? filter_content(plans, @project&.plans) : plans
|
369
405
|
end
|
370
406
|
end
|
371
407
|
|
372
|
-
def get_plan_info(plan_name)
|
408
|
+
def get_plan_info(plan_name, with_mtime: false)
|
373
409
|
plan_sig = in_bolt_compiler do |compiler|
|
374
410
|
compiler.plan_signature(plan_name)
|
375
411
|
end
|
@@ -410,18 +446,30 @@ module Bolt
|
|
410
446
|
params[name] = { 'type' => param.types.first }
|
411
447
|
params[name]['sensitive'] = param.types.first =~ /\ASensitive(\[.*\])?\z/ ? true : false
|
412
448
|
params[name]['default_value'] = defaults[name] if defaults.key?(name)
|
413
|
-
params[name]['description'] = param.text
|
449
|
+
params[name]['description'] = param.text if param.text && !param.text.empty?
|
414
450
|
else
|
415
|
-
|
451
|
+
Bolt::Logger.warn(
|
452
|
+
"missing_plan_parameter",
|
453
|
+
"The documented parameter '#{name}' does not exist in signature for plan '#{plan.name}'"
|
454
|
+
)
|
416
455
|
end
|
417
456
|
end
|
418
457
|
|
419
|
-
|
420
|
-
|
458
|
+
privie = plan.tag(:private)&.text
|
459
|
+
unless privie.nil? || %w[true false].include?(privie.downcase)
|
460
|
+
msg = "Plan #{plan_name} key 'private' must be a boolean, received: #{privie}"
|
461
|
+
raise Bolt::Error.new(msg, 'bolt/invalid-plan')
|
462
|
+
end
|
463
|
+
|
464
|
+
pp_info = {
|
465
|
+
'name' => plan_name,
|
421
466
|
'description' => description,
|
422
|
-
'parameters'
|
423
|
-
'module'
|
467
|
+
'parameters' => parameters,
|
468
|
+
'module' => mod
|
424
469
|
}
|
470
|
+
pp_info.merge!({ 'private' => privie&.downcase == 'true' }) unless privie.nil?
|
471
|
+
pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
|
472
|
+
pp_info
|
425
473
|
|
426
474
|
# If it's a YAML plan, fall back to limited data
|
427
475
|
else
|
@@ -444,12 +492,32 @@ module Bolt
|
|
444
492
|
params[name]['default_value'] = param.value unless param.value.nil?
|
445
493
|
params[name]['description'] = param.description if param.description
|
446
494
|
end
|
447
|
-
|
448
|
-
|
495
|
+
|
496
|
+
yaml_info = {
|
497
|
+
'name' => plan_name,
|
449
498
|
'description' => plan.description,
|
450
|
-
'parameters'
|
451
|
-
'module'
|
499
|
+
'parameters' => parameters,
|
500
|
+
'module' => mod
|
452
501
|
}
|
502
|
+
yaml_info.merge!({ 'private' => plan.private }) unless plan.private.nil?
|
503
|
+
yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
|
504
|
+
yaml_info
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
def get_plan_mtime(path)
|
509
|
+
# If the plan is from the project modules/ directory, or is in the
|
510
|
+
# project itself, include the last mtime of the file so we can compare
|
511
|
+
# if the plan has been updated since it was cached.
|
512
|
+
if @project &&
|
513
|
+
File.exist?(path) &&
|
514
|
+
(path.include?(File.join(@project.path, 'modules')) ||
|
515
|
+
path.include?(@project.plans_path.to_s))
|
516
|
+
|
517
|
+
{ 'file' => { 'mtime' => File.mtime(path),
|
518
|
+
'path' => path } }
|
519
|
+
else
|
520
|
+
{}
|
453
521
|
end
|
454
522
|
end
|
455
523
|
|
@@ -490,16 +558,28 @@ module Bolt
|
|
490
558
|
end
|
491
559
|
end
|
492
560
|
|
493
|
-
def generate_types
|
561
|
+
def generate_types(cache: false)
|
494
562
|
require 'puppet/face/generate'
|
495
563
|
in_bolt_compiler do
|
496
564
|
generator = Puppet::Generate::Type
|
497
565
|
inputs = generator.find_inputs(:pcore)
|
498
566
|
FileUtils.mkdir_p(@resource_types)
|
567
|
+
cache_plan_info if @project && cache
|
499
568
|
generator.generate(inputs, @resource_types, true)
|
500
569
|
end
|
501
570
|
end
|
502
571
|
|
572
|
+
def cache_plan_info
|
573
|
+
# plan_name is an array here
|
574
|
+
plans_info = list_plans(filter_content: false).map do |plan_name,|
|
575
|
+
data = get_plan_info(plan_name, with_mtime: true)
|
576
|
+
{ plan_name => data }
|
577
|
+
end.reduce({}, :merge)
|
578
|
+
|
579
|
+
FileUtils.touch(@project.plan_cache_file)
|
580
|
+
File.write(@project.plan_cache_file, plans_info.to_json)
|
581
|
+
end
|
582
|
+
|
503
583
|
def run_task(task_name, targets, params, executor, inventory, description = nil)
|
504
584
|
in_task_compiler(executor, inventory) do |compiler|
|
505
585
|
params = params.merge('_bolt_api_call' => true, '_catch_errors' => true)
|
data/lib/bolt/pal/yaml_plan.rb
CHANGED
@@ -6,10 +6,10 @@ require 'bolt/pal/yaml_plan/step'
|
|
6
6
|
module Bolt
|
7
7
|
class PAL
|
8
8
|
class YamlPlan
|
9
|
-
PLAN_KEYS = Set['parameters', 'steps', 'return', 'version', 'description']
|
9
|
+
PLAN_KEYS = Set['parameters', 'private', 'steps', 'return', 'version', 'description']
|
10
10
|
VAR_NAME_PATTERN = /\A[a-z_][a-z0-9_]*\z/.freeze
|
11
11
|
|
12
|
-
attr_reader :name, :parameters, :steps, :return, :description
|
12
|
+
attr_reader :name, :parameters, :private, :steps, :return, :description
|
13
13
|
|
14
14
|
def initialize(name, plan)
|
15
15
|
# Top-level plan keys aren't allowed to be Puppet code, so force them
|
@@ -30,6 +30,12 @@ module Bolt
|
|
30
30
|
Parameter.new(param, definition)
|
31
31
|
end.freeze
|
32
32
|
|
33
|
+
@private = plan['private']
|
34
|
+
unless @private.nil? || @private.is_a?(TrueClass) || @private.is_a?(FalseClass)
|
35
|
+
msg = "Plan #{@name} key 'private' must be a boolean, received: #{@private.inspect}"
|
36
|
+
raise Bolt::Error.new(msg, "bolt/invalid-plan")
|
37
|
+
end
|
38
|
+
|
33
39
|
# Validate top level plan keys
|
34
40
|
top_level_keys = plan.keys.to_set
|
35
41
|
unless PLAN_KEYS.superset?(top_level_keys)
|
@@ -67,8 +73,7 @@ module Bolt
|
|
67
73
|
def duplicate_check(used_names, name, step_number)
|
68
74
|
if used_names.include?(name)
|
69
75
|
error_message = "Duplicate step name or parameter detected: #{name.inspect}"
|
70
|
-
|
71
|
-
raise Bolt::Error.new(err, "bolt/invalid-plan")
|
76
|
+
raise Step::StepError.new(error_message, name, step_number)
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
@@ -12,165 +12,15 @@ module Bolt
|
|
12
12
|
@evaluator = Puppet::Pops::Parser::EvaluatingParser.new
|
13
13
|
end
|
14
14
|
|
15
|
-
def dispatch_step(scope, step)
|
16
|
-
step_body = evaluate_code_blocks(scope, step.body)
|
17
|
-
|
18
|
-
# Dispatch based on the step class name
|
19
|
-
step_type = step.class.name.split('::').last.downcase
|
20
|
-
method = "#{step_type}_step"
|
21
|
-
|
22
|
-
send(method, scope, step_body)
|
23
|
-
end
|
24
|
-
|
25
|
-
def task_step(scope, step)
|
26
|
-
task = step['task']
|
27
|
-
targets = step['targets'] || step['target']
|
28
|
-
description = step['description']
|
29
|
-
params = step['parameters'] || {}
|
30
|
-
|
31
|
-
args = if description
|
32
|
-
[task, targets, description, params]
|
33
|
-
else
|
34
|
-
[task, targets, params]
|
35
|
-
end
|
36
|
-
|
37
|
-
scope.call_function('run_task', args)
|
38
|
-
end
|
39
|
-
|
40
|
-
def plan_step(scope, step)
|
41
|
-
plan = step['plan']
|
42
|
-
parameters = step['parameters'] || {}
|
43
|
-
|
44
|
-
args = [plan, parameters]
|
45
|
-
|
46
|
-
scope.call_function('run_plan', args)
|
47
|
-
end
|
48
|
-
|
49
|
-
def script_step(scope, step)
|
50
|
-
script = step['script']
|
51
|
-
targets = step['targets'] || step['target']
|
52
|
-
description = step['description']
|
53
|
-
arguments = step['arguments'] || []
|
54
|
-
|
55
|
-
options = { 'arguments' => arguments }
|
56
|
-
args = if description
|
57
|
-
[script, targets, description, options]
|
58
|
-
else
|
59
|
-
[script, targets, options]
|
60
|
-
end
|
61
|
-
|
62
|
-
scope.call_function('run_script', args)
|
63
|
-
end
|
64
|
-
|
65
|
-
def command_step(scope, step)
|
66
|
-
command = step['command']
|
67
|
-
targets = step['targets'] || step['target']
|
68
|
-
description = step['description']
|
69
|
-
|
70
|
-
args = [command, targets]
|
71
|
-
args << description if description
|
72
|
-
scope.call_function('run_command', args)
|
73
|
-
end
|
74
|
-
|
75
|
-
def upload_step(scope, step)
|
76
|
-
source = step['upload'] || step['source']
|
77
|
-
destination = step['destination']
|
78
|
-
targets = step['targets'] || step['target']
|
79
|
-
description = step['description']
|
80
|
-
|
81
|
-
args = [source, destination, targets]
|
82
|
-
args << description if description
|
83
|
-
scope.call_function('upload_file', args)
|
84
|
-
end
|
85
|
-
|
86
|
-
def download_step(scope, step)
|
87
|
-
source = step['download']
|
88
|
-
destination = step['destination']
|
89
|
-
targets = step['targets'] || step['target']
|
90
|
-
description = step['description']
|
91
|
-
|
92
|
-
args = [source, destination, targets]
|
93
|
-
args << description if description
|
94
|
-
scope.call_function('download_file', args)
|
95
|
-
end
|
96
|
-
|
97
|
-
def eval_step(_scope, step)
|
98
|
-
step['eval']
|
99
|
-
end
|
100
|
-
|
101
|
-
def resources_step(scope, step)
|
102
|
-
targets = step['targets'] || step['target']
|
103
|
-
|
104
|
-
# TODO: Only call apply_prep when needed
|
105
|
-
scope.call_function('apply_prep', targets)
|
106
|
-
manifest = generate_manifest(step['resources'])
|
107
|
-
|
108
|
-
apply_manifest(scope, targets, manifest)
|
109
|
-
end
|
110
|
-
|
111
|
-
def message_step(scope, step)
|
112
|
-
scope.call_function('out::message', [step['message']])
|
113
|
-
end
|
114
|
-
|
115
|
-
def generate_manifest(resources)
|
116
|
-
# inspect returns the Ruby representation of the resource hashes,
|
117
|
-
# which happens to be the same as the Puppet representation
|
118
|
-
puppet_resources = resources.inspect
|
119
|
-
|
120
|
-
# Because the :tasks setting globally controls which mode the parser
|
121
|
-
# is in, we need to make this snippet of non-tasks manifest code
|
122
|
-
# parseable in tasks mode. The way to do that is by putting it in an
|
123
|
-
# apply statement and taking the body.
|
124
|
-
<<~MANIFEST
|
125
|
-
apply('placeholder') {
|
126
|
-
$resources = #{puppet_resources}
|
127
|
-
$resources.each |$res| {
|
128
|
-
Resource[$res['type']] { $res['title']:
|
129
|
-
* => $res['parameters'],
|
130
|
-
}
|
131
|
-
}
|
132
|
-
|
133
|
-
# Add relationships if there is more than one resource
|
134
|
-
if $resources.length > 1 {
|
135
|
-
($resources.length - 1).each |$index| {
|
136
|
-
$lhs = $resources[$index]
|
137
|
-
$rhs = $resources[$index+1]
|
138
|
-
$lhs_resource = Resource[$lhs['type'] , $lhs['title']]
|
139
|
-
$rhs_resource = Resource[$rhs['type'] , $rhs['title']]
|
140
|
-
$lhs_resource -> $rhs_resource
|
141
|
-
}
|
142
|
-
}
|
143
|
-
}
|
144
|
-
MANIFEST
|
145
|
-
end
|
146
|
-
|
147
|
-
def apply_manifest(scope, targets, manifest)
|
148
|
-
ast = @evaluator.parse_string(manifest)
|
149
|
-
apply_block = ast.body.body
|
150
|
-
applicator = Puppet.lookup(:apply_executor)
|
151
|
-
applicator.apply([targets], apply_block, scope)
|
152
|
-
end
|
153
|
-
|
154
15
|
# This is the method that Puppet calls to evaluate the plan. The name
|
155
16
|
# makes more sense for .pp plans.
|
17
|
+
#
|
156
18
|
def evaluate_block_with_bindings(closure_scope, args_hash, plan)
|
157
|
-
if plan.steps.any? { |step| step.body.key?('target') }
|
158
|
-
msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
|
159
|
-
"in a future version of Bolt. Use the 'targets' parameter instead."
|
160
|
-
Bolt::Logger.deprecation_warning("Using 'target' parameter for YAML plan steps, not 'targets'", msg)
|
161
|
-
end
|
162
|
-
|
163
|
-
if plan.steps.any? { |step| step.body.key?('source') }
|
164
|
-
msg = "The 'source' parameter for YAML plan upload steps is deprecated and will be removed "\
|
165
|
-
"in a future version of Bolt. Use the 'upload' parameter instead."
|
166
|
-
Bolt::Logger.deprecation_warning("Using 'source' parameter for YAML upload steps, not 'upload'", msg)
|
167
|
-
end
|
168
|
-
|
169
19
|
plan_result = closure_scope.with_local_scope(args_hash) do |scope|
|
170
20
|
plan.steps.each do |step|
|
171
|
-
step_result =
|
21
|
+
step_result = step.evaluate(scope, self)
|
172
22
|
|
173
|
-
scope.setvar(step.name, step_result) if step.name
|
23
|
+
scope.setvar(step.body['name'], step_result) if step.body['name']
|
174
24
|
end
|
175
25
|
|
176
26
|
evaluate_code_blocks(scope, plan.return)
|
@@ -180,6 +30,7 @@ module Bolt
|
|
180
30
|
end
|
181
31
|
|
182
32
|
# Recursively evaluate any EvaluableString instances in the object.
|
33
|
+
#
|
183
34
|
def evaluate_code_blocks(scope, value)
|
184
35
|
# XXX We should establish a local scope here probably
|
185
36
|
case value
|
@@ -204,6 +55,7 @@ module Bolt
|
|
204
55
|
# Occasionally the Closure will ask us to evaluate what it assumes are
|
205
56
|
# AST objects. Because we've sidestepped the AST, they aren't, so just
|
206
57
|
# return the values as already evaluated.
|
58
|
+
#
|
207
59
|
def evaluate(value, _scope)
|
208
60
|
value
|
209
61
|
end
|