bolt 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca6e259cbc835ea3d8b4f5f67d2b199faf19adfeb2a2d9b0b999582b919a863a
4
- data.tar.gz: 2152f34ff150aaaf2d1c0a6fb114d401ef3ddf5c2da5a739e8f2fa22ab5565cf
3
+ metadata.gz: d1188441f6f0d4a4012c0e1bedbfeff66dcafa7624911448865e4081950510c3
4
+ data.tar.gz: 5add229df85ea63038f8a2492adcd1086ad3d3201280bed7b22fb7cfdaf205a3
5
5
  SHA512:
6
- metadata.gz: 69fe10cdd01be7f4965a8e6cca73aa9c65f449b94bb35909399839ca4c506107b611df43f3a0e27fe9fce896d3e63261f3dc90ce329d257583ff427e6c712eb3
7
- data.tar.gz: 400b7ef4c9b01ad6c5464481ac73f5c3b7774e25aa6780f1aa404e3e6504091c57828d9a0bdaf7a3f4effecd08ff1b35cae4dd8e5b7dd6b6329267805ce0ef77
6
+ metadata.gz: 49cdd374d0607bfd3ff6367d1c9b3be6061df74a7c1ed57940e07b78f9ae7f9fc8e235769fb741b63b85b136f127ee289ac26110b692c47d03d19cf543e0e495
7
+ data.tar.gz: 5bdb2cea2de3c6a8696b7e3b76a2ece602d94480f42b30feb8c2531bc93ca5ebadf3ba1b3f6d3bfe9cc5ba0effc0e9d0949729af7df20313d159fcbac554eb03
@@ -26,7 +26,7 @@ Puppet::Functions.create_function(:run_command) do
26
26
  # @param options Additional options: '_catch_errors', '_run_as'.
27
27
  # @return A list of results, one entry per target.
28
28
  # @example Run a command on targets
29
- # run_command('hostname', $targets, '_catch_errors' => true)
29
+ # run_command('hostname', $targets, 'Get hostname')
30
30
  dispatch :run_command_with_description do
31
31
  param 'String[1]', :command
32
32
  param 'Boltlib::TargetSpec', :targets
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/util'
4
+
5
+ # Wait until all targets accept connections.
6
+ Puppet::Functions.create_function(:wait_until_available) do
7
+ # Wait until targets are available.
8
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
9
+ # @param options Additional options: 'description', 'wait_time', 'retry_interval', '_catch_errors'.
10
+ # @return A list of results, one entry per target. Successful results have no value.
11
+ # @example Wait for targets
12
+ # wait_until_available($targets, wait_time => 300)
13
+ dispatch :wait_until_available do
14
+ param 'Boltlib::TargetSpec', :targets
15
+ optional_param 'Hash[String[1], Any]', :options
16
+ return_type 'ResultSet'
17
+ end
18
+
19
+ def wait_until_available(targets, options = nil)
20
+ options ||= {}
21
+
22
+ unless Puppet[:tasks]
23
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
24
+ Puppet::Pops::Issues::TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, operation: 'wait_until_available'
25
+ )
26
+ end
27
+
28
+ executor = Puppet.lookup(:bolt_executor) { nil }
29
+ inventory = Puppet.lookup(:bolt_inventory) { nil }
30
+ unless executor && inventory && Puppet.features.bolt?
31
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
32
+ Puppet::Pops::Issues::TASK_MISSING_BOLT, action: _('wait until targets are available')
33
+ )
34
+ end
35
+
36
+ executor.report_function_call('wait_until_available')
37
+
38
+ # Ensure that given targets are all Target instances
39
+ targets = inventory.get_targets(targets)
40
+
41
+ if targets.empty?
42
+ call_function('debug', "Simulating wait_until_available - no targets given")
43
+ r = Bolt::ResultSet.new([])
44
+ else
45
+ opts = Bolt::Util.symbolize_top_level_keys(options.reject { |k| k == '_catch_errors' })
46
+ r = executor.wait_until_available(targets, **opts)
47
+ end
48
+
49
+ if !r.ok && !options['_catch_errors']
50
+ raise Bolt::RunFailure.new(r, 'wait_until_available')
51
+ end
52
+ r
53
+ end
54
+ end
data/lib/bolt/config.rb CHANGED
@@ -9,13 +9,15 @@ require 'bolt/transport/ssh'
9
9
  require 'bolt/transport/winrm'
