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 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.