bolt 2.44.0 → 3.4.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +11 -9
  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/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -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/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -123
  15. data/lib/bolt/cli.rb +125 -127
  16. data/lib/bolt/config.rb +39 -214
  17. data/lib/bolt/config/options.rb +34 -125
  18. data/lib/bolt/config/transport/local.rb +1 -0
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +9 -2
  21. data/lib/bolt/executor.rb +20 -5
  22. data/lib/bolt/logger.rb +9 -1
  23. data/lib/bolt/module_installer.rb +2 -2
  24. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  25. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  26. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +52 -24
  29. data/lib/bolt/outputter/json.rb +16 -16
  30. data/lib/bolt/pal.rb +26 -5
  31. data/lib/bolt/pal/yaml_plan.rb +1 -2
  32. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  33. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  34. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  35. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  36. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  37. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  38. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  39. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  40. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  41. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  42. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  43. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  44. data/lib/bolt/plan_creator.rb +1 -1
  45. data/lib/bolt/plugin/module.rb +0 -23
  46. data/lib/bolt/plugin/puppet_connect_data.rb +45 -3
  47. data/lib/bolt/project.rb +16 -56
  48. data/lib/bolt/project_manager.rb +5 -4
  49. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  50. data/lib/bolt/result.rb +10 -11
  51. data/lib/bolt/shell.rb +16 -0
  52. data/lib/bolt/shell/bash.rb +61 -31
  53. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  54. data/lib/bolt/shell/powershell.rb +35 -14
  55. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  56. data/lib/bolt/task.rb +1 -1
  57. data/lib/bolt/transport/base.rb +0 -9
  58. data/lib/bolt/transport/docker.rb +1 -125
  59. data/lib/bolt/transport/docker/connection.rb +86 -161
  60. data/lib/bolt/transport/local.rb +1 -9
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +8 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  75. data/modules/aggregate/plans/count.pp +21 -0
  76. data/modules/aggregate/plans/targets.pp +21 -0
  77. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  78. data/modules/puppetdb_fact/plans/init.pp +10 -0
  79. metadata +7 -3
  80. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -11,18 +11,10 @@ module Bolt
11
11
  end
12
12
 
13
13
  def with_connection(target)
14
- if target.transport_config['bundled-ruby'] || target.name == 'localhost'
14
+ if target.transport_config['bundled-ruby']
15
15
  target.set_local_defaults
16
16
  end
17
17
 
18
- if target.name != 'localhost' &&
19
- !target.transport_config.key?('bundled-ruby')
20
- msg = "The local transport will default to using Bolt's Ruby interpreter and "\
21
- "setting the 'puppet-agent' feature in Bolt 3.0. Enable or disable these "\
22
- "defaults by setting 'bundled-ruby' in the local transport config."
23
- Bolt::Logger.warn_once("local_default_config", msg)
24
- end
25
-
26
18
  yield Connection.new(target)
27
19
  end
