train-core 3.5.2 → 3.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4debe973595fd78f3fc61e3ebb8d62061d5d6a72f72077ae972422a24c8fa403
4
- data.tar.gz: 9696bd7bc5ad5de2cbaa1fef3b8cf38deb34ebb4a78b61fe85acf648559d50c4
3
+ metadata.gz: 7e0529be878892640599f401af47c6c012a8d060fea3113895872b0af7e24127
4
+ data.tar.gz: 4b387bc5129fbf652b37713a63d74173d8d04f3ea5dbdf9993cadf0d276f3add
5
5
  SHA512:
6
- metadata.gz: a4edf4fadbfe0f9e9ee8febd26bfa806d953129ed06e1c892acde68ffa3fcb0820bef51f5eeacf31f61ba83e3ed0084cec5ed2cc34911974338a20e1b3c0c580
7
- data.tar.gz: e37d56303596018a0185b569c85fe72a38a00d0d99ee597f9cd42012142eb33eb0483c74e7d17fd1141c77982e7346507ad4f1d0c7fd31614fc84b66803ad99a
6
+ metadata.gz: 7fdf259b32a3dc0f033681b2f98e164f2ef76c8b4e7f51fb5bc1fe2412a1501039cdddfc06ef409f7b235dbcefef693f97c7fff21c02d7011618fdfc5ad09a9d
7
+ data.tar.gz: 2f90638d94542daf5e70488f6fd5e592986d3a76ac6b10f188414a109b53fffc8000202ce0a853f68bc1a8c1dfee9178107da5fee2b51728a01b1863b3976baf
data/lib/train/options.rb CHANGED
@@ -59,6 +59,9 @@ module Train
59
59
  default = hm[:default]
60
60
  if default.is_a? Proc
61
61
  res[field] = default.call(res)
62
+ elsif hm.key?(:coerce)
63
+ field_value = hm[:coerce].call(res)
64
+ res[field] = field_value.nil? ? default : field_value
62
65
  else
63
66
  res[field] = default
64
67
  end
@@ -86,9 +86,9 @@ module Train::Transports
86
86
  end
87
87
  end
88
88
 
89
- def run_command_via_connection(cmd, &_data_handler)
89
+ def run_command_via_connection(cmd, opts, &_data_handler)
90
90
  # Use the runner if it is available
91
- return @runner.run_command(cmd) if defined?(@runner)
91
+ return @runner.run_command(cmd, opts) if defined?(@runner)
92
92
 
93
93
  # If we don't have a runner, such as at the beginning of setting up the
94
94
  # transport and performing the first few steps of OS detection, fall
@@ -115,13 +115,18 @@ module Train::Transports
115
115
  @cmd_wrapper = Local::CommandWrapper.load(connection, options)
116
116
  end
117
117
 
118
- def run_command(cmd)
118
+ def run_command(cmd, opts = {})
119
119
  if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
120
120
  cmd = @cmd_wrapper.run(cmd)
121
121
  end
122
122
 
123
123
  res = Mixlib::ShellOut.new(cmd)
124
- res.run_command
124
+ res.timeout = opts[:timeout]
125
+ begin
126
+ res.run_command
127
+ rescue Mixlib::ShellOut::CommandTimeout
128
+ raise Train::CommandTimeoutReached
129
+ end
125
130
  Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
126
131
  end
127
132
 
@@ -138,7 +143,7 @@ module Train::Transports
138
143
  @powershell_cmd = powershell_cmd
139
144
  end
140
145
 
141
- def run_command(script)
146
+ def run_command(script, opts)
142
147
  # Prevent progress stream from leaking into stderr
143
148
  script = "$ProgressPreference='SilentlyContinue';" + script
144
149
 
@@ -149,7 +154,12 @@ module Train::Transports
149
154
  cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
150
155
 
151
156
  res = Mixlib::ShellOut.new(cmd)
152
- res.run_command
157
+ res.timeout = opts[:timeout]
158
+ begin
159
+ res.run_command
160
+ rescue Mixlib::ShellOut::CommandTimeout
161
+ raise Train::CommandTimeoutReached
162
+ end
153
163
  Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
154
164
  end
155
165
 
@@ -176,9 +186,10 @@ module Train::Transports
176
186
  # A command that succeeds without setting an exit code will have exitstatus 0
177
187
  # A command that exits with an exit code will have that value as exitstatus
178
188
  # A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
179
- def run_command(cmd)
189
+ def run_command(cmd, _opts)
180
190
  script = "$ProgressPreference='SilentlyContinue';" + cmd
181
191
  encoded_script = Base64.strict_encode64(script)
