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.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +21 -19
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -5
  8. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
  10. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  11. data/lib/bolt/analytics.rb +3 -2
  12. data/lib/bolt/applicator.rb +11 -1
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -116
  15. data/lib/bolt/catalog.rb +10 -29
  16. data/lib/bolt/cli.rb +90 -154
  17. data/lib/bolt/config.rb +66 -239
  18. data/lib/bolt/config/options.rb +79 -102
  19. data/lib/bolt/config/transport/local.rb +1 -0
  20. data/lib/bolt/config/transport/lxd.rb +21 -0
  21. data/lib/bolt/config/transport/options.rb +9 -2
  22. data/lib/bolt/config/transport/orch.rb +1 -0
  23. data/lib/bolt/executor.rb +23 -6
  24. data/lib/bolt/inventory.rb +1 -1
  25. data/lib/bolt/inventory/group.rb +7 -4
  26. data/lib/bolt/logger.rb +123 -11
  27. data/lib/bolt/module_installer.rb +6 -4
  28. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  29. data/lib/bolt/module_installer/resolver.rb +59 -14
  30. data/lib/bolt/module_installer/specs/forge_spec.rb +10 -4
  31. data/lib/bolt/module_installer/specs/git_spec.rb +19 -4
  32. data/lib/bolt/outputter/human.rb +56 -17
  33. data/lib/bolt/outputter/json.rb +16 -16
  34. data/lib/bolt/outputter/rainbow.rb +3 -3
  35. data/lib/bolt/pal.rb +95 -15
  36. data/lib/bolt/pal/yaml_plan.rb +9 -4
  37. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  38. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  39. data/lib/bolt/pal/yaml_plan/step/command.rb +16 -16
  40. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  41. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  42. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  43. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  44. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  45. data/lib/bolt/pal/yaml_plan/step/script.rb +32 -17
  46. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  47. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  48. data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -1
  49. data/lib/bolt/plan_creator.rb +1 -1
  50. data/lib/bolt/plugin.rb +2 -2
  51. data/lib/bolt/plugin/cache.rb +7 -7
  52. data/lib/bolt/plugin/module.rb +0 -23
  53. data/lib/bolt/plugin/puppet_connect_data.rb +77 -0
  54. data/lib/bolt/plugin/puppetdb.rb +1 -1
  55. data/lib/bolt/project.rb +54 -81
  56. data/lib/bolt/project_manager.rb +5 -4
  57. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  58. data/lib/bolt/rerun.rb +1 -1
  59. data/lib/bolt/result.rb +6 -1
  60. data/lib/bolt/shell.rb +16 -0
  61. data/lib/bolt/shell/bash.rb +57 -25
  62. data/lib/bolt/shell/bash/tmpdir.rb +6 -3
  63. data/lib/bolt/shell/powershell.rb +33 -10
  64. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  65. data/lib/bolt/task.rb +2 -2
  66. data/lib/bolt/transport/base.rb +0 -9
  67. data/lib/bolt/transport/docker.rb +1 -125
  68. data/lib/bolt/transport/docker/connection.rb +86 -161
  69. data/lib/bolt/transport/local.rb +1 -9
  70. data/lib/bolt/transport/lxd.rb +26 -0
  71. data/lib/bolt/transport/lxd/connection.rb +99 -0
  72. data/lib/bolt/transport/orch/connection.rb +1 -1
  73. data/lib/bolt/transport/ssh.rb +1 -2
  74. data/lib/bolt/transport/ssh/connection.rb +2 -2
  75. data/lib/bolt/transport/winrm/connection.rb +1 -1
  76. data/lib/bolt/validator.rb +2 -2
  77. data/lib/bolt/version.rb +1 -1
  78. data/lib/bolt_server/config.rb +1 -1
  79. data/lib/bolt_server/transport_app.rb +61 -32
  80. data/lib/bolt_spec/bolt_context.rb +9 -4
  81. data/lib/bolt_spec/plans.rb +1 -109
  82. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  83. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  84. data/libexec/bolt_catalog +1 -1
  85. data/modules/aggregate/plans/count.pp +21 -0
  86. data/modules/aggregate/plans/targets.pp +21 -0
  87. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  88. data/modules/puppetdb_fact/plans/init.pp +10 -0
  89. metadata +13 -9
  90. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -32,8 +32,8 @@ module Bolt
32
32
  end
33
33
 
34
34
  def start_spin
35
- return unless @spin && @stream.isatty
36
- @spin = true
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 print_table(results, padding_left = 0, padding_right = 3)
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
- @stream.puts Terminal::Table.new(
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
- tasks.any? ? print_table(tasks) : print_message('No available tasks')
238
- print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
239
- "\nUse '#{command}' to view "\
240
- "details and parameters for a specific task.")
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
- plans.any? ? print_table(plans) : print_message('No available plans')
322
- print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
323
- "\nUse '#{command}' to view "\
324
- "details and parameters for a specific plan.")
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
- print_table(module_info, 2, 1)
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
- print_table(targets, 0, 2)
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
- "targets": targets.map(&:detail)
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)
@@ -95,38 +95,38 @@ module Bolt
95
95
  end
96
96
 
97
97
  def print_puppetfile_result(success, puppetfile, moduledir)
98
- @stream.puts({ "success": success,
99
- "puppetfile": puppetfile,
100
- "moduledir": moduledir.to_s }.to_json)
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
- "inventory": {
106
- "targets": target_list[:inventory].map(&:name),
107
- "count": target_list[:inventory].count,
108
- "file": inventoryfile.to_s
105
+ inventory: {
106
+ targets: target_list[:inventory].map(&:name),
107
+ count: target_list[:inventory].count,
108
+ file: inventoryfile.to_s
109
109
  },
110
- "adhoc": {
111
- "targets": target_list[:adhoc].map(&:name),
112
- "count": target_list[:adhoc].count
110
+ adhoc: {
111
+ targets: target_list[:adhoc].map(&:name),
112
+ count: target_list[:adhoc].count
113
113
  },
114
- "targets": target_list.values.flatten.map(&:name),
115
- "count": target_list.values.flatten.count
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
- "targets": targets.map(&:detail),
122
- "count": targets.count
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({ "groups": groups,
129
- "count": count }.to_json)
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
- @spin = true
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("project shadows module",
157
- "The project '#{project.name}' shadows an existing module of the same name")
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
- @logger.warn(error.details['original_error'])
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 unless param.text.empty?
449
+ params[name]['description'] = param.text if param.text && !param.text.empty?
414
450
  else
415
- @logger.warn("The documented parameter '#{name}' does not exist in plan signature")
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
- 'name' => plan_name,
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' => parameters,
423
- 'module' => mod
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
- 'name' => plan_name,
495
+
496
+ yaml_info = {
497
+ 'name' => plan_name,
449
498
  'description' => plan.description,
450
- 'parameters' => parameters,
451
- 'module' => mod
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)
@@ -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
- err = Step.step_error(error_message, name, step_number)
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 = dispatch_step(scope, step)
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