10
10
  require 'bolt/transport/orch'
11
11
  require 'bolt/transport/local'
12
+ require 'bolt/transport/docker'
12
13
 
13
14
  module Bolt
14
15
  TRANSPORTS = {
15
16
  ssh: Bolt::Transport::SSH,
16
17
  winrm: Bolt::Transport::WinRM,
17
18
  pcp: Bolt::Transport::Orch,
18
- local: Bolt::Transport::Local
19
+ local: Bolt::Transport::Local,
20
+ docker: Bolt::Transport::Docker
19
21
  }.freeze
20
22
 
21
23
  class UnknownTransportError < Bolt::Error
@@ -50,7 +52,8 @@ module Bolt
50
52
  pcp: {
51
53
  'task-environment' => 'production'
52
54
  },
53
- local: {}
55
+ local: {},
56
+ docker: {}
54
57
  }.freeze
55
58
 
56
59
  def self.default
data/lib/bolt/error.rb CHANGED
@@ -50,13 +50,14 @@ module Bolt
50
50
  class RunFailure < Bolt::Error
51
51
  attr_reader :result_set
52
52
 
53
- def initialize(result_set, action, object)
53
+ def initialize(result_set, action, object = nil)
54
54
  details = {
55
55
  'action' => action,
56
56
  'object' => object,
57
57
  'result_set' => result_set
58
58
  }
59
- message = "Plan aborted: #{action} '#{object}' failed on #{result_set.error_set.length} nodes"
59
+ object_msg = " '#{object}'" if object
60
+ message = "Plan aborted: #{action}#{object_msg} failed on #{result_set.error_set.length} nodes"
60
61
  super(message, 'bolt/run-failure', details)
61
62
  @result_set = result_set
62
63
  @error_code = 2
data/lib/bolt/executor.rb CHANGED
@@ -256,6 +256,39 @@ module Bolt
256
256
  end
257
257
  end
258
258
 
259
+ class TimeoutError < RuntimeError; end
260
+
261
+ def wait_until_available(targets,
262
+ description: 'wait until available',
263
+ wait_time: 120,
264
+ retry_interval: 1)
265
+ log_action(description, targets) do
266
+ batch_execute(targets) do |transport, batch|
267
+ with_node_logging('Waiting until available', batch) do
268
+ begin
269
+ wait_until(wait_time, retry_interval) { transport.batch_connected?(batch) }
270
+ batch.map { |target| Result.new(target) }
271
+ rescue TimeoutError => e
272
+ batch.map { |target| Result.from_exception(target, e) }
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ # Used to simplify unit testing, to avoid having to mock other calls to Time.now.
280
+ private def wait_now
281
+ Time.now
282
+ end
283
+
284
+ def wait_until(timeout, retry_interval)
285
+ start = wait_now
286
+ until yield
287
+ raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
288
+ sleep(retry_interval)
289
+ end
290
+ end
291
+
259
292
  # Plan context doesn't make sense for most transports but it is tightly
260
293
  # coupled with the orchestrator transport since the transport behaves
261
294
  # differently when a plan is running. In order to limit how much this
data/lib/bolt/target.rb CHANGED
@@ -47,8 +47,8 @@ module Bolt
47
47
  @password = t_conf['password']
48
48
  @port = t_conf['port']
49
49
 
50
- url_keys = %w[user password port]
51
- @options = t_conf.reject { |k, _| url_keys.include?(k) }.merge(@options)
50
+ # Preserve everything in options so we can easily create copies of a Target.
51
+ @options = t_conf.merge(@options)
52
52
 
