train-core 3.4.1 → 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcdd1ac651c1272f119e63d0edb3d9267b4811088a0e3133b90d1fa969c79258
4
- data.tar.gz: '09c95e4d2627b99f5c878336f84f416fd61ee46225362973b81ad0d4b70559a2'
3
+ metadata.gz: 4debe973595fd78f3fc61e3ebb8d62061d5d6a72f72077ae972422a24c8fa403
4
+ data.tar.gz: 9696bd7bc5ad5de2cbaa1fef3b8cf38deb34ebb4a78b61fe85acf648559d50c4
5
5
  SHA512:
6
- metadata.gz: f0bec9907895f76f5e211fafdc1e1adc5ea7603a5db7c6e3ac4e098102976aa13c0ca2e2c2fa4eb8d67666820e516336a063a32687a4ba22683cdec8fc7a4584
7
- data.tar.gz: 3c2444b2309bccff9f61f52a0c235b090e7d3040a01ec084da895a287a600708278b934e84a9f268c30fa2e306035cd3eeb6621fd201cd7492f5af5560b04a13
6
+ metadata.gz: a4edf4fadbfe0f9e9ee8febd26bfa806d953129ed06e1c892acde68ffa3fcb0820bef51f5eeacf31f61ba83e3ed0084cec5ed2cc34911974338a20e1b3c0c580
7
+ data.tar.gz: e37d56303596018a0185b569c85fe72a38a00d0d99ee597f9cd42012142eb33eb0483c74e7d17fd1141c77982e7346507ad4f1d0c7fd31614fc84b66803ad99a
@@ -15,6 +15,12 @@ module Train
15
15
  nil
16
16
  end
17
17
 
18
+ def content=(new_content)
19
+ ::File.open(@path, "w", encoding: "UTF-8") { |fp| fp.write(new_content) }
20
+
21
+ @content = new_content
22
+ end
23
+
18
24
  def link_path
19
25
  return nil unless symlink?
20
26
 
@@ -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 :hostname
33
- attr_reader :transport_options
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 IdentitiesOnly=yes } if options[:keys]
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
- Array(options[:keys]).each do |ssh_key|
80
- args += %W{ -i #{ssh_key} }
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
@@ -2,5 +2,5 @@
2
2
  # Author:: Dominik Richter (<dominik.richter@gmail.com>)
3
3
 
4
4
  module Train
5
- VERSION = "3.4.1".freeze
5
+ VERSION = "3.5.2".freeze
6
6
  end
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.1
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: 2020-12-07 00:00:00.000000000 Z
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.0.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.