bolt 3.3.0 → 3.7.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +5 -5
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +19 -2
  8. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  9. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  10. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  11. data/guides/targets.txt +31 -0
  12. data/lib/bolt/analytics.rb +4 -8
  13. data/lib/bolt/bolt_option_parser.rb +35 -17
  14. data/lib/bolt/cli.rb +109 -28
  15. data/lib/bolt/config.rb +11 -7
  16. data/lib/bolt/config/options.rb +41 -9
  17. data/lib/bolt/config/transport/lxd.rb +3 -1
  18. data/lib/bolt/config/transport/options.rb +7 -0
  19. data/lib/bolt/config/transport/podman.rb +33 -0
  20. data/lib/bolt/container_result.rb +105 -0
  21. data/lib/bolt/error.rb +15 -0
  22. data/lib/bolt/executor.rb +27 -15
  23. data/lib/bolt/inventory.rb +5 -4
  24. data/lib/bolt/inventory/inventory.rb +3 -2
  25. data/lib/bolt/inventory/options.rb +9 -0
  26. data/lib/bolt/inventory/target.rb +16 -0
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +243 -84
  29. data/lib/bolt/outputter/json.rb +6 -4
  30. data/lib/bolt/outputter/logger.rb +17 -0
  31. data/lib/bolt/pal.rb +22 -2
  32. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  33. data/lib/bolt/pal/yaml_plan/step/command.rb +8 -0
  34. data/lib/bolt/pal/yaml_plan/step/script.rb +4 -0
  35. data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -2
  36. data/lib/bolt/plan_creator.rb +2 -2
  37. data/lib/bolt/plugin.rb +13 -11
  38. data/lib/bolt/puppetdb/client.rb +54 -0
  39. data/lib/bolt/result.rb +5 -14
  40. data/lib/bolt/shell/bash.rb +33 -22
  41. data/lib/bolt/shell/powershell.rb +6 -8
  42. data/lib/bolt/transport/docker.rb +1 -1
  43. data/lib/bolt/transport/docker/connection.rb +21 -32
  44. data/lib/bolt/transport/lxd/connection.rb +5 -5
  45. data/lib/bolt/transport/orch.rb +13 -5
  46. data/lib/bolt/transport/podman.rb +19 -0
  47. data/lib/bolt/transport/podman/connection.rb +98 -0
  48. data/lib/bolt/util.rb +42 -0
  49. data/lib/bolt/version.rb +1 -1
  50. data/lib/bolt_server/transport_app.rb +3 -0
  51. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  52. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  53. data/lib/bolt_spec/plans/mock_executor.rb +91 -11
  54. data/modules/puppet_connect/plans/test_input_data.pp +22 -0
  55. metadata +11 -2
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'shellwords'
5
- require 'bolt/transport/base'
5
+ require 'bolt/transport/simple'
6
6
 
7
7
  module Bolt
8
8
  module Transport
@@ -34,6 +34,15 @@ module Bolt
34
34
  @container_info["Id"]
35
35
  end
36
36
 
37
+ def run_cmd(cmd, env_vars)
38
+ Bolt::Util.exec_docker(cmd, env_vars)
39
+ end
40
+
41
+ private def env_hash
42
+ # Set the DOCKER_HOST if we are using a non-default service-url
43
+ @docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
44
+ end
45
+
37
46
  def connect
38
47
  # We don't actually have a connection, but we do need to
39
48
  # check that the container exists and is running.
@@ -54,10 +63,7 @@ module Bolt
54
63
  end
55
64
 
56
65
  def add_env_vars(env_vars)
57
- @env_vars = env_vars.each_with_object([]) do |env_var, acc|
58
- acc << "--env"
59
- acc << "#{env_var[0]}=#{env_var[1]}"
60
- end
66
+ @env_vars = Bolt::Util.format_env_vars_for_cli(env_vars)
61
67
  end
62
68
 
63
69
  # Executes a command inside the target container. This is called from the shell class.
@@ -88,9 +94,9 @@ module Bolt
88
94
 
89
95
  def upload_file(source, destination)
90
96
  @logger.trace { "Uploading #{source} to #{destination}" }