28
20
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/logger'
4
+ require 'bolt/node/errors'
5
+ require 'bolt/transport/simple'
6
+
7
+ module Bolt
8
+ module Transport
9
+ class LXD < Simple
10
+ def provided_features
11
+ ['shell']
12
+ end
13
+
14
+ def with_connection(target, options = {})
15
+ Bolt::Logger.warn_once("lxd_experimental",
16
+ "The LXD transport is experimental, and might "\
17
+ "include breaking changes between minor versions.")
18
+ conn = Connection.new(target, options)
19
+ conn.connect
20
+ yield conn
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'bolt/transport/lxd/connection'
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logging'
4
+ require 'bolt/node/errors'
5
+
6
+ module Bolt
7
+ module Transport
8
+ class LXD < Simple
9
+ class Connection
10
+ attr_reader :user, :target
11
+
12
+ def initialize(target, options)
13
+ raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
14
+
15
+ @target = target
16
+ @user = ENV['USER'] || Etc.getlogin
17
+ @options = options
18
+ @logger = Bolt::Logger.logger(target.safe_name)
19
+ @logger.trace("Initializing LXD connection to #{target.safe_name}")
20
+ end
21
+
22
+ def shell
23
+ Bolt::Shell::Bash.new(target, self)
24
+ end
25
+
26
+ def container_id
27
+ "#{@target.transport_config['remote']}:#{@target.host}"
28
+ end
29
+
30
+ def connect
31
+ out, err, status = execute_local_command(%W[list #{container_id} --format json])
32
+ unless status.exitstatus.zero?
33
+ raise "Error listing available containers: #{err}"
34
+ end
35
+ containers = JSON.parse(out)
36
+ if containers.empty?
37
+ raise "Could not find a container with name or ID matching '#{container_id}'"
38
+ end
39
+ @logger.trace("Opened session")
40
+ true
41
+ rescue StandardError => e
42
+ raise Bolt::Node::ConnectError.new(
43
+ "Failed to connect to #{container_id}: #{e.message}",
44
+ 'CONNECT_ERROR'
45
+ )
46
+ end
47
+
48
+ def add_env_vars(env_vars)
49
+ @env_vars = env_vars.each_with_object([]) do |env_var, acc|
50
+ acc << "--env"
51
+ acc << "#{env_var[0]}=#{Shellwords.shellescape(env_var[1])}"
52
+ end
53
+ end
54
+
55
+ def execute(command)
56
+ lxc_command = %w[lxc exec]
57
+ lxc_command += @env_vars if @env_vars
58
+ lxc_command += %W[#{container_id} -- sh -c #{Shellwords.shellescape(command)}]
59
+
60
+ @logger.trace { "Executing: #{lxc_command.join(' ')}" }
61
+ Open3.popen3(lxc_command.join(' '))
62
+ end
63
+
64
+ private def execute_local_command(command)
65
+ Open3.capture3('lxc', *command, { binmode: true })
66
+ end
67
+
68
+ def upload_file(source, destination)
69
+ @logger.trace { "Uploading #{source} to #{destination}" }
70
+ args = %w[--create-dirs]
71
+ if File.directory?(source)
72
+ args << '--recursive'
73
+ # If we don't do this, LXD will upload to
74
+ # /tmp/d2020-11/d2020-11/dir instead of /tmp/d2020-11/dir
75
+ destination = Pathname.new(destination).dirname.to_s
76
+ end
77
+ cmd = %w[file push] + args + %W[#{source} #{container_id}#{destination}]
78
+ _out, err, stat = execute_local_command(cmd)
79
+ unless stat.exitstatus.zero?
80
+ raise "Error writing to #{container_id}: #{err}"
81
+ end
82
+ rescue StandardError => e
83
+ raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
84
+ end
85
+
86
+ def download_file(source, destination, _download)
87
+ @logger.trace { "Downloading #{source} to #{destination}" }
88
+ FileUtils.mkdir_p(destination)
89
+ _out, err, stat = execute_local_command(%W[file pull --recursive #{container_id}#{source} #{destination}])
90
+ unless stat.exitstatus.zero?
91
+ raise "Error downloading content from container #{container_id}: #{err}"
92
+ end
93
+ rescue StandardError => e
94
+ raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -59,6 +59,18 @@ module Bolt
59
59
  # the result otherwise make sure an error is generated
60
60
  if state == 'finished' || (result && result['_error'])
61
61
  if result['_error']
62
+ unless result['_error'].is_a?(Hash)
63
+ result['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
64
+ 'issue_code' => 'TASK_ERROR',
65
+ 'msg' => result['_error'],
66
+ 'details' => {} }
67
+ end
68
+
69
+ result['_error']['details'] ||= {}
70
+ unless result['_error']['details'].is_a?(Hash)
71
+ deets = result['_error']['details']
72
+ result['_error']['details'] = { 'msg' => deets }
73
+ end
62
74
  file_line = %w[file line].zip(position).to_h.compact
63
75
  result['_error']['details'].merge!(file_line) unless result['_error']['details']['file']
64
76
  end
@@ -252,11 +264,7 @@ module Bolt
252
264
 
253
265
  # If we get here, there's no error so we don't need the file or line
254
266
  # number
255
- Bolt::Result.for_command(target,
256
- result.value['stdout'],
257
- result.value['stderr'],
258
- result.value['exit_code'],
259
- action, obj, [])
267
+ Bolt::Result.for_command(target, result.value, action, obj, [])
260
268
  end
261
269
  end
262
270
  end
@@ -230,7 +230,7 @@ module Bolt
230
230
  end
231
231
  [in_wr, out_rd, err_rd, th]
232
232
  rescue Errno::EMFILE => e
233
- msg = "#{e.message}. This may be resolved by increasing your user limit "\
233
+ msg = "#{e.message}. This might be resolved by increasing your user limit "\
234
234
  "with 'ulimit -n 1024'. See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
235
235
  raise Bolt::Error.new(msg, 'bolt/too-many-files')
236
236
  end
@@ -130,7 +130,7 @@ module Bolt
130
130
 
131
131
  [inp, out_rd, err_rd, th]
132
132
  rescue Errno::EMFILE => e
133
- msg = "#{e.message}. This may be resolved by increasing your user limit "\
133
+ msg = "#{e.message}. This might be resolved by increasing your user limit "\
134
134
  "with 'ulimit -n 1024'. See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
135
135
  raise Bolt::Error.new(msg, 'bolt/too-many-files')
136
136
  rescue StandardError
data/lib/bolt/util.rb CHANGED
@@ -77,6 +77,14 @@ module Bolt
77
77
  File.exist?(path) ? read_yaml_hash(path, file_name) : {}
78
78
  end
79
79
 
80
+ def first_runs_free
81
+ Bolt::Config.user_path + '.first_runs_free'
82
+ end
83
+
84
+ def first_run?
85
+ Bolt::Config.user_path && !File.exist?(first_runs_free)
86
+ end
87
+
80
88
  # Accepts a path with either 'plans' or 'tasks' in it and determines
81
89
  # the name of the module
82
90
  def module_name(path)
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.44.0'
4
+ VERSION = '3.4.0'
5
5
  end
@@ -133,7 +133,14 @@ module BoltServer
133
133
  task_data = body['task']
134
134
  task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
135
135
  parameters = body['parameters'] || {}
136
- [@executor.run_task(target, task, parameters), nil]
136
+ task_result = @executor.run_task(target, task, parameters)
137
+ task_result.each do |result|
138
+ value = result.value
139
+ next unless value.is_a?(Hash)
140
+ next unless value.key?('_sensitive')
141
+ value['_sensitive'] = value['_sensitive'].unwrap
142
+ end
143
+ [task_result, nil]
137
144
  end
138
145
 
139
146
  def run_command(target, body)
@@ -275,15 +282,19 @@ module BoltServer
275
282
  Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
276
283
  end
277
284
 
285
+ def pal_from_project_bolt_config(bolt_config)
286
+ modulepath_object = Bolt::Config::Modulepath.new(
287
+ bolt_config.modulepath,
288
+ boltlib_path: [PE_BOLTLIB_PATH, Bolt::Config::Modulepath::BOLTLIB_PATH],
289
+ builtin_content_path: @config['builtin-content-dir']
290
+ )
291
+ Bolt::PAL.new(modulepath_object, nil, nil, nil, nil, nil, bolt_config.project)
292
+ end
293
+
278
294
  def in_bolt_project(versioned_project)
279
295
  @pal_mutex.synchronize do
280
296
  bolt_config = config_from_project(versioned_project)
281
- modulepath_object = Bolt::Config::Modulepath.new(
282
- bolt_config.modulepath,
283
- boltlib_path: [PE_BOLTLIB_PATH, Bolt::Config::Modulepath::BOLTLIB_PATH],
284
- builtin_content_path: @config['builtin-content-dir']
285
- )
286
- pal = Bolt::PAL.new(modulepath_object, nil, nil, nil, nil, nil, bolt_config.project)
297
+ pal = pal_from_project_bolt_config(bolt_config)
287
298
  context = {
288
299
  pal: pal,
289
300
  config: bolt_config
@@ -351,8 +362,8 @@ module BoltServer
351
362
  }
352
363
  end
353
364
 
354
- def allowed_helper(metadata, allowlist)
355
- allowed = allowlist.nil? || allowlist.include?(metadata['name']) ? true : false
365
+ def allowed_helper(pal, metadata, allowlist)
366
+ allowed = !pal.filter_content([metadata['name']], allowlist).empty?
356
367
  metadata.merge({ 'allowed' => allowed })
357
368
  end
358
369
 
@@ -366,21 +377,27 @@ module BoltServer
366
377
  plans.map { |plan_name| { 'name' => plan_name } }
367
378
  end
368
379
 
369
- def file_metadatas(pal, module_name, file)
370
- pal.in_bolt_compiler do
371
- mod = Puppet.lookup(:current_environment).module(module_name)
372
- raise ArgumentError, "`module_name`: #{module_name} does not exist" unless mod
373
- abs_file_path = mod.file(file)
374
- raise ArgumentError, "`file`: #{file} does not exist inside the module's 'files' directory" unless abs_file_path
375
- fileset = Puppet::FileServing::Fileset.new(abs_file_path, 'recurse' => 'yes')
376
- Puppet::FileServing::Fileset.merge(fileset).collect do |relative_file_path, base_path|
377
- metadata = Puppet::FileServing::Metadata.new(base_path, relative_path: relative_file_path)
378
- metadata.checksum_type = 'sha256'
379
- metadata.links = 'follow'
380
- metadata.collect
381
- metadata.to_data_hash
380
+ def file_metadatas(versioned_project, module_name, file)
381
+ abs_file_path = @pal_mutex.synchronize do
382
+ bolt_config = config_from_project(versioned_project)
383
+ pal = pal_from_project_bolt_config(bolt_config)
384
+ pal.in_bolt_compiler do
385
+ mod = Puppet.lookup(:current_environment).module(module_name)
386
+ raise ArgumentError, "`module_name`: #{module_name} does not exist" unless mod
387
+ mod.file(file)
382
388
  end
383
389
  end
390
+
391
+ raise ArgumentError, "`file`: #{file} does not exist inside the module's 'files' directory" unless abs_file_path
392
+
393
+ fileset = Puppet::FileServing::Fileset.new(abs_file_path, 'recurse' => 'yes')
394
+ Puppet::FileServing::Fileset.merge(fileset).collect do |relative_file_path, base_path|
395
+ metadata = Puppet::FileServing::Metadata.new(base_path, relative_path: relative_file_path)
396
+ metadata.checksum_type = 'sha256'
397
+ metadata.links = 'follow'
398
+ metadata.collect
399
+ metadata.to_data_hash
400
+ end
384
401
  end
385
402
 
386
403
  get '/' do
@@ -520,7 +537,7 @@ module BoltServer
520
537
  return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
521
538
  in_bolt_project(params['versioned_project']) do |context|
522
539
  plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
523
- plan_info = allowed_helper(plan_info, context[:config].project.plans)
540
+ plan_info = allowed_helper(context[:pal], plan_info, context[:config].project.plans)
524
541
  [200, plan_info.to_json]
525
542
  end
526
543
  rescue Bolt::Error => e
@@ -550,7 +567,7 @@ module BoltServer
550
567
  'versioned_project' => params['versioned_project']
551
568
  }
552
569
  task_info = pe_task_info(context[:pal], params[:module_name], params[:task_name], ps_parameters)
553
- task_info = allowed_helper(task_info, context[:config].project.tasks)
570
+ task_info = allowed_helper(context[:pal], task_info, context[:config].project.tasks)
554
571
  [200, task_info.to_json]
555
572
  end
556
573
  rescue Bolt::Error => e
@@ -590,7 +607,7 @@ module BoltServer
590
607
  plans_response = plan_list(context[:pal])
591
608
 
592
609
  # Dig in context for the allowlist of plans from project object
593
- plans_response.map! { |metadata| allowed_helper(metadata, context[:config].project.plans) }
610
+ plans_response.map! { |metadata| allowed_helper(context[:pal], metadata, context[:config].project.plans) }
594
611
 
595
612
  # We structure this array of plans to be an array of hashes so that it matches the structure
596
613
  # returned by the puppetserver API that serves data like this. Structuring the output this way
@@ -626,7 +643,7 @@ module BoltServer
626
643
  tasks_response = task_list(context[:pal])
627
644
 
628
645
  # Dig in context for the allowlist of tasks from project object
629
- tasks_response.map! { |metadata| allowed_helper(metadata, context[:config].project.tasks) }
646
+ tasks_response.map! { |metadata| allowed_helper(context[:pal], metadata, context[:config].project.tasks) }
630
647
 
631
648
  # We structure this array of tasks to be an array of hashes so that it matches the structure
632
649
  # returned by the puppetserver API that serves data like this. Structuring the output this way
@@ -642,12 +659,11 @@ module BoltServer
642
659
  #
643
660
  # @param versioned_project [String] the versioned_project to fetch the file metadatas from
644
661
  get '/project_file_metadatas/:module_name/*' do
645
- return MISSING_VERSIONED_PROJECT_RESPONSE if params['versioned_project'].nil?
646
- in_bolt_project(params['versioned_project']) do |context|
647
- file = params[:splat].first
648
- metadatas = file_metadatas(context[:pal], params[:module_name], file)
649
- [200, metadatas.to_json]
650
- end
662
+ versioned_project = params['versioned_project']
663
+ return MISSING_VERSIONED_PROJECT_RESPONSE if versioned_project.nil?
664
+ file = params[:splat].first
665
+ metadatas = file_metadatas(versioned_project, params[:module_name], file)
666
+ [200, metadatas.to_json]
651
667
  rescue Bolt::Error => e
652
668
  [400, e.to_json]
653
669
  rescue ArgumentError => e
@@ -674,11 +690,23 @@ module BoltServer
674
690
  Bolt::Util.validate_file('inventory file', context[:config].project.inventory_file)
675
691
 
676
692
  begin
693
+ # Set the default puppet_library plugin hook if it has not already been
694
+ # set
695
+ context[:config].data['plugin-hooks']['puppet_library'] ||= {
696
+ 'plugin' => 'task',
697
+ 'task' => 'puppet_agent::install',
698
+ 'parameters' => {
699
+ 'stop_service' => true
700
+ }
701
+ }
702
+
677
703
  connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
678
704
  plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
679
705
  plugins.add_plugin(connect_plugin)
680
706
  inventory = Bolt::Inventory.from_config(context[:config], plugins)
681
- target_list = inventory.get_targets('all').map { |targ| targ.to_h.merge({ 'transport' => targ.transport }) }
707
+ target_list = inventory.get_targets('all').map do |targ|
708
+ targ.to_h.merge({ 'transport' => targ.transport, 'plugin_hooks' => targ.plugin_hooks })
709
+ end
682
710
  rescue Bolt::Plugin::PluginError::LoadingDisabled => e
683
711
  msg = "Cannot load plugin #{e.details['plugin_name']}: plugin not supported"
684
712
  raise BoltServer::Plugin::PluginNotSupported.new(msg, e.details['plugin_name'])
@@ -130,11 +130,16 @@ module BoltSpec
130
130
  @executor ||= BoltSpec::Plans::MockExecutor.new(modulepath)
131
131
  end
132
132
 
133
- # Override in your tests
133
+ # Overrides inventory for tests.
134
134
  def inventory_data
135
135
  {}
136
136
  end
137
137
 
138
+ # Overrides configuration for tests.
139
+ def config_data
140
+ {}
141
+ end
142
+
138
143
  def inventory
139
144
  @inventory ||= Bolt::Inventory.create_version(inventory_data, config.transport, config.transports, plugins)
140
145
  end
@@ -142,7 +147,7 @@ module BoltSpec
142
147
  # Override in your tests
143
148
  def config
144
149
  @config ||= begin
145
- conf = Bolt::Config.default
150
+ conf = Bolt::Config.new(Bolt::Project.default_project, config_data)
146
151
  conf.modulepath = [modulepath].flatten
147
152
  conf
148
153
  end
@@ -161,7 +166,7 @@ module BoltSpec
161
166
  BoltSpec::Plans::MOCKED_ACTIONS.each do |action|
162
167
  # Allowed action stubs can be called up to be_called_times number of times
163
168
  define_method :"allow_#{action}" do |object|
164
- executor.send(:"stub_#{action}", object).add_stub
169
+ executor.send(:"stub_#{action}", object).add_stub(inventory)
165
170
  end
166
171
 
167
172
  # Expected action stubs must be called exactly the expected number of times
@@ -172,7 +177,7 @@ module BoltSpec
172
177
 
173
178
  # This stub will catch any action call if there are no stubs specifically for that task
174
179
  define_method :"allow_any_#{action}" do
175
- executor.send(:"stub_#{action}", :default).add_stub
180
+ executor.send(:"stub_#{action}", :default).add_stub(inventory)
176
181
  end
177
182
  end
178
183
 
@@ -5,119 +5,11 @@ require 'bolt_spec/plans/mock_executor'
5
5
  require 'bolt/pal'
6
6
 
7
7
  # These helpers are intended to be used for plan unit testing without calling
8
- # out to target nodes. It uses the BoltContext helper to set up a mock executor
8
+ # out to targets. It uses the BoltContext helper to set up a mock executor
9
9
  # which allows calls to run_* functions to be stubbed for testing. The context
10
10
  # helper also loads Bolt datatypes and plan functions to be used by the code
11
11
  # being tested.
12
12
  #
13
- # Stub matching
14
- #
15
- # Stubs match invocations of run_* functions by default matching any call but
16
- # with_targets and with_params helpers can further restrict the stub to match
17
- # more exact invocations. It's possible a call to run_* could match multiple
18
- # stubs. In this case the mock executor will first check for stubs specifically
19
- # matching the task being run after which it will use the last stub that
20
- # matched
21
- #
22
- #
23
- # allow vs expect
24
- #
25
- # Stubs have two general modes bases on whether the test is making assertions
26
- # on whether function was called. Allow stubs allow the run_* invocation to
27
- # be called any number of times while expect stubs will fail if no run_*
28
- # invocation matches them. The be_called_times(n) stub method can be used to
29
- # ensure an allow stub is not called more than n times or that an expect stub
30
- # is called exactly n times.
31
- #
32
- # Configuration
33
- #
34
- # To configure Puppet and Bolt at the beginning of tests, add the following
35
- # line to your spec_helper.rb:
36
- #
37
- # BoltSpec::Plans.init
38
- #
39
- # By default the plan helpers use the modulepath set up for rspec-puppet and
40
- # an otherwise empty bolt config and inventory. To create your own values for
41
- # these override the modulepath, config, or inventory methods.
42
- #
43
- # Sub-plan Execution
44
- #
45
- # When testing a plan, often times those plans call other plans in order to
46
- # build complex workflows. To support this we offer running in two different
47
- # modes:
48
- # execute_any_plan (default) - This mode will execute any plan that is encountered
49
- # without having to be stubbed/mocked. This default mode allows for plan control
50
- # flow to behave as normal. If you choose to stub/mock out a sub-plan in this mode
51
- # that will be honored and the sub-plan will not be executed. We will use the modifiers
52
- # on the stub to check for the conditions specified (example: be_called_times(3))
53
- #
54
- # execute_no_plan - This mode will not execute a plans that it encounters. Instead, when
55
- # a plan is encountered it will throw an error unless the plan is mocked out. This
56
- # mode is useful for ensuring that there are no plans called that you do not expect.
57
- # This plan requires authors to mock out all sub-plans that may be invoked when running
58
- # tests.
59
- #
60
- # TODO:
61
- # - Allow description based stub matching
62
- # - Better testing of plan errors
63
- # - Better error collection around call counts. Show what stubs exists and more than a single failure
64
- # - Allow stubbing with a block(at the double level? As a matched stub?)
65
- # - package code so that it can be used for testing modules outside of this repo
66
- # - set subject from describe and provide matchers similar to rspec puppets function tests
67
- # - Allow specific plans to be executed when running in execute_no_plan mode.
68
- #
69
- # MAYBE TODO?:
70
- # - validate call expectations at the end of the example instead of in run_plan
71
- # - resultset matchers to help testing canary like plans?
72
- # - inventory matchers to help testing plans that change inventory
73
- #
74
- # Flags:
75
- # - execute_any_plan: execute any plan that is encountered unless it is mocked (default)
76
- # - execute_no_plan: throw an error if a plan is encountered that is not stubbed
77
- #
78
- # Stubs:
79
- # - allow_command(cmd), expect_command(cmd): expect the exact command
80
- # - allow_plan(plan), expect_plan(plan): expect the named plan
81
- # - allow_script(script), expect_script(script): expect the script as <module>/path/to/file
82
- # - allow_task(task), expect_task(task): expect the named task
83
- # - allow_download(file), expect_download(file): expect the identified source file
84
- # - allow_upload(file), expect_upload(file): expect the identified source file
85
- # - allow_apply_prep: allows `apply_prep` to be invoked in the plan but does not allow modifiers
86
- # - allow_apply: allows `apply` to be invoked in the plan but does not allow modifiers
87
- # - allow_out_message, expect_out_message: expect a message to be passed to out::message (only modifiers are
88
- # be_called_times(n), with_params(params), and not_be_called)
89
- #
90
- # Stub modifiers:
91
- # - be_called_times(n): if allowed, fail if the action is called more than 'n' times
92
- # if expected, fail unless the action is called 'n' times
93
- # - not_be_called: fail if the action is called
94
- # - with_targets(targets): target or list of targets that you expect to be passed to the action
95
- # plan: does not support this modifier
96
- # - with_params(params): list of params and metaparams (or options) that you expect to be passed to the action.
97
- # Corresponds to the action's last argument.
98
- # - with_destination(dest): for upload_file and download_file, the expected destination path
99
- # - always_return(value): return a Bolt::ResultSet of Bolt::Result objects with the specified value Hash
100
- # plan: returns a Bolt::PlanResult with the specified value with a status of 'success'
101
- # command and script: only accept 'stdout' and 'stderr' keys
102
- # upload: does not support this modifier
103
- # download: does not support this modifier
104
- # - return_for_targets(targets_to_values): return a Bolt::ResultSet of Bolt::Result objects from the Hash mapping
105
- # targets to their value Hashes
106
- # command and script: only accept 'stdout' and 'stderr' keys
107
- # upload: does not support this modifier
108
- # download: does not support this modifier
109
- # plan: does not support this modifier
110
- # - return(&block): invoke the block to construct a Bolt::ResultSet. The blocks parameters differ based on action
111
- # command: `{ |targets:, command:, params:| ... }`
112
- # plan: `{ |plan:, params:| ... }`
113
- # script: `{ |targets:, script:, params:| ... }`
114
- # task: `{ |targets:, task:, params:| ... }`
115
- # upload: `{ |targets:, source:, destination:, params:| ... }`
116
- # download: `{ |targets:, source:, destination:, params:| ... }`
117
- # - error_with(err): return a failing Bolt::ResultSet, with Bolt::Result objects with the identified err hash
118
- # plans will throw a Bolt::PlanFailure that will be returned as the value of
119
- # the Bolt::PlanResult object with a status of 'failure'.
120
- #
121
13
  # Example:
122
14
  # describe "my_plan" do
123
15
  # it 'should return' do