53
53
  self
54
54
  end
@@ -153,6 +153,11 @@ module Bolt
153
153
  end
154
154
  end
155
155
 
156
+ def batch_connected?(targets)
157
+ assert_batch_size_one("connected?()", targets)
158
+ connected?(targets.first)
159
+ end
160
+
156
161
  # Split the given list of targets into a list of batches. The default
157
162
  # implementation returns single-node batches.
158
163
  #
@@ -182,6 +187,11 @@ module Bolt
182
187
  raise NotImplementedError, "upload() must be implemented by the transport class"
183
188
  end
184
189
 
190
+ # Transports should override this method with their own implementation of a connection test.
191
+ def connected?(_targets)
192
+ raise NotImplementedError, "connected?() must be implemented by the transport class"
193
+ end
194
+
185
195
  # Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed
186
196
  # to the Task/Script.
187
197
  #
@@ -0,0 +1,121 @@
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 Docker < Base
10
+ def self.options
11
+ %w[service-url service-options tmpdir]
12
+ end
13
+
14
+ PROVIDED_FEATURES = ['shell'].freeze
15
+
16
+ def self.validate(options)
17
+ if (url = options['service-url'])
18
+ unless url.instance_of?(String)
19
+ raise Bolt::ValidationError, 'service-url must be a string'
20
+ end
21
+ end
22
+
23
+ if (opts = options['service-options'])
24
+ unless opts.instance_of?(Hash)
25
+ raise Bolt::ValidationError, 'service-options must be a hash'
26
+ end
27
+ end
28
+ end
29
+
30
+ def with_connection(target)
31
+ conn = Connection.new(target)
32
+ conn.connect
33
+ yield conn
34
+ end
35
+
36
+ def upload(target, source, destination, _options = {})
37
+ with_connection(target) do |conn|
38
+ conn.with_remote_tempdir do |dir|
39
+ basename = File.basename(destination)
40
+ tmpfile = "#{dir}/#{basename}"
41
+ if File.directory?(source)
42
+ conn.write_remote_directory(source, tmpfile)
43
+ else
44
+ conn.write_remote_file(source, tmpfile)
45
+ end
46
+
47
+ _, stderr, exitcode = conn.execute('mv', tmpfile, destination, {})
48
+ if exitcode != 0
49
+ message = "Could not move temporary file '#{tmpfile}' to #{destination}: #{stderr.join}"
50
+ raise Bolt::Node::FileError.new(message, 'MV_ERROR')
51
+ end
52
+ end
53
+ Bolt::Result.for_upload(target, source, destination)
54
+ end
55
+ end
56
+
57
+ def run_command(target, command, _options = {})
58
+ with_connection(target) do |conn|
59
+ stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), {})
60
+ Bolt::Result.for_command(target, stdout.join, stderr.join, exitcode)
61
+ end
62
+ end
63
+
64
+ def run_script(target, script, arguments, _options = {})
65
+ # unpack any Sensitive data
66
+ arguments = unwrap_sensitive_args(arguments)
67
+
68
+ with_connection(target) do |conn|
69
+ conn.with_remote_tempdir do |dir|
70
+ remote_path = conn.write_remote_executable(dir, script)
71
+ stdout, stderr, exitcode = conn.execute(remote_path, *arguments, {})
72
+ Bolt::Result.for_command(target, stdout.join, stderr.join, exitcode)
73
+ end
74
+ end
75
+ end
76
+
77
+ def run_task(target, task, arguments, _options = {})
78
+ implementation = task.select_implementation(target, PROVIDED_FEATURES)
79
+ executable = implementation['path']
80
+ input_method = implementation['input_method']
81
+ extra_files = implementation['files']
82
+ input_method ||= 'both'
83
+
84
+ # unpack any Sensitive data
85
+ arguments = unwrap_sensitive_args(arguments)
86
+ with_connection(target) do |conn|
87
+ execute_options = {}
88
+
89
+ conn.with_remote_tempdir do |dir|
90
+ if extra_files.empty?
91
+ task_dir = dir
92
+ else
93
+ # TODO: optimize upload of directories
94
+ arguments['_installdir'] = dir
95
+ task_dir = File.join(dir, task.tasks_dir)
96
+ conn.mkdirs([task_dir] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
97
+ extra_files.each do |file|
98
+ conn.write_remote_file(file['path'], File.join(dir, file['name']))
99
+ end
100
+ end
101
+
102
+ remote_task_path = conn.write_remote_executable(task_dir, executable)
103
+
104
+ if STDIN_METHODS.include?(input_method)
105
+ execute_options[:stdin] = StringIO.new(JSON.dump(arguments))
106
+ end
107
+
108
+ if ENVIRONMENT_METHODS.include?(input_method)
109
+ execute_options[:environment] = envify_params(arguments)
110
+ end
111
+
112
+ stdout, stderr, exitcode = conn.execute(remote_task_path, execute_options)
113
+ Bolt::Result.for_task(target, stdout.join, stderr.join, exitcode)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ require 'bolt/transport/docker/connection'
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'docker'
4
+ require 'logging'
5
+ require 'bolt/node/errors'
6
+
7
+ module Bolt
8
+ module Transport
9
+ class Docker < Base
10
+ class Connection
11
+ def initialize(target)
12
+ @target = target
13
+ @logger = Logging.logger[target.host]
14
+ end
15
+
16
+ def connect
17
+ # Explicitly create the new Connection to avoid relying on global state in the Docker module.
18
+ url = @target.options['service-url'] || ::Docker.url
19
+ options = ::Docker.options.merge(@target.options['service-options'] || {})
20
+ @container = ::Docker::Container.get(@target.host, {}, ::Docker::Connection.new(url, options))
21
+ @logger.debug { "Opened session" }
22
+ rescue StandardError => e
23
+ raise Bolt::Node::ConnectError.new(
24
+ "Failed to connect to #{@target.uri}: #{e.message}",
25
+ 'CONNECT_ERROR'
26
+ )
27
+ end
28
+
29
+ def execute(*command, options)
30
+ if options[:environment]
31
+ envs = options[:environment].map { |env, val| "#{env}=#{val}" }
32
+ command = ['env'] + envs + command
33
+ end
34
+
35
+ @logger.debug { "Executing: #{command}" }
36
+ result = @container.exec(command, options) { |stream, chunk| @logger.debug("#{stream}: #{chunk}") }
37
+ if result[2] == 0
38
+ @logger.debug { "Command returned successfully" }
39
+ else
40
+ @logger.info { "Command failed with exit code #{result[2]}" }
41
+ end
42
+ result
43
+ rescue StandardError
44
+ @logger.debug { "Command aborted" }
45
+ raise
46
+ end
47
+
48
+ def write_remote_file(source, destination)
49
+ @container.store_file(destination, File.binread(source))
50
+ rescue StandardError => e
51
+ raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
52
+ end
53
+
54
+ def write_remote_directory(source, destination)
55
+ tar = ::Docker::Util.create_dir_tar(source)
56
+ mkdirs([destination])
57
+ @container.archive_in_stream(destination) { tar.read(Excon.defaults[:chunk_size]).to_s }
58
+ rescue StandardError => e
59
+ raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
60
+ end
61
+
62
+ def mkdirs(dirs)
63
+ _, stderr, exitcode = execute('mkdir', '-p', *dirs, {})
64
+ if exitcode != 0
65
+ message = "Could not create directories: #{stderr.join}"
66
+ raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
67
+ end
68
+ end
69
+
70
+ def make_tempdir
71
+ tmpdir = @target.options.fetch('tmpdir', '/tmp')
72
+ tmppath = "#{tmpdir}/#{SecureRandom.uuid}"
73
+
74
+ stdout, stderr, exitcode = execute('mkdir', '-m', '700', tmppath, {})
75
+ if exitcode != 0
76
+ raise Bolt::Node::FileError.new("Could not make tempdir: #{stderr.join}", 'TEMPDIR_ERROR')
77
+ end
78
+ tmppath || stdout.first
79
+ end
80
+
81
+ def with_remote_tempdir
82
+ dir = make_tempdir
83
+ yield dir
84
+ ensure
85
+ if dir
86
+ _, stderr, exitcode = execute('rm', '-rf', dir, {})
87
+ if exitcode != 0
88
+ @logger.warn("Failed to clean up tempdir '#{dir}': #{stderr.join}")
89
+ end
90
+ end
91
+ end
92
+
93
+ def write_remote_executable(dir, file, filename = nil)
94
+ filename ||= File.basename(file)
95
+ remote_path = File.join(dir.to_s, filename)
96
+ write_remote_file(file, remote_path)
97
+ make_executable(remote_path)
98
+ remote_path
99
+ end
100
+
101
+ def make_executable(path)
102
+ _, stderr, exitcode = execute('chmod', 'u+x', path, {})
103
+ if exitcode != 0
104
+ message = "Could not make file '#{path}' executable: #{stderr.join}"
105
+ raise Bolt::Node::FileError.new(message, 'CHMOD_ERROR')
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -32,6 +32,8 @@ module Bolt
32
32
  Dir.mktmpdir(*args) do |dir|
33
33
  yield dir
34
34
  end
35
+ rescue StandardError => e
36
+ raise Bolt::Node::FileError.new("Could not make tempdir: #{e.message}", 'TEMPDIR_ERROR')
35
37
  end
36
38
  private :in_tmpdir
37
39
 
@@ -117,6 +119,10 @@ module Bolt
117
119
  Bolt::Result.for_task(target, output.stdout.string, output.stderr.string, output.exit_code)
118
120
  end
119
121
  end
122
+
123
+ def connected?(_targets)
124
+ true
125
+ end
120
126
  end
121
127
  end
122
128
  end
@@ -211,6 +211,11 @@ module Bolt
211
211
  end
212
212
  end
213
213
 
214
+ def batch_connected?(targets)
215
+ resp = get_connection(targets.first.options).query_inventory(targets)
216
+ resp['items'].all? { |node| node['connected'] }
217
+ end
218
+
214
219
  # run_task generates a result that makes sense for a generic task which
215
220
  # needs to be unwrapped to extract stdout/stderr/exitcode.
216
221
  #
@@ -52,7 +52,7 @@ module Bolt
52
52
  if @plan_job
53
53
  @client.command.plan_finish(
54
54
  plan_job: @plan_job,
55
- result: plan_result.value,
55
+ result: plan_result.value || '',
56
56
  status: plan_result.status
57
57
  )
58
58
  end
@@ -75,6 +75,10 @@ module Bolt
75
75
  body = build_request(targets, task, arguments, options['_description'])
76
76
  @client.run_task(body)
77
77
  end
78
+
79
+ def query_inventory(targets)
80
+ @client.post('inventory', nodes: targets.map(&:host))
81
+ end
78
82
  end
79
83
  end
80
84
  end
@@ -187,6 +187,12 @@ module Bolt
187
187
  EOF
188
188
  SCRIPT
189
189
  end
190
+
191
+ def connected?(target)
192
+ with_connection(target) { true }
193
+ rescue Bolt::Node::ConnectError
194
+ false
195
+ end
190
196
  end
191
197
  end
192
198
  end
@@ -216,6 +216,12 @@ try { & "#{remote_task_path}" @taskArgs } catch { Write-Error $_.Exception; exit
216
216
  end
217
217
  end
218
218
  end
219
+
220
+ def connected?(target)
221
+ with_connection(target) { true }
222
+ rescue Bolt::Node::ConnectError
223
+ false
224
+ end
219
225
  end
220
226
  end
221
227
  end
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.2.0'
4
+ VERSION = '1.3.0'
5
5
  end
@@ -9,6 +9,9 @@ module BoltServer
9
9
  'ssl-cipher-suites', 'loglevel', 'logfile', 'whitelist', 'concurrency',
10
10
  'cache-dir', 'file-server-conn-timeout', 'file-server-uri'].freeze
11
11
 
12
+ ENV_KEYS = ['ssl-cert', 'ssl-key', 'ssl-ca-cert', 'loglevel',
13
+ 'concurrency', 'file-server-conn-timeout', 'file-server-uri'].freeze
14
+
12
15
  DEFAULTS = {
13
16
  'host' => '127.0.0.1',
14
17
  'port' => 62658,
@@ -40,7 +43,7 @@ module BoltServer
40
43
  @config_path = nil
41
44
  end
42
45
 
43
- def load_config(path)
46
+ def load_file_config(path)
44
47
  @config_path = path
45
48
  begin
46
49
  parsed_hocon = Hocon.load(path)['bolt-server']
@@ -53,10 +56,20 @@ module BoltServer
53
56
  raise "Could not find bolt-server config at #{path}" if parsed_hocon.nil?
54
57
 
55
58
  parsed_hocon = parsed_hocon.select { |key, _| CONFIG_KEYS.include?(key) }
59
+
56
60
  @data = @data.merge(parsed_hocon)
61
+ end
57
62
 
58
- validate
59
- self
63
+ def load_env_config
64
+ ENV_KEYS.each do |key|
65
+ transformed_key = "BOLT_#{key.tr('-', '_').upcase}"
66
+ next unless ENV.key?(transformed_key)
67
+ @data[key] = if ['concurrency', 'file-server-conn-timeout'].include?(key)
68
+ ENV[transformed_key].to_i
69
+ else
70
+ ENV[transformed_key]
71
+ end
72
+ end
60
73
  end
61
74
 
62
75
  def natural?(num)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'sinatra'
4
4
  require 'bolt'
5
+ require 'bolt/error'
5
6
  require 'bolt/target'
6
7
  require 'bolt/task/puppet_server'
7
8
  require 'bolt_server/file_cache'
@@ -30,6 +31,22 @@ module BoltServer
30
31
  super(nil)
31
32
  end
32
33
 
34
+ def scrub_stack_trace(result)
35
+ if result.dig(:result, '_error', 'details', 'stack_trace')
36
+ result[:result]['_error']['details'].reject! { |k| k == 'stack_trace' }
37
+ end
38
+ result
39
+ end
40
+
41
+ def validate_schema(schema, body)
42
+ schema_error = JSON::Validator.fully_validate(schema, body)
43
+ if schema_error.any?
44
+ Bolt::Error.new("There was an error validating the request body.",
45
+ 'boltserver/schema-error',
46
+ schema_error)
47
+ end
48
+ end
49
+
33
50
  get '/' do
34
51
  200
35
52
  end
@@ -53,8 +70,8 @@ module BoltServer
53
70
  content_type :json
54
71
 
55
72
  body = JSON.parse(request.body.read)
56
- schema_error = JSON::Validator.fully_validate(@schemas["ssh-run_task"], body)
57
- return [400, schema_error.join] if schema_error.any?
73
+ error = validate_schema(@schemas["ssh-run_task"], body)
74
+ return [400, error.to_json] unless error.nil?
58
75
 
59
76
  opts = body['target']
60
77
  if opts['private-key-content']
@@ -70,15 +87,16 @@ module BoltServer
70
87
 
71
88
  # Since this will only be on one node we can just return the first result
72
89
  results = @executor.run_task(target, task, parameters)
73
- [200, results.first.to_json]
90
+ result = scrub_stack_trace(results.first.status_hash)
91
+ [200, result.to_json]
74
92
  end
75
93
 
76
94
  post '/winrm/run_task' do
77
95
  content_type :json
78
96
 
79
97
  body = JSON.parse(request.body.read)
80
- schema_error = JSON::Validator.fully_validate(@schemas["winrm-run_task"], body)
81
- return [400, schema_error.join] if schema_error.any?
98
+ error = validate_schema(@schemas["winrm-run_task"], body)
99
+ return [400, error.to_json] unless error.nil?
82
100
 
83
101
  opts = body['target'].merge('protocol' => 'winrm')
84
102
 
@@ -90,16 +108,21 @@ module BoltServer
90
108
 
91
109
  # Since this will only be on one node we can just return the first result
92
110
  results = @executor.run_task(target, task, parameters)
93
- [200, results.first.to_json]
111
+ result = scrub_stack_trace(results.first.status_hash)
112
+ [200, result.to_json]
94
113
  end
95
114
 
96
115
  error 404 do
97
- [404, "Could not find route #{request.path}"]
116
+ err = Bolt::Error.new("Could not find route #{request.path}",
117
+ 'boltserver/not-found')
118
+ [404, err.to_json]
98
119
  end
99
120
 
100
121
  error 500 do
101
122
  e = env['sinatra.error']
102
- [500, "500: Unknown error: #{e.message}"]
123
+ err = Bolt::Error.new("500: Unknown error: #{e.message}",
124
+ 'boltserver/server-error')
125
+ [500, err.to_json]
103
126
  end
104
127
  end
105
128
  end
@@ -91,6 +91,16 @@ require 'bolt/pal'
91
91
  #
92
92
  module BoltSpec
93
93
  module Plans
94
+ def self.init
95
+ # Ensure tasks are enabled when rspec-puppet sets up an environment so we get task loaders.
96
+ # Note that this is probably not safe to do in modules that also test Puppet manifest code.
97
+ Bolt::PAL.load_puppet
98
+ Puppet[:tasks] = true
99
+
100
+ # Ensure logger is initialized with Puppet levels so 'notice' works when running plan specs.
101
+ Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
102
+ end
103
+
94
104
  # Override in your tests if needed
95
105
  def modulepath
96
106
  [RSpec.configuration.module_path]
@@ -209,10 +209,18 @@ module BoltSpec
209
209
  @task_doubles[task_name] ||= TaskDouble.new
210
210
  end
211
211
 
212
+ def wait_until_available(targets, _options)
213
+ targets.map { |target| Bolt::Result.new(target) }
214
+ end
215
+
212
216
  def log_plan(_plan_name)
213
217
  yield
214
218
  end
215
219
 
220
+ def without_default_logging
221
+ yield
222
+ end
223
+
216
224
  def report_function_call(_function); end
217
225
 
218
226
  def report_bundled_content(_mode, _name); end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-30 00:00:00.000000000 Z
11
+ date: 2018-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: docker-api
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.34'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.34'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: logging
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -283,6 +297,7 @@ files:
283
297
  - bolt-modules/boltlib/lib/puppet/functions/set_var.rb
284
298
  - bolt-modules/boltlib/lib/puppet/functions/upload_file.rb
285
299
  - bolt-modules/boltlib/lib/puppet/functions/vars.rb
300
+ - bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb
286
301
  - bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb
287
302
  - bolt-modules/boltlib/types/planresult.pp
288
303
  - bolt-modules/boltlib/types/targetspec.pp
@@ -323,6 +338,8 @@ files:
323
338
  - lib/bolt/task.rb
324
339
  - lib/bolt/task/puppet_server.rb
325
340
  - lib/bolt/transport/base.rb
341
+ - lib/bolt/transport/docker.rb
342
+ - lib/bolt/transport/docker/connection.rb
326
343
  - lib/bolt/transport/local.rb
327
344
  - lib/bolt/transport/local/shell.rb
328
345
  - lib/bolt/transport/orch.rb