91
- _stdout, stderr, status = execute_local_command('cp', [source, "#{container_id}:#{destination}"])
92
- unless status.exitstatus.zero?
93
- raise "Error writing to container #{container_id}: #{stderr}"
97
+ _out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
98
+ unless stat.exitstatus.zero?
99
+ raise "Error writing to container #{container_id}: #{err}"
94
100
  end
95
101
  rescue StandardError => e
96
102
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
@@ -102,31 +108,14 @@ module Bolt
102
108
  # copy the *contents* of the directory.
103
109
  # https://docs.docker.com/engine/reference/commandline/cp/
104
110
  FileUtils.mkdir_p(destination)
105
- _stdout, stderr, status = execute_local_command('cp', ["#{container_id}:#{source}", destination])
106
- unless status.exitstatus.zero?
107
- raise "Error downloading content from container #{container_id}: #{stderr}"
111
+ _out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
112
+ unless stat.exitstatus.zero?
113
+ raise "Error downloading content from container #{container_id}: #{err}"
108
114
  end
109
115
  rescue StandardError => e
110
116
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
111
117
  end
112
118
 
113
- # Executes a Docker CLI command. This is useful for running commands as
114
- # part of this class without having to go through the `execute`
115
- # function and manage pipes.
116
- #
117
- # @param subcommand [String] The docker subcommand to run
118
- # e.g. 'inspect' for `docker inspect`
119
- # @param arguments [Array] Arguments to pass to the docker command
120
- # e.g. 'src' and 'dest' for `docker cp <src> <dest>
121
- # @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
122
- private def execute_local_command(subcommand, arguments = [])
123
- # Set the DOCKER_HOST if we are using a non-default service-url
124
- env_hash = @docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
125
- docker_command = [subcommand].concat(arguments)
126
-
127
- Open3.capture3(env_hash, 'docker', *docker_command, { binmode: true })
128
- end
129
-
130
119
  # Executes a Docker CLI command and parses the output in JSON format
131
120
  #
132
121
  # @param subcommand [String] The docker subcommand to run
@@ -134,15 +123,15 @@ module Bolt
134
123
  # @param arguments [Array] Arguments to pass to the docker command
135
124
  # e.g. 'src' and 'dest' for `docker cp <src> <dest>
136
125
  # @return [Object] Ruby object representation of the JSON string
137
- private def execute_local_json_command(subcommand, arguments = [])
138
- command_options = ['--format', '{{json .}}'].concat(arguments)
139
- stdout, _stderr, _status = execute_local_command(subcommand, command_options)
140
- extract_json(stdout)
126
+ def execute_local_json_command(subcommand, arguments = [])
127
+ cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
128
+ out, _err, _stat = run_cmd(cmd, env_hash)
129
+ extract_json(out)
141
130
  end
142
131
 
143
132
  # Converts the JSON encoded STDOUT string from the docker cli into ruby objects
144
133
  #
145
- # @param stdout_string [String] The string to convert
134
+ # @param stdout [String] The string to convert
146
135
  # @return [Object] Ruby object representation of the JSON string
147
136
  private def extract_json(stdout)
148
137
  # The output from the docker format command is a JSON string per line.
@@ -24,17 +24,17 @@ module Bolt
24
24
  end
25
25
 
26
26
  def container_id
27
- "local:#{@target.host}"
27
+ "#{@target.transport_config['remote']}:#{@target.host}"
28
28
  end
29
29
 
30
30
  def connect
