train-core 3.4.1 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/train/file/local.rb +6 -0
- data/lib/train/file/remote/unix.rb +16 -0
- data/lib/train/file/remote/windows.rb +10 -0
- data/lib/train/plugins/base_connection.rb +43 -0
- data/lib/train/transports/cisco_ios_connection.rb +8 -0
- data/lib/train/transports/local.rb +36 -6
- data/lib/train/transports/ssh_connection.rb +23 -8
- data/lib/train/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4debe973595fd78f3fc61e3ebb8d62061d5d6a72f72077ae972422a24c8fa403
|
4
|
+
data.tar.gz: 9696bd7bc5ad5de2cbaa1fef3b8cf38deb34ebb4a78b61fe85acf648559d50c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4edf4fadbfe0f9e9ee8febd26bfa806d953129ed06e1c892acde68ffa3fcb0820bef51f5eeacf31f61ba83e3ed0084cec5ed2cc34911974338a20e1b3c0c580
|
7
|
+
data.tar.gz: e37d56303596018a0185b569c85fe72a38a00d0d99ee597f9cd42012142eb33eb0483c74e7d17fd1141c77982e7346507ad4f1d0c7fd31614fc84b66803ad99a
|
data/lib/train/file/local.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "base64" unless defined?(Base64)
|
1
2
|
require "shellwords" unless defined?(Shellwords)
|
2
3
|
|
3
4
|
module Train
|
@@ -19,6 +20,21 @@ module Train
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
23
|
+
def content=(new_content)
|
24
|
+
execute_result = @backend.run_command("base64 --help")
|
25
|
+
if execute_result.exit_status != 0
|
26
|
+
raise TransportError, "#{self.class} found no base64 binary for file writes"
|
27
|
+
end
|
28
|
+
|
29
|
+
unix_cmd = format("echo '%<base64>s' | base64 --decode > %<file>s",
|
30
|
+
base64: Base64.strict_encode64(new_content),
|
31
|
+
file: @spath)
|
32
|
+
|
33
|
+
@backend.run_command(unix_cmd)
|
34
|
+
|
35
|
+
@content = new_content
|
36
|
+
end
|
37
|
+
|
22
38
|
def exist?
|
23
39
|
@exist ||= begin
|
24
40
|
f = @follow_symlink ? "" : " || test -L #{@spath}"
|
@@ -26,6 +26,16 @@ module Train
|
|
26
26
|
@content
|
27
27
|
end
|
28
28
|
|
29
|
+
def content=(new_content)
|
30
|
+
win_cmd = format('[IO.File]::WriteAllBytes("%<file>s", [Convert]::FromBase64String("%<base64>s"))',
|
31
|
+
base64: Base64.strict_encode64(new_content),
|
32
|
+
file: @spath)
|
33
|
+
|
34
|
+
@backend.run_command(win_cmd)
|
35
|
+
|
36
|
+
@content = new_content
|
37
|
+
end
|
38
|
+
|
29
39
|
def exist?
|
30
40
|
return @exist if defined?(@exist)
|
31
41
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require_relative "../errors"
|
2
2
|
require_relative "../extras"
|
3
3
|
require_relative "../file"
|
4
|
+
require "fileutils" unless defined?(FileUtils)
|
4
5
|
require "logger"
|
5
6
|
|
6
7
|
class Train::Plugins::Transport
|
@@ -161,6 +162,48 @@ class Train::Plugins::Transport
|
|
161
162
|
@cache[:file][path] ||= file_via_connection(path, *args)
|
162
163
|
end
|
163
164
|
|
165
|
+
# Uploads local files or directories to remote host.
|
166
|
+
#
|
167
|
+
# @param locals [Array<String>] paths to local files or directories
|
168
|
+
# @param remote [String] path to remote destination
|
169
|
+
# @raise [TransportFailed] if the files could not all be uploaded
|
170
|
+
# successfully, which may vary by implementation
|
171
|
+
def upload(locals, remote)
|
172
|
+
unless file(remote).directory?
|
173
|
+
raise TransportError, "#{self.class} expects remote directory as second upload parameter"
|
174
|
+
end
|
175
|
+
|
176
|
+
Array(locals).each do |local|
|
177
|
+
new_content = File.read(local)
|
178
|
+
remote_file = File.join(remote, File.basename(local))
|
179
|
+
|
180
|
+
logger.debug("Attempting to upload '#{local}' as file #{remote_file}")
|
181
|
+
|
182
|
+
file(remote_file).content = new_content
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Download remote files or directories to local host.
|
187
|
+
#
|
188
|
+
# @param remotes [Array<String>] paths to remote files or directories
|
189
|
+
# @param local [String] path to local destination. If `local` is an
|
190
|
+
# existing directory, `remote` will be downloaded into the directory
|
191
|
+
# using its original name
|
192
|
+
# @raise [TransportFailed] if the files could not all be downloaded
|
193
|
+
# successfully, which may vary by implementation
|
194
|
+
def download(remotes, local)
|
195
|
+
FileUtils.mkdir_p(File.dirname(local))
|
196
|
+
|
197
|
+
Array(remotes).each do |remote|
|
198
|
+
new_content = file(remote).content
|
199
|
+
local_file = File.join(local, File.basename(remote))
|
200
|
+
|
201
|
+
logger.debug("Attempting to download '#{remote}' as file #{local_file}")
|
202
|
+
|
203
|
+
File.open(local_file, "w") { |fp| fp.write(new_content) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
164
207
|
# Builds a LoginCommand which can be used to open an interactive
|
165
208
|
# session on the remote host.
|
166
209
|
#
|
@@ -29,6 +29,14 @@ class Train::Transports::SSH
|
|
29
29
|
result.stdout.split(" ")[-1]
|
30
30
|
end
|
31
31
|
|
32
|
+
def upload(locals, remote)
|
33
|
+
raise NotImplementedError, "#{self.class} does not implement #upload()"
|
34
|
+
end
|
35
|
+
|
36
|
+
def download(remotes, local)
|
37
|
+
raise NotImplementedError, "#{self.class} does not implement #download()"
|
38
|
+
end
|
39
|
+
|
32
40
|
private
|
33
41
|
|
34
42
|
def establish_connection
|
@@ -31,10 +31,26 @@ module Train::Transports
|
|
31
31
|
nil # none, open your shell
|
32
32
|
end
|
33
33
|
|
34
|
+
def close
|
35
|
+
@runner.close
|
36
|
+
end
|
37
|
+
|
34
38
|
def uri
|
35
39
|
"local://"
|
36
40
|
end
|
37
41
|
|
42
|
+
def upload(locals, remote)
|
43
|
+
FileUtils.mkdir_p(remote)
|
44
|
+
|
45
|
+
Array(locals).each do |local|
|
46
|
+
FileUtils.cp_r(local, remote)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def download(remotes, local)
|
51
|
+
upload(remotes, local)
|
52
|
+
end
|
53
|
+
|
38
54
|
private
|
39
55
|
|
40
56
|
def select_runner(options)
|
@@ -108,6 +124,10 @@ module Train::Transports
|
|
108
124
|
res.run_command
|
109
125
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
110
126
|
end
|
127
|
+
|
128
|
+
def close
|
129
|
+
# nothing to do at the moment
|
130
|
+
end
|
111
131
|
end
|
112
132
|
|
113
133
|
class WindowsShellRunner
|
@@ -132,6 +152,10 @@ module Train::Transports
|
|
132
152
|
res.run_command
|
133
153
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
134
154
|
end
|
155
|
+
|
156
|
+
def close
|
157
|
+
# nothing to do at the moment
|
158
|
+
end
|
135
159
|
end
|
136
160
|
|
137
161
|
class WindowsPipeRunner
|
@@ -141,6 +165,7 @@ module Train::Transports
|
|
141
165
|
|
142
166
|
def initialize(powershell_cmd = "powershell")
|
143
167
|
@powershell_cmd = powershell_cmd
|
168
|
+
@server_pid = nil
|
144
169
|
@pipe = acquire_pipe
|
145
170
|
raise PipeError if @pipe.nil?
|
146
171
|
end
|
@@ -160,12 +185,21 @@ module Train::Transports
|
|
160
185
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
161
186
|
end
|
162
187
|
|
188
|
+
def close
|
189
|
+
Process.kill("KILL", @server_pid) unless @server_pid.nil?
|
190
|
+
@server_pid = nil
|
191
|
+
end
|
192
|
+
|
163
193
|
private
|
164
194
|
|
165
195
|
def acquire_pipe
|
196
|
+
require "win32/process"
|
166
197
|
pipe_name = "inspec_#{SecureRandom.hex}"
|
167
198
|
|
168
|
-
start_pipe_server(pipe_name)
|
199
|
+
@server_pid = start_pipe_server(pipe_name)
|
200
|
+
|
201
|
+
# Ensure process is killed when the Train process exits
|
202
|
+
at_exit { close rescue Errno::EIO }
|
169
203
|
|
170
204
|
pipe = nil
|
171
205
|
|
@@ -227,11 +261,7 @@ module Train::Transports
|
|
227
261
|
utf8_script = script.encode("UTF-16LE", "UTF-8")
|
228
262
|
base64_script = Base64.strict_encode64(utf8_script)
|
229
263
|
cmd = "#{@powershell_cmd} -NoProfile -ExecutionPolicy bypass -NonInteractive -EncodedCommand #{base64_script}"
|
230
|
-
|
231
|
-
server_pid = Process.create(command_line: cmd).process_id
|
232
|
-
|
233
|
-
# Ensure process is killed when the Train process exits
|
234
|
-
at_exit { Process.kill("KILL", server_pid) }
|
264
|
+
Process.create(command_line: cmd).process_id
|
235
265
|
end
|
236
266
|
end
|
237
267
|
end
|
@@ -29,8 +29,8 @@ class Train::Transports::SSH
|
|
29
29
|
#
|
30
30
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
31
31
|
class Connection < BaseConnection # rubocop:disable Metrics/ClassLength
|
32
|
-
attr_reader
|
33
|
-
|
32
|
+
attr_reader :hostname
|
33
|
+
attr_accessor :transport_options
|
34
34
|
|
35
35
|
def initialize(options)
|
36
36
|
# Track IOS command retries to prevent infinite loop on IOError. This must
|
@@ -72,13 +72,18 @@ class Train::Transports::SSH
|
|
72
72
|
|
73
73
|
args = %w{ -o UserKnownHostsFile=/dev/null }
|
74
74
|
args += %w{ -o StrictHostKeyChecking=no }
|
75
|
-
args += %w{ -o
|
76
|
-
args += %w{ -o BatchMode=yes } if options[:non_interactive]
|
75
|
+
args += %w{ -o BatchMode=yes } if options[:non_interactive]
|
77
76
|
args += %W{ -o LogLevel=#{level} }
|
78
77
|
args += %W{ -o ForwardAgent=#{fwd_agent} } if options.key?(:forward_agent)
|
79
|
-
|
80
|
-
|
78
|
+
|
79
|
+
keys = Array(options[:keys])
|
80
|
+
unless keys.empty?
|
81
|
+
args += %w{ -o IdentitiesOnly=yes }
|
82
|
+
keys.each do |ssh_key|
|
83
|
+
args += %W{ -i #{ssh_key} }
|
84
|
+
end
|
81
85
|
end
|
86
|
+
|
82
87
|
args
|
83
88
|
end
|
84
89
|
|
@@ -242,6 +247,16 @@ class Train::Transports::SSH
|
|
242
247
|
|
243
248
|
exit_status, stdout, stderr = execute_on_channel(cmd, opts, &data_handler)
|
244
249
|
|
250
|
+
# An interactive console might contain the STDERR in STDOUT
|
251
|
+
# concat both outputs for non-zero exit status.
|
252
|
+
output = "#{stdout} #{stderr}".strip if exit_status != 0
|
253
|
+
|
254
|
+
# Abstract the su - USER authentication failure
|
255
|
+
# raise the Train::UserError and passes message & reason
|
256
|
+
if output && output.match?("su: Authentication failure")
|
257
|
+
raise Train::UserError.new(output, :bad_su_user_password)
|
258
|
+
end
|
259
|
+
|
245
260
|
# Since `@session.loop` succeeded, reset the IOS command retry counter
|
246
261
|
@ios_cmd_retries = 0
|
247
262
|
|
@@ -317,12 +332,12 @@ class Train::Transports::SSH
|
|
317
332
|
channel.exec(cmd) do |_, success|
|
318
333
|
abort "Couldn't execute command on SSH." unless success
|
319
334
|
channel.on_data do |_, data|
|
320
|
-
yield(data) if block_given?
|
335
|
+
yield(data, channel) if block_given?
|
321
336
|
stdout += data
|
322
337
|
end
|
323
338
|
|
324
339
|
channel.on_extended_data do |_, _type, data|
|
325
|
-
yield(data) if block_given?
|
340
|
+
yield(data, channel) if block_given?
|
326
341
|
stderr += data
|
327
342
|
end
|
328
343
|
|
data/lib/train/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef InSpec Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -188,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
188
|
- !ruby/object:Gem::Version
|
189
189
|
version: '0'
|
190
190
|
requirements: []
|
191
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.1.4
|
192
192
|
signing_key:
|
193
193
|
specification_version: 4
|
194
194
|
summary: Transport interface to talk to a selected set of backends.
|