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 +4 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +54 -0
- data/lib/bolt/config.rb +5 -2
- data/lib/bolt/error.rb +3 -2
- data/lib/bolt/executor.rb +33 -0
- data/lib/bolt/target.rb +2 -2
- data/lib/bolt/transport/base.rb +10 -0
- data/lib/bolt/transport/docker.rb +121 -0
- data/lib/bolt/transport/docker/connection.rb +111 -0
- data/lib/bolt/transport/local.rb +6 -0
- data/lib/bolt/transport/orch.rb +5 -0
- data/lib/bolt/transport/orch/connection.rb +5 -1
- data/lib/bolt/transport/ssh.rb +6 -0
- data/lib/bolt/transport/winrm.rb +6 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +16 -3
- data/lib/bolt_server/transport_app.rb +31 -8
- data/lib/bolt_spec/plans.rb +10 -0
- data/lib/bolt_spec/plans/mock_executor.rb +8 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1188441f6f0d4a4012c0e1bedbfeff66dcafa7624911448865e4081950510c3
|
4
|
+
data.tar.gz: 5add229df85ea63038f8a2492adcd1086ad3d3201280bed7b22fb7cfdaf205a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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, '
|
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
|
-
|
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
|
-
|
51
|
-
@options = t_conf.
|
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
|
data/lib/bolt/transport/base.rb
CHANGED
@@ -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
|
data/lib/bolt/transport/local.rb
CHANGED
@@ -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
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -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
|
data/lib/bolt/transport/ssh.rb
CHANGED
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -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
data/lib/bolt_server/config.rb
CHANGED
@@ -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
|
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
|
-
|
59
|
-
|
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
|
-
|
57
|
-
return [400,
|
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
|
-
|
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
|
-
|
81
|
-
return [400,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bolt_spec/plans.rb
CHANGED
@@ -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.
|
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-
|
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
|