31
- out, err, status = execute_local_command(%w[list --format json])
31
+ out, err, status = execute_local_command(%W[list #{container_id} --format json])
32
32
  unless status.exitstatus.zero?
33
33
  raise "Error listing available containers: #{err}"
34
34
  end
35
- containers = JSON.parse(out).map { |c| c['name'] }
36
- unless containers.include?(@target.host)
37
- raise "Could not find a container with name or ID matching '#{@target.host}'"
35
+ containers = JSON.parse(out)
36
+ if containers.empty?
37
+ raise "Could not find a container with name or ID matching '#{container_id}'"
38
38
  end
39
39
  @logger.trace("Opened session")
40
40
  true
@@ -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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'shellwords'
5
+ require 'bolt/transport/base'
6
+
7
+ module Bolt
8
+ module Transport
9
+ class Podman < Docker
10
+ def with_connection(target)
11
+ conn = Connection.new(target)
12
+ conn.connect
13
+ yield conn
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require 'bolt/transport/podman/connection'
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logging'
4
+ require 'bolt/node/errors'
5
+
6
+ module Bolt
7
+ module Transport
8
+ class Podman < Docker
9
+ class Connection < Connection
10
+ attr_reader :user, :target
11
+
12
+ def initialize(target)
13
+ raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
14
+ @target = target
15
+ @user = ENV['USER'] || Etc.getlogin
16
+ @logger = Bolt::Logger.logger(target.safe_name)
17
+ @container_info = {}
18
+ @logger.trace("Initializing podman connection to #{target.safe_name}")
19
+ end
20
+
21
+ def run_cmd(cmd, env_vars)
22
+ Bolt::Util.exec_podman(cmd, env_vars)
23
+ end
24
+
25
+ def shell
26
+ @shell ||= if Bolt::Util.windows?
27
+ Bolt::Shell::Powershell.new(target, self)
28
+ else
29
+ Bolt::Shell::Bash.new(target, self)
30
+ end
31
+ end
32
+
33
+ def connect
34
+ # We don't actually have a connection, but we do need to
35
+ # check that the container exists and is running.
36
+ ps = execute_local_json_command('ps')
37
+ container = Array(ps).find { |item|
38
+ item["ID"].to_s.eql?(@target.host) ||
39
+ item["Id"].to_s.start_with?(@target.host) ||
40
+ Array(item["Names"]).include?(@target.host)
41
+ }
42
+ raise "Could not find a container with name or ID matching '#{@target.host}'" if container.nil?
43
+ # Now find the indepth container information
44
+ id = container["ID"] || container["Id"]
45
+ output = execute_local_json_command('inspect', [id])
46
+ # Store the container information for later
47
+ @container_info = output.first
48
+ @logger.trace { "Opened session" }
49
+ true
50
+ rescue StandardError => e
51
+ raise Bolt::Node::ConnectError.new(
52
+ "Failed to connect to #{target.safe_name}: #{e.message}",
53
+ 'CONNECT_ERROR'
54
+ )
55
+ end
56
+
57
+ # Executes a command inside the target container. This is called from the shell class.
58
+ #
59
+ # @param command [string] The command to run
60
+ def execute(command)
61
+ args = []
62
+ args += %w[--interactive]
63
+ args += %w[--tty] if target.options['tty']
64
+ args += @env_vars if @env_vars
65
+
66
+ if target.options['shell-command'] && !target.options['shell-command'].empty?
67
+ # escape any double quotes in command
68
+ command = command.gsub('"', '\"')
69
+ command = "#{target.options['shell-command']} \"#{command}\""
70
+ end
71
+
72
+ podman_command = %w[podman exec] + args + [container_id] + Shellwords.split(command)
73
+ @logger.trace { "Executing: #{podman_command.join(' ')}" }
74
+
75
+ Open3.popen3(*podman_command)
76
+ rescue StandardError
77
+ @logger.trace { "Command aborted" }
78
+ raise
79
+ end
80
+
81
+ # Converts the JSON encoded STDOUT string from the podman cli into ruby objects
82
+ #
83
+ # @param stdout [String] The string to convert
84
+ # @return [Object] Ruby object representation of the JSON string
85
+ private def extract_json(stdout)
86
+ # Podman renders the output in pretty JSON, which results in a newline
87
+ # appearing in the output before the closing bracket.
88
+ # should we only get a single line with no newline at all, we also
89
+ # assume it is a single minified JSON object
90
+ stdout.strip!
91
+ newline = stdout.index("\n") || -1
92
+ bracket = stdout.index('}') || -1
93
+ JSON.parse(stdout) if bracket > newline
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
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)
@@ -324,6 +332,40 @@ module Bolt
324
332
  end
325
333
  end
326
334
 
335
+ # Executes a Docker CLI command. This is useful for running commands as
336
+ # part of this class without having to go through the `execute`
337
+ # function and manage pipes.
338
+ #
339
+ # @param cmd [String] The docker command and arguments to run
340
+ # e.g. 'cp <src> <dest>' for `docker cp <src> <dest>`
341
+ # @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
342
+ def exec_docker(cmd, env = {})
343
+ Open3.capture3(env, 'docker', *cmd, { binmode: true })
344
+ end
345
+
346
+ # Executes a Podman CLI command. This is useful for running commands as
347
+ # part of this class without having to go through the `execute`
348
+ # function and manage pipes.
349
+ #
350
+ # @param cmd [String] The podman command and arguments to run
351
+ # e.g. 'cp <src> <dest>' for `podman cp <src> <dest>`
352
+ # @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
353
+ def exec_podman(cmd, env = {})
354
+ Open3.capture3(env, 'podman', *cmd, { binmode: true })
355
+ end
356
+
357
+ # Formats a map of environment variables to be passed to a command that
358
+ # accepts repeated `--env` flags
359
+ #
360
+ # @param env_vars [Hash] A map of environment variables keys and their values
361
+ # @return [String]
362
+ def format_env_vars_for_cli(env_vars)
363
+ @env_vars = env_vars.each_with_object([]) do |(key, value), acc|
364
+ acc << "--env"
365
+ acc << "#{key}=#{value}"
366
+ end
367
+ end
368
+
327
369
  def unix_basename(path)
328
370
  raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
329
371
  path.split('/').last
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '3.3.0'
4
+ VERSION = '3.7.0'
5
5
  end
@@ -703,6 +703,9 @@ module BoltServer
703
703
  connect_plugin = BoltServer::Plugin::PuppetConnectData.new(body['puppet_connect_data'])
704
704
  plugins = Bolt::Plugin.setup(context[:config], context[:pal], load_plugins: false)
705
705
  plugins.add_plugin(connect_plugin)
706
+ %w[aws_inventory azure_inventory gcloud_inventory].each do |plugin_name|
707
+ plugins.add_module_plugin(plugin_name) if plugins.known_plugin?(plugin_name)
708
+ end
706
709
  inventory = Bolt::Inventory.from_config(context[:config], plugins)
707
710
  target_list = inventory.get_targets('all').map do |targ|
708
711
  targ.to_h.merge({ 'transport' => targ.transport, 'plugin_hooks' => targ.plugin_hooks })
@@ -29,7 +29,14 @@ module BoltSpec
29
29
  end
30
30
 
31
31
  def result_for(target, stdout: '', stderr: '')
32
- Bolt::Result.for_command(target, stdout, stderr, 0, 'command', '', [])
32
+ value = {
33
+ 'stdout' => stdout,
34
+ 'stderr' => stderr,
35
+ 'merged_output' => "#{stdout}\n#{stderr}".strip,
36
+ 'exit_code' => 0
37
+ }
38
+
39
+ Bolt::Result.for_command(target, value, 'command', '', [])
33
40
  end
34
41
 
35
42
  # Public methods
@@ -35,7 +35,14 @@ module BoltSpec
35
35
  end
36
36
 
37
37
  def result_for(target, stdout: '', stderr: '')
38
- Bolt::Result.for_command(target, stdout, stderr, 0, 'script', '', [])
38
+ value = {
39
+ 'stdout' => stdout,
40
+ 'stderr' => stderr,
41
+ 'merged_output' => "#{stdout}\n#{stderr}".strip,
42
+ 'exit_code' => 0
43
+ }
44
+
45
+ Bolt::Result.for_command(target, value, 'script', '', [])
39
46
  end
40
47
 
41
48
  # Public methods
@@ -17,13 +17,14 @@ module BoltSpec
17
17
 
18
18
  # Nothing on the executor is 'public'
19
19
  class MockExecutor
20
- attr_reader :noop, :error_message, :in_parallel
20
+ attr_reader :noop, :error_message, :in_parallel, :transports, :future
21
21
  attr_accessor :run_as, :transport_features, :execute_any_plan
22
22
 
23
23
  def initialize(modulepath)
24
24
  @noop = false
25
25
  @run_as = nil
26
26
  @in_parallel = false
27
+ @future = {}
27
28
  @error_message = nil
28
29
  @allow_apply = false
29
30
  @modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
@@ -91,6 +92,14 @@ module BoltSpec
91
92
  result
92
93
  end
93
94
 
95
+ def run_task_with(target_mapping, task, options = {}, _position = [])
96
+ resultsets = target_mapping.map do |target, arguments|
97
+ run_task([target], task, arguments, options)
98
+ end.compact
99
+
100
+ Bolt::ResultSet.new(resultsets.map(&:results).flatten)
101
+ end
102
+
94
103
  def download_file(targets, source, destination, options = {}, _position = [])
95
104
  result = nil
96
105
  if (doub = @download_doubles[source] || @download_doubles[:default])
@@ -210,16 +219,6 @@ module BoltSpec
210
219
  yield
211
220
  end
212
221
 
213
- def report_function_call(_function); end
214
-
215
- def report_bundled_content(_mode, _name); end
216
-
217
- def report_file_source(_plan_function, _source); end
218
-
219
- def report_apply(_statements, _resources); end
220
-
221
- def report_yaml_plan(_plan); end
222
-
223
222
  def publish_event(event)
224
223
  if event[:type] == :message
225
224
  unless @stub_out_message
@@ -257,6 +256,87 @@ module BoltSpec
257
256
  end.new(transport_features)
258
257
  end
259
258
  # End apply_prep mocking
259
+
260
+ # Evaluates a `parallelize()` block and returns the result. Normally,
261
+ # Bolt's executor wraps this in a Yarn for each object passed to the
262
+ # `parallelize()` function, and then executes them in parallel before
263
+ # returning the result from the block. However, in BoltSpec the block is
264
+ # executed for each object sequentially, and this function returns the
265
+ # result itself.
266
+ #
267
+ def create_yarn(scope, block, object, _index)
268
+ # Create the new scope
269
+ newscope = Puppet::Parser::Scope.new(scope.compiler)
270
+ local = Puppet::Parser::Scope::LocalScope.new
271
+
272
+ # Compress the current scopes into a single vars hash to add to the new scope
273
+ current_scope = scope.effective_symtable(true)
274
+ until current_scope.nil?
275
+ current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
276
+ current_scope = current_scope.parent
277
+ end
278
+ newscope.push_ephemerals([local])
279
+
280
+ begin
281
+ result = catch(:return) do
282
+ args = { block.parameters[0][1].to_s => object }
283
+ block.closure.call_by_name_with_scope(newscope, args, true)
284
+ end
285
+
286
+ # If we got a return from the block, get it's value
287
+ # Otherwise the result is the last line from the block
288
+ result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
289
+
290
+ # Validate the result is a PlanResult
291
+ unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
292
+ raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
293
+ end
294
+
295
+ result
296
+ rescue Puppet::PreformattedError => e
297
+ if e.cause.is_a?(Bolt::Error)
298
+ e.cause
299
+ else
300
+ raise e
301
+ end
302
+ end
303
+ end
304
+
305
+ # BoltSpec already evaluated the `parallelize()` block for each object
306
+ # passed to the function, so these results can be returned as-is.
307
+ #
308
+ def round_robin(results)
309
+ results
310
+ end
311
+
312
+ # Public methods on Bolt::Executor that need to be mocked so there aren't
313
+ # "undefined method" errors.
314
+
315
+ def batch_execute(_targets); end
316
+
317
+ def finish_plan(_plan_result); end
318
+
319
+ def handle_event(_event); end
320
+
321
+ def prompt(_prompt, _options); end
322
+
323
+ def report_function_call(_function); end
324
+
325
+ def report_bundled_content(_mode, _name); end
326
+
327
+ def report_file_source(_plan_function, _source); end
328
+
329
+ def report_apply(_statements, _resources); end
330
+
331
+ def report_yaml_plan(_plan); end
332
+
333
+ def shutdown; end
334
+
335
+ def start_plan(_plan_context); end
336
+
337
+ def subscribe(_subscriber, _types = nil); end
338
+
339
+ def unsubscribe(_subscriber, _types = nil); end
260
340
  end
261
341
  end
262
342
  end