192
+ # TODO: no way to safely implement timeouts here.
182
193
  @pipe.puts(encoded_script)
183
194
  @pipe.flush
184
195
  res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
@@ -43,8 +43,8 @@ module Train::Transports
43
43
 
44
44
  # common target configuration
45
45
  option :host, required: true
46
- option :port, default: 22, required: true
47
- option :user, default: "root", required: true
46
+ option :port, default: 22, coerce: proc { |u| read_options_from_ssh_config(u, :port) }, required: true
47
+ option :user, default: "root", coerce: proc { |u| read_options_from_ssh_config(u, :user) }, required: true
48
48
  option :key_files, default: nil
49
49
  option :password, default: nil
50
50
 
@@ -86,6 +86,14 @@ module Train::Transports
86
86
  end
87
87
  end
88
88
 
89
+ # Returns the ssh config option like user, port from config files
90
+ # Params options [Hash], option_type [String]
91
+ # Return String
92
+ def self.read_options_from_ssh_config(options, option_type)
93
+ config_options = Net::SSH.configuration_for(options[:host], true)
94
+ config_options[option_type]
95
+ end
96
+
89
97
  private
90
98
 
91
99
  def reusable_connection?(conn_opts)
@@ -278,5 +286,6 @@ module Train::Transports
278
286
  yield @connection if block_given?
279
287
  @connection
280
288
  end
289
+
281
290
  end
282
291
  end
@@ -32,6 +32,10 @@ class Train::Transports::SSH
32
32
  attr_reader :hostname
33
33
  attr_accessor :transport_options
34
34
 
35
+ # If we use the GNU timeout utility to timout a command server-side, it will
36
+ # exit with this status code if the command timed out.
37
+ GNU_TIMEOUT_EXIT_STATUS = 124
38
+
35
39
  def initialize(options)
36
40
  # Track IOS command retries to prevent infinite loop on IOError. This must
37
41
  # be done before `super()` because the parent runs detection commands.
@@ -321,9 +325,12 @@ class Train::Transports::SSH
321
325
  # wrap commands if that is configured
322
326
  cmd = @cmd_wrapper.run(cmd) if @cmd_wrapper
323
327
 
328
+ # Timeout the command if requested and able
329
+ cmd = "timeout #{timeout}s #{cmd}" if timeout && timeoutable?(cmd)
330
+
324
331
  logger.debug("[SSH] #{self} cmd = #{cmd}")
325
332
 
326
- if @transport_options[:pty] || timeout
333
+ if @transport_options[:pty]
327
334
  channel.request_pty do |_ch, success|
328
335
  raise Train::Transports::SSHPTYFailed, "Requesting PTY failed" unless success
329
336
  end
@@ -350,21 +357,30 @@ class Train::Transports::SSH
350
357
  end
351
358
  end
352
359
  end
360
+ session.loop
353
361
 
354
- thr = Thread.new { session.loop }
355
-
356
- if timeout
357
- res = thr.join(timeout)
358
- unless res
359
- logger.debug("train ssh command '#{cmd}' reached requested timeout (#{timeout}s)")
360
- session.channels.each_value { |c| c.eof!; c.close }
361
- raise Train::CommandTimeoutReached.new "ssh command reached timeout (#{timeout}s)"
362
- end
363
- else
364
- thr.join
362
+ if timeout && timeoutable?(cmd) && exit_status == GNU_TIMEOUT_EXIT_STATUS
363
+ logger.debug("train ssh command '#{cmd}' reached requested timeout (#{timeout}s)")
364
+ session.channels.each_value { |c| c.eof!; c.close }
365
+ raise Train::CommandTimeoutReached.new "ssh command reached timeout (#{timeout}s)"
365
366
  end
366
367
 
367
368
  [exit_status, stdout, stderr]
368
369
  end
370
+
371
+ # Returns true if we think we can attempt to timeout the command
372
+ def timeoutable?(cmd)
373
+ have_timeout_cli? && !cmd.include?("|") # Don't try to timeout a command that has pipes
374
+ end
375
+
376
+ # Returns true if the GNU timeout command is available
377
+ def have_timeout_cli?
378
+ return @have_timeout_cli unless @have_timeout_cli.nil?
379
+
380
+ res = session.exec!("timeout --version")
381
+ @have_timeout_cli = res.exitstatus == 0
382
+ logger.debug("train ssh have_timeout_cli status is '#{@have_timeout_cli}'")
383
+ @have_timeout_cli
384
+ end
369
385
  end
370
386
  end
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.5.2".freeze
5
+ VERSION = "3.7.0".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.5.2
4
+ version: 3.7.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-02-22 00:00:00.000000000 Z
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable