train-core 3.4.8 → 3.6.0
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 +54 -13
- data/lib/train/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a7d91d3461558b977d37ebc8b71a904995e096cd42776b4d63df73314985c5d
|
|
4
|
+
data.tar.gz: b6b8ccaec9143f42ed4a2a6f03d49335a199f3ba76f31a63a4d26ded880cf397
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d034aa2c18e2d875969a1bae10d81a3afae603d6ed7112344caccbcedcf4dd0c267b60bc29bf0213998add5aa4e9b190274980a8ed17804034824c1753c3158
|
|
7
|
+
data.tar.gz: f0c2a005d0dd5c27cd3ccb6128f49ed3ea748460cfd32deb1742549ac4eef7f09298a8532ea791fb76cf13ab0eb02d0b023dbebad9bb3ccfe0e74fbb5eb2fdc0
|
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)
|
|
@@ -70,9 +86,9 @@ module Train::Transports
|
|
|
70
86
|
end
|
|
71
87
|
end
|
|
72
88
|
|
|
73
|
-
def run_command_via_connection(cmd, &_data_handler)
|
|
89
|
+
def run_command_via_connection(cmd, opts, &_data_handler)
|
|
74
90
|
# Use the runner if it is available
|
|
75
|
-
return @runner.run_command(cmd) if defined?(@runner)
|
|
91
|
+
return @runner.run_command(cmd, opts) if defined?(@runner)
|
|
76
92
|
|
|
77
93
|
# If we don't have a runner, such as at the beginning of setting up the
|
|
78
94
|
# transport and performing the first few steps of OS detection, fall
|
|
@@ -99,15 +115,24 @@ module Train::Transports
|
|
|
99
115
|
@cmd_wrapper = Local::CommandWrapper.load(connection, options)
|
|
100
116
|
end
|
|
101
117
|
|
|
102
|
-
def run_command(cmd)
|
|
118
|
+
def run_command(cmd, opts = {})
|
|
103
119
|
if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
|
|
104
120
|
cmd = @cmd_wrapper.run(cmd)
|
|
105
121
|
end
|
|
106
122
|
|
|
107
123
|
res = Mixlib::ShellOut.new(cmd)
|
|
108
|
-
res.
|
|
124
|
+
res.timeout = opts[:timeout]
|
|
125
|
+
begin
|
|
126
|
+
res.run_command
|
|
127
|
+
rescue Mixlib::ShellOut::CommandTimeout
|
|
128
|
+
raise Train::CommandTimeoutReached
|
|
129
|
+
end
|
|
109
130
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
|
110
131
|
end
|
|
132
|
+
|
|
133
|
+
def close
|
|
134
|
+
# nothing to do at the moment
|
|
135
|
+
end
|
|
111
136
|
end
|
|
112
137
|
|
|
113
138
|
class WindowsShellRunner
|
|
@@ -118,7 +143,7 @@ module Train::Transports
|
|
|
118
143
|
@powershell_cmd = powershell_cmd
|
|
119
144
|
end
|
|
120
145
|
|
|
121
|
-
def run_command(script)
|
|
146
|
+
def run_command(script, opts)
|
|
122
147
|
# Prevent progress stream from leaking into stderr
|
|
123
148
|
script = "$ProgressPreference='SilentlyContinue';" + script
|
|
124
149
|
|
|
@@ -129,9 +154,18 @@ module Train::Transports
|
|
|
129
154
|
cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
|
|
130
155
|
|
|
131
156
|
res = Mixlib::ShellOut.new(cmd)
|
|
132
|
-
res.
|
|
157
|
+
res.timeout = opts[:timeout]
|
|
158
|
+
begin
|
|
159
|
+
res.run_command
|
|
160
|
+
rescue Mixlib::ShellOut::CommandTimeout
|
|
161
|
+
raise Train::CommandTimeoutReached
|
|
162
|
+
end
|
|
133
163
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
|
134
164
|
end
|
|
165
|
+
|
|
166
|
+
def close
|
|
167
|
+
# nothing to do at the moment
|
|
168
|
+
end
|
|
135
169
|
end
|
|
136
170
|
|
|
137
171
|
class WindowsPipeRunner
|
|
@@ -141,6 +175,7 @@ module Train::Transports
|
|
|
141
175
|
|
|
142
176
|
def initialize(powershell_cmd = "powershell")
|
|
143
177
|
@powershell_cmd = powershell_cmd
|
|
178
|
+
@server_pid = nil
|
|
144
179
|
@pipe = acquire_pipe
|
|
145
180
|
raise PipeError if @pipe.nil?
|
|
146
181
|
end
|
|
@@ -151,21 +186,31 @@ module Train::Transports
|
|
|
151
186
|
# A command that succeeds without setting an exit code will have exitstatus 0
|
|
152
187
|
# A command that exits with an exit code will have that value as exitstatus
|
|
153
188
|
# A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
|
|
154
|
-
def run_command(cmd)
|
|
189
|
+
def run_command(cmd, _opts)
|
|
155
190
|
script = "$ProgressPreference='SilentlyContinue';" + cmd
|
|
156
191
|
encoded_script = Base64.strict_encode64(script)
|
|
192
|
+
# TODO: no way to safely implement timeouts here.
|
|
157
193
|
@pipe.puts(encoded_script)
|
|
158
194
|
@pipe.flush
|
|
159
195
|
res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
|
|
160
196
|
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
|
161
197
|
end
|
|
162
198
|
|
|
199
|
+
def close
|
|
200
|
+
Process.kill("KILL", @server_pid) unless @server_pid.nil?
|
|
201
|
+
@server_pid = nil
|
|
202
|
+
end
|
|
203
|
+
|
|
163
204
|
private
|
|
164
205
|
|
|
165
206
|
def acquire_pipe
|
|
207
|
+
require "win32/process"
|
|
166
208
|
pipe_name = "inspec_#{SecureRandom.hex}"
|
|
167
209
|
|
|
168
|
-
start_pipe_server(pipe_name)
|
|
210
|
+
@server_pid = start_pipe_server(pipe_name)
|
|
211
|
+
|
|
212
|
+
# Ensure process is killed when the Train process exits
|
|
213
|
+
at_exit { close rescue Errno::EIO }
|
|
169
214
|
|
|
170
215
|
pipe = nil
|
|
171
216
|
|
|
@@ -227,11 +272,7 @@ module Train::Transports
|
|
|
227
272
|
utf8_script = script.encode("UTF-16LE", "UTF-8")
|
|
228
273
|
base64_script = Base64.strict_encode64(utf8_script)
|
|
229
274
|
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) }
|
|
275
|
+
Process.create(command_line: cmd).process_id
|
|
235
276
|
end
|
|
236
277
|
end
|
|
237
278
|
end
|
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.6.0
|
|
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: 2021-
|
|
11
|
+
date: 2021-04-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|