bolt 2.38.0 → 3.0.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 +17 -17
- 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/wait_until_available.rb +7 -3
- data/lib/bolt/analytics.rb +3 -2
- data/lib/bolt/applicator.rb +11 -1
- data/lib/bolt/bolt_option_parser.rb +3 -113
- data/lib/bolt/catalog.rb +10 -29
- data/lib/bolt/cli.rb +54 -155
- data/lib/bolt/config.rb +63 -269
- data/lib/bolt/config/options.rb +59 -97
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +10 -2
- data/lib/bolt/config/transport/orch.rb +1 -0
- data/lib/bolt/config/transport/ssh.rb +0 -5
- data/lib/bolt/executor.rb +15 -5
- data/lib/bolt/inventory.rb +3 -2
- data/lib/bolt/inventory/group.rb +35 -12
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/logger.rb +115 -11
- data/lib/bolt/module.rb +10 -2
- data/lib/bolt/module_installer.rb +4 -2
- data/lib/bolt/module_installer/resolver.rb +65 -12
- data/lib/bolt/module_installer/specs/forge_spec.rb +8 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +17 -2
- data/lib/bolt/outputter/human.rb +9 -5
- data/lib/bolt/outputter/json.rb +16 -16
- data/lib/bolt/outputter/rainbow.rb +3 -3
- data/lib/bolt/pal.rb +93 -14
- data/lib/bolt/pal/yaml_plan.rb +8 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +7 -19
- data/lib/bolt/pal/yaml_plan/step.rb +3 -24
- data/lib/bolt/pal/yaml_plan/step/upload.rb +2 -2
- data/lib/bolt/pal/yaml_plan/transpiler.rb +6 -1
- data/lib/bolt/plugin.rb +3 -3
- data/lib/bolt/plugin/cache.rb +8 -8
- 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 +4 -3
- data/lib/bolt/project_manager/module_migrator.rb +6 -5
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/shell/bash/tmpdir.rb +4 -1
- data/lib/bolt/shell/powershell.rb +3 -4
- data/lib/bolt/shell/powershell/snippets.rb +9 -149
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +2 -2
- data/lib/bolt/transport/local.rb +1 -9
- data/lib/bolt/transport/orch/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/validator.rb +16 -15
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/schemas/partials/task.json +1 -1
- data/lib/bolt_server/transport_app.rb +3 -2
- 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 +31 -0
- data/modules/puppetdb_fact/plans/init.pp +10 -0
- metadata +26 -17
- 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
|
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
|
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
|
|
@@ -81,6 +81,10 @@ module Bolt
|
|
81
81
|
print_plan_start(event)
|
82
82
|
when :plan_finish
|
83
83
|
print_plan_finish(event)
|
84
|
+
when :start_spin
|
85
|
+
start_spin
|
86
|
+
when :stop_spin
|
87
|
+
stop_spin
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
@@ -388,7 +392,7 @@ module Bolt
|
|
388
392
|
|
389
393
|
def print_target_info(targets)
|
390
394
|
@stream.puts ::JSON.pretty_generate(
|
391
|
-
|
395
|
+
targets: targets.map(&:detail)
|
392
396
|
)
|
393
397
|
count = "#{targets.count} target#{'s' unless targets.count == 1}"
|
394
398
|
@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
|
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
|
|
@@ -357,19 +359,52 @@ module Bolt
|
|
357
359
|
Bolt::Task.from_task_signature(task)
|
358
360
|
end
|
359
361
|
|
362
|
+
def list_plans_with_cache(filter_content: false)
|
363
|
+
# Don't filter content yet, so that if users update their plan filters
|
364
|
+
# we don't need to refresh the cache
|
365
|
+
plan_names = list_plans(filter_content: false).map(&:first)
|
366
|
+
plan_cache = if @project
|
367
|
+
Bolt::Util.read_optional_json_file(@project.plan_cache_file, 'Plan cache file')
|
368
|
+
else
|
369
|
+
{}
|
370
|
+
end
|
371
|
+
updated = false
|
372
|
+
|
373
|
+
plan_list = plan_names.each_with_object([]) do |plan_name, list|
|
374
|
+
info = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
|
375
|
+
|
376
|
+
# If the plan is a 'local' plan (in the project itself, or the
|
377
|
+
# modules/ directory) then verify it hasn't been updated since we
|
378
|
+
# cached it. If it has been updated, refresh the cache and use the
|
379
|
+
# new data.
|
380
|
+
if info['file'] &&
|
381
|
+
(File.mtime(info.dig('file', 'path')) <=> info.dig('file', 'mtime')) != 0
|
382
|
+
info = get_plan_info(plan_name, with_mtime: true)
|
383
|
+
updated = true
|
384
|
+
plan_cache[plan_name] = info
|
385
|
+
end
|
386
|
+
|
387
|
+
list << [plan_name] unless info['private']
|
388
|
+
end
|
389
|
+
|
390
|
+
File.write(@project.plan_cache_file, plan_cache.to_json) if updated
|
391
|
+
|
392
|
+
filter_content ? filter_content(plan_list, @project&.plans) : plan_list
|
393
|
+
end
|
394
|
+
|
360
395
|
def list_plans(filter_content: false)
|
361
396
|
in_bolt_compiler do |compiler|
|
362
397
|
errors = []
|
363
398
|
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
364
399
|
errors.each do |error|
|
365
|
-
|
400
|
+
Bolt::Logger.warn("plan_load_error", error.details['original_error'])
|
366
401
|
end
|
367
402
|
|
368
403
|
filter_content ? filter_content(plans, @project&.plans) : plans
|
369
404
|
end
|
370
405
|
end
|
371
406
|
|
372
|
-
def get_plan_info(plan_name)
|
407
|
+
def get_plan_info(plan_name, with_mtime: false)
|
373
408
|
plan_sig = in_bolt_compiler do |compiler|
|
374
409
|
compiler.plan_signature(plan_name)
|
375
410
|
end
|
@@ -412,16 +447,28 @@ module Bolt
|
|
412
447
|
params[name]['default_value'] = defaults[name] if defaults.key?(name)
|
413
448
|
params[name]['description'] = param.text unless param.text.empty?
|
414
449
|
else
|
415
|
-
|
450
|
+
Bolt::Logger.warn(
|
451
|
+
"missing_plan_parameter",
|
452
|
+
"The documented parameter '#{name}' does not exist in signature for plan '#{plan.name}'"
|
453
|
+
)
|
416
454
|
end
|
417
455
|
end
|
418
456
|
|
419
|
-
|
420
|
-
|
457
|
+
privie = plan.tag(:private)&.text
|
458
|
+
unless privie.nil? || %w[true false].include?(privie.downcase)
|
459
|
+
msg = "Plan #{plan_name} key 'private' must be a boolean, received: #{privie}"
|
460
|
+
raise Bolt::Error.new(msg, 'bolt/invalid-plan')
|
461
|
+
end
|
462
|
+
|
463
|
+
pp_info = {
|
464
|
+
'name' => plan_name,
|
421
465
|
'description' => description,
|
422
|
-
'parameters'
|
423
|
-
'module'
|
466
|
+
'parameters' => parameters,
|
467
|
+
'module' => mod
|
424
468
|
}
|
469
|
+
pp_info.merge!({ 'private' => privie&.downcase == 'true' }) unless privie.nil?
|
470
|
+
pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
|
471
|
+
pp_info
|
425
472
|
|
426
473
|
# If it's a YAML plan, fall back to limited data
|
427
474
|
else
|
@@ -444,12 +491,32 @@ module Bolt
|
|
444
491
|
params[name]['default_value'] = param.value unless param.value.nil?
|
445
492
|
params[name]['description'] = param.description if param.description
|
446
493
|
end
|
447
|
-
|
448
|
-
|
494
|
+
|
495
|
+
yaml_info = {
|
496
|
+
'name' => plan_name,
|
449
497
|
'description' => plan.description,
|
450
|
-
'parameters'
|
451
|
-
'module'
|
498
|
+
'parameters' => parameters,
|
499
|
+
'module' => mod
|
452
500
|
}
|
501
|
+
yaml_info.merge!({ 'private' => plan.private }) unless plan.private.nil?
|
502
|
+
yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
|
503
|
+
yaml_info
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def get_plan_mtime(path)
|
508
|
+
# If the plan is from the project modules/ directory, or is in the
|
509
|
+
# project itself, include the last mtime of the file so we can compare
|
510
|
+
# if the plan has been updated since it was cached.
|
511
|
+
if @project &&
|
512
|
+
File.exist?(path) &&
|
513
|
+
(path.include?(File.join(@project.path, 'modules')) ||
|
514
|
+
path.include?(@project.plans_path.to_s))
|
515
|
+
|
516
|
+
{ 'file' => { 'mtime' => File.mtime(path),
|
517
|
+
'path' => path } }
|
518
|
+
else
|
519
|
+
{}
|
453
520
|
end
|
454
521
|
end
|
455
522
|
|
@@ -490,16 +557,28 @@ module Bolt
|
|
490
557
|
end
|
491
558
|
end
|
492
559
|
|
493
|
-
def generate_types
|
560
|
+
def generate_types(cache: false)
|
494
561
|
require 'puppet/face/generate'
|
495
562
|
in_bolt_compiler do
|
496
563
|
generator = Puppet::Generate::Type
|
497
564
|
inputs = generator.find_inputs(:pcore)
|
498
565
|
FileUtils.mkdir_p(@resource_types)
|
566
|
+
cache_plan_info if @project && cache
|
499
567
|
generator.generate(inputs, @resource_types, true)
|
500
568
|
end
|
501
569
|
end
|
502
570
|
|
571
|
+
def cache_plan_info
|
572
|
+
# plan_name is an array here
|
573
|
+
plans_info = list_plans(filter_content: false).map do |plan_name,|
|
574
|
+
data = get_plan_info(plan_name, with_mtime: true)
|
575
|
+
{ plan_name => data }
|
576
|
+
end.reduce({}, :merge)
|
577
|
+
|
578
|
+
FileUtils.touch(@project.plan_cache_file)
|
579
|
+
File.write(@project.plan_cache_file, plans_info.to_json)
|
580
|
+
end
|
581
|
+
|
503
582
|
def run_task(task_name, targets, params, executor, inventory, description = nil)
|
504
583
|
in_task_compiler(executor, inventory) do |compiler|
|
505
584
|
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)
|
@@ -24,7 +24,7 @@ module Bolt
|
|
24
24
|
|
25
25
|
def task_step(scope, step)
|
26
26
|
task = step['task']
|
27
|
-
targets = step['targets']
|
27
|
+
targets = step['targets']
|
28
28
|
description = step['description']
|
29
29
|
params = step['parameters'] || {}
|
30
30
|
|
@@ -48,7 +48,7 @@ module Bolt
|
|
48
48
|
|
49
49
|
def script_step(scope, step)
|
50
50
|
script = step['script']
|
51
|
-
targets = step['targets']
|
51
|
+
targets = step['targets']
|
52
52
|
description = step['description']
|
53
53
|
arguments = step['arguments'] || []
|
54
54
|
|
@@ -64,7 +64,7 @@ module Bolt
|
|
64
64
|
|
65
65
|
def command_step(scope, step)
|
66
66
|
command = step['command']
|
67
|
-
targets = step['targets']
|
67
|
+
targets = step['targets']
|
68
68
|
description = step['description']
|
69
69
|
|
70
70
|
args = [command, targets]
|
@@ -73,9 +73,9 @@ module Bolt
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def upload_step(scope, step)
|
76
|
-
source = step['upload']
|
76
|
+
source = step['upload']
|
77
77
|
destination = step['destination']
|
78
|
-
targets = step['targets']
|
78
|
+
targets = step['targets']
|
79
79
|
description = step['description']
|
80
80
|
|
81
81
|
args = [source, destination, targets]
|
@@ -86,7 +86,7 @@ module Bolt
|
|
86
86
|
def download_step(scope, step)
|
87
87
|
source = step['download']
|
88
88
|
destination = step['destination']
|
89
|
-
targets = step['targets']
|
89
|
+
targets = step['targets']
|
90
90
|
description = step['description']
|
91
91
|
|
92
92
|
args = [source, destination, targets]
|
@@ -99,7 +99,7 @@ module Bolt
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def resources_step(scope, step)
|
102
|
-
targets = step['targets']
|
102
|
+
targets = step['targets']
|
103
103
|
|
104
104
|
# TODO: Only call apply_prep when needed
|
105
105
|
scope.call_function('apply_prep', targets)
|
@@ -154,18 +154,6 @@ module Bolt
|
|
154
154
|
# This is the method that Puppet calls to evaluate the plan. The name
|
155
155
|
# makes more sense for .pp plans.
|
156
156
|
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
157
|
plan_result = closure_scope.with_local_scope(args_hash) do |scope|
|
170
158
|
plan.steps.each do |step|
|
171
159
|
step_result = dispatch_step(scope, step)
|
@@ -9,19 +9,17 @@ module Bolt
|
|
9
9
|
attr_reader :name, :type, :body, :targets
|
10
10
|
|
11
11
|
def self.allowed_keys
|
12
|
-
Set['name', 'description', '
|
12
|
+
Set['name', 'description', 'targets']
|
13
13
|
end
|
14
14
|
|
15
15
|
STEP_KEYS = %w[
|
16
16
|
command
|
17
|
-
destination
|
18
17
|
download
|
19
18
|
eval
|
20
19
|
message
|
21
20
|
plan
|
22
21
|
resources
|
23
22
|
script
|
24
|
-
source
|
25
23
|
task
|
26
24
|
upload
|
27
25
|
].freeze
|
@@ -34,13 +32,7 @@ module Bolt
|
|
34
32
|
when 1
|
35
33
|
type = type_keys.first
|
36
34
|
else
|
37
|
-
|
38
|
-
type = 'upload'
|
39
|
-
elsif type_keys.to_set == Set['download', 'destination']
|
40
|
-
type = 'download'
|
41
|
-
else
|
42
|
-
raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
|
43
|
-
end
|
35
|
+
raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
|
44
36
|
end
|
45
37
|
|
46
38
|
step_class = const_get("Bolt::PAL::YamlPlan::Step::#{type.capitalize}")
|
@@ -51,7 +43,7 @@ module Bolt
|
|
51
43
|
def initialize(step_body)
|
52
44
|
@name = step_body['name']
|
53
45
|
@description = step_body['description']
|
54
|
-
@targets = step_body['targets']
|
46
|
+
@targets = step_body['targets']
|
55
47
|
@body = step_body
|
56
48
|
end
|
57
49
|
|
@@ -96,19 +88,6 @@ module Bolt
|
|
96
88
|
# Ensure all required keys are present
|
97
89
|
missing_keys = required_keys - body.keys
|
98
90
|
|
99
|
-
# Handle cases where steps with a required 'targets' key are using the deprecated
|
100
|
-
# 'target' key instead.
|
101
|
-
# TODO: Remove this when 'target' is removed
|
102
|
-
if body.include?('target')
|
103
|
-
missing_keys -= ['targets']
|
104
|
-
end
|
105
|
-
|
106
|
-
# Handle cases where upload step uses deprecated 'source' key instead of 'upload'
|
107
|
-
# TODO: Remove when 'source' is removed
|
108
|
-
if body.include?('source')
|
109
|
-
missing_keys -= ['upload']
|
110
|
-
end
|
111
|
-
|
112
91
|
if missing_keys.any?
|
113
92
|
error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
|
114
93
|
err = step_error(error_message, body['name'], step_number)
|
@@ -6,7 +6,7 @@ module Bolt
|
|
6
6
|
class Step
|
7
7
|
class Upload < Step
|
8
8
|
def self.allowed_keys
|
9
|
-
super + Set['
|
9
|
+
super + Set['destination', 'upload']
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.required_keys
|
@@ -15,7 +15,7 @@ module Bolt
|
|
15
15
|
|
16
16
|
def initialize(step_body)
|
17
17
|
super
|
18
|
-
@source = step_body['upload']
|
18
|
+
@source = step_body['upload']
|
19
19
|
@destination = step_body['destination']
|
20
20
|
end
|
21
21
|
|