bolt 1.0.0 → 1.1.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/lib/bolt/catalog.rb +5 -1
- data/lib/bolt/cli.rb +10 -4
- data/lib/bolt/executor.rb +6 -1
- data/lib/bolt/pal.rb +4 -2
- data/lib/bolt/task.rb +14 -1
- data/lib/bolt/transport/base.rb +1 -1
- data/lib/bolt/transport/local.rb +18 -7
- data/lib/bolt/transport/orch.rb +42 -2
- data/lib/bolt/transport/ssh.rb +20 -9
- data/lib/bolt/transport/ssh/connection.rb +19 -2
- data/lib/bolt/transport/winrm.rb +27 -14
- data/lib/bolt/transport/winrm/connection.rb +8 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_ext/schemas/ssh-run_task.json +6 -2
- data/lib/bolt_ext/server.rb +5 -2
- data/lib/bolt_ext/server_config.rb +1 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5955d8922691cc5c2875f1614d69181289c0ba9561e6016fb8c18bcecd09391a
|
4
|
+
data.tar.gz: 0077fbd497fbbabf23c72a7916517b660a9c21553e8a6bc04ffaddc8cc283e53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a92a53a9e95ff635ff3adf36e594d6ba38d130cbfd117ecfe7111404cba7f453c63e483269120ecf51d1cd1b58f96cd8bfa6087d88e4ed3c1692c7dfe804ece9
|
7
|
+
data.tar.gz: f2df408dcd301c87b0d598b0f5848576b832a815f2bf13aa5784e85a2384a098b3a34a0b12e3b7b1d80292e334913ab553848826a52e712cb563fc3814abc24d
|
data/lib/bolt/catalog.rb
CHANGED
@@ -12,6 +12,10 @@ require 'bolt/catalog/logging'
|
|
12
12
|
|
13
13
|
module Bolt
|
14
14
|
class Catalog
|
15
|
+
def initialize(log_level = 'debug')
|
16
|
+
@log_level = log_level
|
17
|
+
end
|
18
|
+
|
15
19
|
def with_puppet_settings(hiera_config = {})
|
16
20
|
Dir.mktmpdir('bolt') do |dir|
|
17
21
|
cli = []
|
@@ -24,7 +28,7 @@ module Bolt
|
|
24
28
|
|
25
29
|
# Use a special logdest that serializes all log messages and their level to stderr.
|
26
30
|
Puppet::Util::Log.newdestination(:stderr)
|
27
|
-
Puppet.settings[:log_level] =
|
31
|
+
Puppet.settings[:log_level] = @log_level
|
28
32
|
yield
|
29
33
|
end
|
30
34
|
end
|
data/lib/bolt/cli.rb
CHANGED
@@ -298,7 +298,7 @@ module Bolt
|
|
298
298
|
if dest.nil?
|
299
299
|
raise Bolt::CLIError, "A destination path must be specified"
|
300
300
|
end
|
301
|
-
validate_file('source file', src)
|
301
|
+
validate_file('source file', src, true)
|
302
302
|
executor.upload_file(targets, src, dest, executor_opts) do |event|
|
303
303
|
outputter.print_event(event)
|
304
304
|
end
|
@@ -392,7 +392,7 @@ module Bolt
|
|
392
392
|
@pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.compile_concurrency)
|
393
393
|
end
|
394
394
|
|
395
|
-
def validate_file(type, path)
|
395
|
+
def validate_file(type, path, allow_dir = false)
|
396
396
|
if path.nil?
|
397
397
|
raise Bolt::CLIError, "A #{type} must be specified"
|
398
398
|
end
|
@@ -401,8 +401,14 @@ module Bolt
|
|
401
401
|
|
402
402
|
if !stat.readable?
|
403
403
|
raise Bolt::FileError.new("The #{type} '#{path}' is unreadable", path)
|
404
|
-
elsif !stat.file?
|
405
|
-
|
404
|
+
elsif !stat.file? && (!allow_dir || !stat.directory?)
|
405
|
+
expected = allow_dir ? 'file or directory' : 'file'
|
406
|
+
raise Bolt::FileError.new("The #{type} '#{path}' is not a #{expected}", path)
|
407
|
+
elsif stat.directory?
|
408
|
+
Dir.foreach(path) do |file|
|
409
|
+
next if %w[. ..].include?(file)
|
410
|
+
validate_file(type, File.join(path, file), allow_dir)
|
411
|
+
end
|
406
412
|
end
|
407
413
|
rescue Errno::ENOENT
|
408
414
|
raise Bolt::FileError.new("The #{type} '#{path}' does not exist", path)
|
data/lib/bolt/executor.rb
CHANGED
@@ -79,9 +79,14 @@ module Bolt
|
|
79
79
|
Array(results).each do |result|
|
80
80
|
result_promises[result.target].set(result)
|
81
81
|
end
|
82
|
-
# NotImplementedError can be thrown if the transport is implemented improperly
|
82
|
+
# NotImplementedError can be thrown if the transport is not implemented improperly
|
83
83
|
rescue StandardError, NotImplementedError => e
|
84
84
|
result_promises.each do |target, promise|
|
85
|
+
# If an exception happens while running, the result won't be logged
|
86
|
+
# by the CLI. Log a warning, as this is probably a problem with the transport.
|
87
|
+
# If batch_* commands are used from the Base transport, then exceptions
|
88
|
+
# normally shouldn't reach here.
|
89
|
+
@logger.warn(e)
|
85
90
|
promise.set(Bolt::Result.from_exception(target, e))
|
86
91
|
end
|
87
92
|
ensure
|
data/lib/bolt/pal.rb
CHANGED
@@ -179,9 +179,11 @@ module Bolt
|
|
179
179
|
def list_tasks
|
180
180
|
in_bolt_compiler do |compiler|
|
181
181
|
tasks = compiler.list_tasks
|
182
|
-
tasks.map(&:name).sort.
|
182
|
+
tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
183
183
|
task_sig = compiler.task_signature(task_name)
|
184
|
-
|
184
|
+
unless task_sig.task_hash['metadata']['private']
|
185
|
+
data << [task_name, task_sig.task_hash['metadata']['description']]
|
186
|
+
end
|
185
187
|
end
|
186
188
|
end
|
187
189
|
end
|
data/lib/bolt/task.rb
CHANGED
@@ -58,7 +58,20 @@ module Bolt
|
|
58
58
|
impl['input_method'] = inmethod unless inmethod.nil?
|
59
59
|
|
60
60
|
mfiles = impl.fetch('files', []) + metadata.fetch('files', [])
|
61
|
-
|
61
|
+
dirnames, filenames = mfiles.partition { |file| file.end_with?('/') }
|
62
|
+
impl['files'] = filenames.map do |file|
|
63
|
+
path = file_map[file]
|
64
|
+
raise "No file found for reference #{file}" if path.nil?
|
65
|
+
{ 'name' => file, 'path' => path }
|
66
|
+
end
|
67
|
+
|
68
|
+
unless dirnames.empty?
|
69
|
+
files.each do |file|
|
70
|
+
if dirnames.any? { |dirname| file['name'].start_with?(dirname) }
|
71
|
+
impl['files'] << file
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
62
75
|
|
63
76
|
impl
|
64
77
|
end
|
data/lib/bolt/transport/base.rb
CHANGED
data/lib/bolt/transport/local.rb
CHANGED
@@ -36,7 +36,7 @@ module Bolt
|
|
36
36
|
private :in_tmpdir
|
37
37
|
|
38
38
|
def copy_file(source, destination)
|
39
|
-
FileUtils.
|
39
|
+
FileUtils.cp_r(source, destination, remove_destination: true)
|
40
40
|
rescue StandardError => e
|
41
41
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
42
42
|
end
|
@@ -83,14 +83,25 @@ module Bolt
|
|
83
83
|
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
84
84
|
executable = implementation['path']
|
85
85
|
input_method = implementation['input_method'] || 'both'
|
86
|
-
|
87
|
-
# unpack any Sensitive data, write it to a separate variable because
|
88
|
-
# we log 'arguments' below
|
89
|
-
unwrapped_arguments = unwrap_sensitive_args(arguments)
|
90
|
-
stdin = STDIN_METHODS.include?(input_method) ? JSON.dump(unwrapped_arguments) : nil
|
91
|
-
env = ENVIRONMENT_METHODS.include?(input_method) ? envify_params(unwrapped_arguments) : nil
|
86
|
+
extra_files = implementation['files']
|
92
87
|
|
93
88
|
with_tmpscript(executable, target.options['tmpdir']) do |script, dir|
|
89
|
+
unless extra_files.empty?
|
90
|
+
installdir = File.join(dir, '_installdir')
|
91
|
+
arguments['_installdir'] = installdir
|
92
|
+
FileUtils.mkdir_p(extra_files.map { |file| File.join(installdir, File.dirname(file['name'])) })
|
93
|
+
|
94
|
+
extra_files.each do |file|
|
95
|
+
copy_file(file['path'], File.join(installdir, file['name']))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# unpack any Sensitive data, write it to a separate variable because
|
100
|
+
# we log 'arguments' below
|
101
|
+
unwrapped_arguments = unwrap_sensitive_args(arguments)
|
102
|
+
stdin = STDIN_METHODS.include?(input_method) ? JSON.dump(unwrapped_arguments) : nil
|
103
|
+
env = ENVIRONMENT_METHODS.include?(input_method) ? envify_params(unwrapped_arguments) : nil
|
104
|
+
|
94
105
|
# log the arguments with sensitive data redacted, do NOT log unwrapped_arguments
|
95
106
|
logger.debug("Running '#{script}' with #{arguments}")
|
96
107
|
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -2,8 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'base64'
|
4
4
|
require 'concurrent'
|
5
|
+
require 'find'
|
5
6
|
require 'json'
|
7
|
+
require 'minitar'
|
6
8
|
require 'orchestrator_client'
|
9
|
+
require 'pathname'
|
10
|
+
require 'zlib'
|
7
11
|
require 'bolt/transport/base'
|
8
12
|
require 'bolt/transport/orch/connection'
|
9
13
|
|
@@ -114,14 +118,50 @@ module Bolt
|
|
114
118
|
end
|
115
119
|
end
|
116
120
|
|
121
|
+
def pack(directory)
|
122
|
+
start_time = Time.now
|
123
|
+
io = StringIO.new
|
124
|
+
output = Minitar::Output.new(Zlib::GzipWriter.new(io))
|
125
|
+
Find.find(directory) do |file|
|
126
|
+
next unless File.file?(file)
|
127
|
+
|
128
|
+
tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
|
129
|
+
@logger.debug("Packing #{file} to #{tar_path}")
|
130
|
+
stat = File.stat(file)
|
131
|
+
content = File.binread(file)
|
132
|
+
output.tar.add_file_simple(
|
133
|
+
tar_path.to_s,
|
134
|
+
data: content,
|
135
|
+
size: content.size,
|
136
|
+
mode: stat.mode & 0o777,
|
137
|
+
mtime: stat.mtime
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
duration = Time.now - start_time
|
142
|
+
@logger.debug("Packed upload in #{duration * 1000} ms")
|
143
|
+
|
144
|
+
output.close
|
145
|
+
io.string
|
146
|
+
ensure
|
147
|
+
# Closes both tar and sgz.
|
148
|
+
output&.close
|
149
|
+
end
|
150
|
+
|
117
151
|
def batch_upload(targets, source, destination, options = {}, &callback)
|
118
|
-
|
152
|
+
stat = File.stat(source)
|
153
|
+
content = if stat.directory?
|
154
|
+
pack(source)
|
155
|
+
else
|
156
|
+
File.open(source, &:read)
|
157
|
+
end
|
119
158
|
content = Base64.encode64(content)
|
120
159
|
mode = File.stat(source).mode
|
121
160
|
params = {
|
122
161
|
'path' => destination,
|
123
162
|
'content' => content,
|
124
|
-
'mode' => mode
|
163
|
+
'mode' => mode,
|
164
|
+
'directory' => stat.directory?
|
125
165
|
}
|
126
166
|
callback ||= proc {}
|
127
167
|
results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options, &callback)
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -126,10 +126,12 @@ module Bolt
|
|
126
126
|
executable = task.file['filename']
|
127
127
|
file_content = Base64.decode64(task.file['file_content'])
|
128
128
|
input_method = task.metadata['input_method']
|
129
|
+
extra_files = []
|
129
130
|
else
|
130
131
|
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
131
132
|
executable = implementation['path']
|
132
133
|
input_method = implementation['input_method']
|
134
|
+
extra_files = implementation['files']
|
133
135
|
end
|
134
136
|
input_method ||= 'both'
|
135
137
|
|
@@ -138,18 +140,9 @@ module Bolt
|
|
138
140
|
with_connection(target, options.fetch('_load_config', true)) do |conn|
|
139
141
|
conn.running_as(options['_run_as']) do
|
140
142
|
stdin, output = nil
|
141
|
-
|
142
143
|
command = []
|
143
144
|
execute_options = {}
|
144
145
|
|
145
|
-
if STDIN_METHODS.include?(input_method)
|
146
|
-
stdin = JSON.dump(arguments)
|
147
|
-
end
|
148
|
-
|
149
|
-
if ENVIRONMENT_METHODS.include?(input_method)
|
150
|
-
execute_options[:environment] = envify_params(arguments)
|
151
|
-
end
|
152
|
-
|
153
146
|
conn.with_remote_tempdir do |dir|
|
154
147
|
remote_task_path = if from_api?(task)
|
155
148
|
conn.write_executable_from_content(dir, file_content, executable)
|
@@ -157,6 +150,24 @@ module Bolt
|
|
157
150
|
conn.write_remote_executable(dir, executable)
|
158
151
|
end
|
159
152
|
|
153
|
+
unless extra_files.empty?
|
154
|
+
# TODO: optimize upload of directories
|
155
|
+
installdir = File.join(dir.to_s, '_installdir')
|
156
|
+
arguments['_installdir'] = installdir
|
157
|
+
dir.mkdirs(extra_files.map { |file| File.join('_installdir', File.dirname(file['name'])) })
|
158
|
+
extra_files.each do |file|
|
159
|
+
conn.write_remote_file(file['path'], File.join(installdir, file['name']))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if STDIN_METHODS.include?(input_method)
|
164
|
+
stdin = JSON.dump(arguments)
|
165
|
+
end
|
166
|
+
|
167
|
+
if ENVIRONMENT_METHODS.include?(input_method)
|
168
|
+
execute_options[:environment] = envify_params(arguments)
|
169
|
+
end
|
170
|
+
|
160
171
|
if conn.run_as && stdin
|
161
172
|
wrapper = make_wrapper_stringio(remote_task_path, stdin)
|
162
173
|
remote_wrapper_path = conn.write_remote_executable(dir, wrapper, 'wrapper.sh')
|
@@ -22,6 +22,15 @@ module Bolt
|
|
22
22
|
@path
|
23
23
|
end
|
24
24
|
|
25
|
+
def mkdirs(subdirs)
|
26
|
+
abs_subdirs = subdirs.map { |subdir| File.join(@path, subdir) }
|
27
|
+
result = @node.execute(['mkdir', '-p'] + abs_subdirs)
|
28
|
+
if result.exit_code != 0
|
29
|
+
message = "Could not create subdirectories in '#{@path}': #{result.stderr.string}"
|
30
|
+
raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
25
34
|
def chown(owner)
|
26
35
|
return if owner.nil? || owner == @owner
|
27
36
|
|
@@ -92,8 +101,16 @@ module Bolt
|
|
92
101
|
|
93
102
|
options[:port] = target.port if target.port
|
94
103
|
options[:password] = target.password if target.password
|
104
|
+
# Support both net-ssh 4 and 5. We use 5 in packaging, but Beaker pins to 4 so we
|
105
|
+
# want the gem to be compatible with version 4.
|
95
106
|
options[:verify_host_key] = if target.options['host-key-check']
|
96
|
-
Net::SSH::Verifiers::
|
107
|
+
if defined?(Net::SSH::Verifiers::Always)
|
108
|
+
Net::SSH::Verifiers::Always.new
|
109
|
+
else
|
110
|
+
Net::SSH::Verifiers::Secure.new
|
111
|
+
end
|
112
|
+
elsif defined?(Net::SSH::Verifiers::Never)
|
113
|
+
Net::SSH::Verifiers::Never.new
|
97
114
|
else
|
98
115
|
Net::SSH::Verifiers::Null.new
|
99
116
|
end
|
@@ -281,7 +298,7 @@ module Bolt
|
|
281
298
|
end
|
282
299
|
|
283
300
|
def write_remote_file(source, destination)
|
284
|
-
@session.scp.upload!(source, destination)
|
301
|
+
@session.scp.upload!(source, destination, recursive: true)
|
285
302
|
rescue StandardError => e
|
286
303
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
287
304
|
end
|
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -112,36 +112,49 @@ catch
|
|
112
112
|
executable = task.file['filename']
|
113
113
|
file_content = StringIO.new(Base64.decode64(task.file['file_content']))
|
114
114
|
input_method = task.metadata['input_method']
|
115
|
+
extra_files = []
|
115
116
|
else
|
116
117
|
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
117
118
|
executable = implementation['path']
|
118
119
|
input_method = implementation['input_method']
|
120
|
+
extra_files = implementation['files']
|
119
121
|
end
|
120
122
|
input_method ||= powershell_file?(executable) ? 'powershell' : 'both'
|
121
123
|
|
122
124
|
# unpack any Sensitive data
|
123
125
|
arguments = unwrap_sensitive_args(arguments)
|
124
126
|
with_connection(target) do |conn|
|
125
|
-
if STDIN_METHODS.include?(input_method)
|
126
|
-
stdin = JSON.dump(arguments)
|
127
|
-
end
|
128
|
-
|
129
|
-
if ENVIRONMENT_METHODS.include?(input_method)
|
130
|
-
envify_params(arguments).each do |(arg, val)|
|
131
|
-
cmd = "[Environment]::SetEnvironmentVariable('#{arg}', @'\n#{val}\n'@)"
|
132
|
-
result = conn.execute(cmd)
|
133
|
-
if result.exit_code != 0
|
134
|
-
raise Bolt::Node::EnvironmentVarError.new(arg, val)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
127
|
conn.with_remote_tempdir do |dir|
|
140
128
|
remote_task_path = if from_api?(task)
|
141
129
|
conn.write_executable_from_content(dir, file_content, executable)
|
142
130
|
else
|
143
131
|
conn.write_remote_executable(dir, executable)
|
144
132
|
end
|
133
|
+
|
134
|
+
unless extra_files.empty?
|
135
|
+
# TODO: optimize upload of directories
|
136
|
+
installdir = File.join(dir, '_installdir')
|
137
|
+
arguments['_installdir'] = installdir
|
138
|
+
conn.mkdirs(extra_files.map { |file| File.join(installdir, File.dirname(file['name'])) })
|
139
|
+
extra_files.each do |file|
|
140
|
+
conn.write_remote_file(file['path'], File.join(installdir, file['name']))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if STDIN_METHODS.include?(input_method)
|
145
|
+
stdin = JSON.dump(arguments)
|
146
|
+
end
|
147
|
+
|
148
|
+
if ENVIRONMENT_METHODS.include?(input_method)
|
149
|
+
envify_params(arguments).each do |(arg, val)|
|
150
|
+
cmd = "[Environment]::SetEnvironmentVariable('#{arg}', @'\n#{val}\n'@)"
|
151
|
+
result = conn.execute(cmd)
|
152
|
+
if result.exit_code != 0
|
153
|
+
raise Bolt::Node::EnvironmentVarError.new(arg, val)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
145
158
|
conn.shell_init
|
146
159
|
output =
|
147
160
|
if powershell_file?(remote_task_path) && stdin.nil?
|
@@ -334,6 +334,14 @@ exit $LASTEXITCODE
|
|
334
334
|
PS
|
335
335
|
end
|
336
336
|
|
337
|
+
def mkdirs(dirs)
|
338
|
+
result = execute("mkdir -Force #{dirs.uniq.sort.join(',')}")
|
339
|
+
if result.exit_code != 0
|
340
|
+
message = "Could not create directories: #{result.stderr}"
|
341
|
+
raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
337
345
|
def write_remote_file(source, destination)
|
338
346
|
fs = ::WinRM::FS::FileManager.new(@connection)
|
339
347
|
fs.upload(source, destination)
|
data/lib/bolt/version.rb
CHANGED
@@ -45,6 +45,10 @@
|
|
45
45
|
"type": "string",
|
46
46
|
"description": "The directory to upload and execute temporary files on the target"
|
47
47
|
},
|
48
|
+
"tty": {
|
49
|
+
"type": "boolean",
|
50
|
+
"description": "Should bolt use pseudo tty to meet sudoer restrictions"
|
51
|
+
},
|
48
52
|
"host-key-check": {
|
49
53
|
"type": "boolean",
|
50
54
|
"description": "Whether to perform host key validation when connecting over SSH"
|
@@ -65,7 +69,7 @@
|
|
65
69
|
"parameters": {
|
66
70
|
"type": "object",
|
67
71
|
"description": "JSON formatted parameters to be provided to task"
|
68
|
-
}
|
72
|
+
}
|
69
73
|
},
|
70
74
|
"required": ["target", "task"]
|
71
|
-
}
|
75
|
+
}
|
data/lib/bolt_ext/server.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'sinatra'
|
4
4
|
require 'bolt'
|
5
|
+
require 'bolt/target'
|
5
6
|
require 'bolt/task'
|
6
7
|
require 'json'
|
7
8
|
require 'json-schema'
|
@@ -50,8 +51,10 @@ class TransportAPI < Sinatra::Base
|
|
50
51
|
schema_error = JSON::Validator.fully_validate(@schemas["ssh-run_task"], body)
|
51
52
|
return [400, schema_error.join] if schema_error.any?
|
52
53
|
|
53
|
-
|
54
|
-
|
54
|
+
# CODEREVIEW: the schema is additionalProperties false do we need this?
|
55
|
+
keys = %w[user password port connect-timeout run-as-command run-as
|
56
|
+
tmpdir host-key-check known-hosts-content private-key-content sudo-password
|
57
|
+
tty]
|
55
58
|
opts = body['target'].select { |k, _| keys.include? k }
|
56
59
|
|
57
60
|
if opts['private-key-content']
|
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.1.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-
|
11
|
+
date: 2018-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -84,16 +84,16 @@ dependencies:
|
|
84
84
|
name: net-ssh
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '4.
|
89
|
+
version: '4.0'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '4.
|
96
|
+
version: '4.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: orchestrator_client
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|