train 3.2.14 → 3.2.20

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. metadata +29 -149
  3. data/LICENSE +0 -201
  4. data/lib/train.rb +0 -193
  5. data/lib/train/errors.rb +0 -44
  6. data/lib/train/extras.rb +0 -11
  7. data/lib/train/extras/command_wrapper.rb +0 -201
  8. data/lib/train/extras/stat.rb +0 -136
  9. data/lib/train/file.rb +0 -212
  10. data/lib/train/file/local.rb +0 -82
  11. data/lib/train/file/local/unix.rb +0 -96
  12. data/lib/train/file/local/windows.rb +0 -68
  13. data/lib/train/file/remote.rb +0 -40
  14. data/lib/train/file/remote/aix.rb +0 -29
  15. data/lib/train/file/remote/linux.rb +0 -21
  16. data/lib/train/file/remote/qnx.rb +0 -41
  17. data/lib/train/file/remote/unix.rb +0 -110
  18. data/lib/train/file/remote/windows.rb +0 -110
  19. data/lib/train/globals.rb +0 -5
  20. data/lib/train/options.rb +0 -81
  21. data/lib/train/platforms.rb +0 -102
  22. data/lib/train/platforms/common.rb +0 -34
  23. data/lib/train/platforms/detect.rb +0 -12
  24. data/lib/train/platforms/detect/helpers/os_common.rb +0 -160
  25. data/lib/train/platforms/detect/helpers/os_linux.rb +0 -80
  26. data/lib/train/platforms/detect/helpers/os_windows.rb +0 -142
  27. data/lib/train/platforms/detect/scanner.rb +0 -85
  28. data/lib/train/platforms/detect/specifications/api.rb +0 -20
  29. data/lib/train/platforms/detect/specifications/os.rb +0 -629
  30. data/lib/train/platforms/detect/uuid.rb +0 -32
  31. data/lib/train/platforms/family.rb +0 -31
  32. data/lib/train/platforms/platform.rb +0 -109
  33. data/lib/train/plugin_test_helper.rb +0 -51
  34. data/lib/train/plugins.rb +0 -40
  35. data/lib/train/plugins/base_connection.rb +0 -198
  36. data/lib/train/plugins/transport.rb +0 -49
  37. data/lib/train/transports/cisco_ios_connection.rb +0 -133
  38. data/lib/train/transports/local.rb +0 -240
  39. data/lib/train/transports/mock.rb +0 -183
  40. data/lib/train/transports/ssh.rb +0 -271
  41. data/lib/train/transports/ssh_connection.rb +0 -342
  42. data/lib/train/version.rb +0 -7
@@ -1,271 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
- # Author:: Dominik Richter (<dominik.richter@gmail.com>)
5
- # Author:: Christoph Hartmann (<chris@lollyrock.com>)
6
- #
7
- # Copyright (C) 2014, Fletcher Nichol
8
- #
9
- # Licensed under the Apache License, Version 2.0 (the "License");
10
- # you may not use this file except in compliance with the License.
11
- # You may obtain a copy of the License at
12
- #
13
- # http://www.apache.org/licenses/LICENSE-2.0
14
- #
15
- # Unless required by applicable law or agreed to in writing, software
16
- # distributed under the License is distributed on an "AS IS" BASIS,
17
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- # See the License for the specific language governing permissions and
19
- # limitations under the License.
20
-
21
- require "net/ssh"
22
- require "net/scp"
23
- require_relative "../errors"
24
-
25
- module Train::Transports
26
- # Wrapped exception for any internally raised SSH-related errors.
27
- #
28
- # @author Fletcher Nichol <fnichol@nichol.ca>
29
- class SSHFailed < Train::TransportError; end
30
- class SSHPTYFailed < Train::TransportError; end
31
-
32
- # A Transport which uses the SSH protocol to execute commands and transfer
33
- # files.
34
- #
35
- # @author Fletcher Nichol <fnichol@nichol.ca>
36
- class SSH < Train.plugin(1) # rubocop:disable Metrics/ClassLength
37
- name "ssh"
38
-
39
- require_relative "ssh_connection"
40
- require_relative "cisco_ios_connection"
41
-
42
- # add options for submodules
43
- include_options Train::Extras::CommandWrapper
44
-
45
- # common target configuration
46
- option :host, required: true
47
- option :port, default: 22, required: true
48
- option :user, default: "root", required: true
49
- option :key_files, default: nil
50
- option :password, default: nil
51
-
52
- # additional ssh options
53
- option :keepalive, default: true
54
- option :keepalive_interval, default: 60
55
- option :connection_timeout, default: 15
56
- option :connection_retries, default: 5
57
- option :connection_retry_sleep, default: 1
58
- option :max_wait_until_ready, default: 600
59
- option :compression, default: false
60
- option :pty, default: false
61
- option :proxy_command, default: nil
62
- option :bastion_host, default: nil
63
- option :bastion_user, default: "root"
64
- option :bastion_port, default: 22
65
- option :non_interactive, default: false
66
- option :verify_host_key, default: false
67
-
68
- option :compression_level do |opts|
69
- # on nil or false: set compression level to 0
70
- opts[:compression] ? 6 : 0
71
- end
72
-
73
- # (see Base#connection)
74
- def connection(state = {}, &block)
75
- opts = merge_options(options, state || {})
76
- validate_options(opts)
77
- conn_opts = connection_options(opts)
78
-
79
- if defined?(@connection) && @connection_options == conn_opts
80
- reuse_connection(&block)
81
- else
82
- create_new_connection(conn_opts, &block)
83
- end
84
- end
85
-
86
- private
87
-
88
- def validate_options(options)
89
- super(options)
90
-
91
- key_files = Array(options[:key_files])
92
- options[:auth_methods] ||= ["none"]
93
-
94
- unless key_files.empty?
95
- options[:auth_methods].push("publickey")
96
- options[:keys_only] = true if options[:password].nil?
97
- options[:key_files] = key_files
98
- end
99
-
100
- unless options[:password].nil?
101
- options[:auth_methods].push("password", "keyboard-interactive")
102
- end
103
-
104
- if options[:auth_methods] == ["none"]
105
- if ssh_known_identities.empty?
106
- raise Train::ClientError.new(
107
- "Your SSH Agent has no keys added, and you have not specified a password or a key file",
108
- :no_ssh_password_or_key_available
109
- )
110
- else
111
- logger.debug("[SSH] Using Agent keys as no password or key file have been specified")
112
- options[:auth_methods].push("publickey")
113
- end
114
- end
115
-
116
- if options[:pty]
117
- logger.warn("[SSH] PTY requested: stderr will be merged into stdout")
118
- end
119
-
120
- if [options[:proxy_command], options[:bastion_host]].all? { |type| !type.nil? }
121
- raise Train::ClientError, "Only one of proxy_command or bastion_host needs to be specified"
122
- end
123
-
124
- super
125
- self
126
- end
127
-
128
- # Creates an SSH Authentication KeyManager instance and saves it for
129
- # potential future reuse.
130
- #
131
- # @return [Hash] hash of SSH Known Identities
132
- # @api private
133
- def ssh_known_identities
134
- # Force KeyManager to load the key(s)
135
- @manager ||= Net::SSH::Authentication::KeyManager.new(nil).each_identity {}
136
- @manager.known_identities
137
- end
138
-
139
- # Builds the hash of options needed by the Connection object on
140
- # construction.
141
- #
142
- # @param opts [Hash] merged configuration and mutable state data
143
- # @return [Hash] hash of connection options
144
- # @api private
145
- def connection_options(opts)
146
- connection_options = {
147
- logger: logger,
148
- user_known_hosts_file: "/dev/null",
149
- hostname: opts[:host],
150
- port: opts[:port],
151
- username: opts[:user],
152
- compression: opts[:compression],
153
- compression_level: opts[:compression_level],
154
- keepalive: opts[:keepalive],
155
- keepalive_interval: opts[:keepalive_interval],
156
- timeout: opts[:connection_timeout],
157
- connection_retries: opts[:connection_retries],
158
- connection_retry_sleep: opts[:connection_retry_sleep],
159
- max_wait_until_ready: opts[:max_wait_until_ready],
160
- auth_methods: opts[:auth_methods],
161
- keys_only: opts[:keys_only],
162
- keys: opts[:key_files],
163
- password: opts[:password],
164
- forward_agent: opts[:forward_agent],
165
- proxy_command: opts[:proxy_command],
166
- bastion_host: opts[:bastion_host],
167
- bastion_user: opts[:bastion_user],
168
- bastion_port: opts[:bastion_port],
169
- non_interactive: opts[:non_interactive],
170
- transport_options: opts,
171
- }
172
- # disable host key verification. The hash key and value to use
173
- # depends on the version of net-ssh in use.
174
- connection_options[verify_host_key_option] = verify_host_key_value(opts[:verify_host_key])
175
-
176
- connection_options
177
- end
178
-
179
- #
180
- # Returns the correct host-key-verification option key to use depending
181
- # on what version of net-ssh is in use. In net-ssh <= 4.1, the supported
182
- # parameter is `paranoid` but in 4.2, it became `verify_host_key`
183
- #
184
- # `verify_host_key` does not work in <= 4.1, and `paranoid` throws
185
- # deprecation warnings in >= 4.2.
186
- #
187
- # While the "right thing" to do would be to pin train's dependency on
188
- # net-ssh to ~> 4.2, this will prevent InSpec from being used in
189
- # Chef v12 because of it pinning to a v3 of net-ssh.
190
- #
191
- def verify_host_key_option
192
- current_net_ssh = Net::SSH::Version::CURRENT
193
- new_option_version = Net::SSH::Version[4, 2, 0]
194
-
195
- current_net_ssh >= new_option_version ? :verify_host_key : :paranoid
196
- end
197
-
198
- # Likewise, version <5 accepted false; 5+ requires :never or will
199
- # issue a deprecation warning. This method allows a lot of common
200
- # things through.
201
- def verify_host_key_value(given)
202
- current_net_ssh = Net::SSH::Version::CURRENT
203
- new_value_version = Net::SSH::Version[5, 0, 0]
204
- if current_net_ssh >= new_value_version
205
- # 5.0+ style
206
- {
207
- # It's not a boolean anymore.
208
- "true" => :always,
209
- "false" => :never,
210
- true => :always,
211
- false => :never,
212
- # May be correct value, but strings from JSON config
213
- "always" => :always,
214
- "never" => :never,
215
- nil => :never,
216
- }.fetch(given, given)
217
- else
218
- # up to 4.2 style
219
- {
220
- "true" => true,
221
- "false" => false,
222
- nil => false,
223
- }.fetch(given, given)
224
- end
225
- end
226
-
227
- # Creates a new SSH Connection instance and save it for potential future
228
- # reuse.
229
- #
230
- # @param options [Hash] conneciton options
231
- # @return [Ssh::Connection] an SSH Connection instance
232
- # @api private
233
- def create_new_connection(options, &block)
234
- if defined?(@connection)
235
- logger.debug("[SSH] shutting previous connection #{@connection}")
236
- @connection.close
237
- end
238
-
239
- @connection_options = options
240
- conn = Connection.new(options, &block)
241
-
242
- # Cisco IOS requires a special implementation of `Net:SSH`. This uses the
243
- # SSH transport to identify the platform, but then replaces SSHConnection
244
- # with a CiscoIOSConnection in order to behave as expected for the user.
245
- if defined?(conn.platform.cisco_ios?) && conn.platform.cisco_ios?
246
- ios_options = {}
247
- ios_options[:host] = @options[:host]
248
- ios_options[:user] = @options[:user]
249
- # The enable password is used to elevate privileges on Cisco devices
250
- # We will also support the sudo password field for the same purpose
251
- # for the interim. # TODO
252
- ios_options[:enable_password] = @options[:enable_password] || @options[:sudo_password]
253
- ios_options[:logger] = @options[:logger]
254
- ios_options.merge!(@connection_options)
255
- conn = CiscoIOSConnection.new(ios_options)
256
- end
257
-
258
- @connection = conn unless conn.nil?
259
- end
260
-
261
- # Return the last saved SSH connection instance.
262
- #
263
- # @return [Ssh::Connection] an SSH Connection instance
264
- # @api private
265
- def reuse_connection
266
- logger.debug("[SSH] reusing existing connection #{@connection}")
267
- yield @connection if block_given?
268
- @connection
269
- end
270
- end
271
- end
@@ -1,342 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
- # Author:: Dominik Richter (<dominik.richter@gmail.com>)
5
- # Author:: Christoph Hartmann (<chris@lollyrock.com>)
6
- #
7
- # Copyright (C) 2014, Fletcher Nichol
8
- #
9
- # Licensed under the Apache License, Version 2.0 (the "License");
10
- # you may not use this file except in compliance with the License.
11
- # You may obtain a copy of the License at
12
- #
13
- # http://www.apache.org/licenses/LICENSE-2.0
14
- #
15
- # Unless required by applicable law or agreed to in writing, software
16
- # distributed under the License is distributed on an "AS IS" BASIS,
17
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- # See the License for the specific language governing permissions and
19
- # limitations under the License.
20
-
21
- require "net/ssh"
22
- require "net/scp"
23
- require "timeout"
24
-
25
- class Train::Transports::SSH
26
- # A Connection instance can be generated and re-generated, given new
27
- # connection details such as connection port, hostname, credentials, etc.
28
- # This object is responsible for carrying out the actions on the remote
29
- # host such as executing commands, transferring files, etc.
30
- #
31
- # @author Fletcher Nichol <fnichol@nichol.ca>
32
- class Connection < BaseConnection # rubocop:disable Metrics/ClassLength
33
- attr_reader :hostname
34
- attr_reader :transport_options
35
-
36
- def initialize(options)
37
- # Track IOS command retries to prevent infinite loop on IOError. This must
38
- # be done before `super()` because the parent runs detection commands.
39
- @ios_cmd_retries = 0
40
-
41
- super(options)
42
-
43
- @session = nil
44
- @username = @options.delete(:username)
45
- @hostname = @options.delete(:hostname)
46
- @port = @options[:port] # don't delete from options
47
- @connection_retries = @options.delete(:connection_retries)
48
- @connection_retry_sleep = @options.delete(:connection_retry_sleep)
49
- @max_wait_until_ready = @options.delete(:max_wait_until_ready)
50
- @max_ssh_sessions = @options.delete(:max_ssh_connections) { 9 }
51
- @transport_options = @options.delete(:transport_options)
52
- @proxy_command = @options.delete(:proxy_command)
53
- @bastion_host = @options.delete(:bastion_host)
54
- @bastion_user = @options.delete(:bastion_user)
55
- @bastion_port = @options.delete(:bastion_port)
56
-
57
- @cmd_wrapper = CommandWrapper.load(self, @transport_options)
58
- end
59
-
60
- # (see Base::Connection#close)
61
- def close
62
- return if @session.nil?
63
-
64
- logger.debug("[SSH] closing connection to #{self}")
65
- session.close
66
- ensure
67
- @session = nil
68
- end
69
-
70
- def ssh_opts
71
- level = logger.debug? ? "VERBOSE" : "ERROR"
72
- fwd_agent = options[:forward_agent] ? "yes" : "no"
73
-
74
- args = %w{ -o UserKnownHostsFile=/dev/null }
75
- args += %w{ -o StrictHostKeyChecking=no }
76
- args += %w{ -o IdentitiesOnly=yes } if options[:keys]
77
- args += %w{ -o BatchMode=yes } if options[:non_interactive]
78
- args += %W{ -o LogLevel=#{level} }
79
- args += %W{ -o ForwardAgent=#{fwd_agent} } if options.key?(:forward_agent)
80
- Array(options[:keys]).each do |ssh_key|
81
- args += %W{ -i #{ssh_key} }
82
- end
83
- args
84
- end
85
-
86
- def check_proxy
87
- [@proxy_command, @bastion_host].any? { |type| !type.nil? }
88
- end
89
-
90
- def generate_proxy_command
91
- return @proxy_command unless @proxy_command.nil?
92
-
93
- args = %w{ ssh }
94
- args += ssh_opts
95
- args += %W{ #{@bastion_user}@#{@bastion_host} }
96
- args += %W{ -p #{@bastion_port} }
97
- args += %w{ -W %h:%p }
98
- args.join(" ")
99
- end
100
-
101
- # (see Base::Connection#login_command)
102
- def login_command
103
- args = ssh_opts
104
- args += %W{ -o ProxyCommand='#{generate_proxy_command}' } if check_proxy
105
- args += %W{ -p #{@port} }
106
- args += %W{ #{@username}@#{@hostname} }
107
- LoginCommand.new("ssh", args)
108
- end
109
-
110
- # (see Base::Connection#upload)
111
- def upload(locals, remote)
112
- waits = []
113
- Array(locals).each do |local|
114
- opts = File.directory?(local) ? { recursive: true } : {}
115
-
116
- waits.push session.scp.upload(local, remote, opts) do |_ch, name, sent, total|
117
- logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total
118
- end
119
- waits.shift.wait while waits.length >= @max_ssh_sessions
120
- end
121
- waits.each(&:wait)
122
- rescue Net::SSH::Exception => ex
123
- raise Train::Transports::SSHFailed, "SCP upload failed (#{ex.message})"
124
- end
125
-
126
- def download(remotes, local)
127
- waits = []
128
- Array(remotes).map do |remote|
129
- opts = file(remote).directory? ? { recursive: true } : {}
130
- waits.push session.scp.download(remote, local, opts) do |_ch, name, recv, total|
131
- logger.debug("Downloaded #{name} (#{total} bytes)") if recv == total
132
- end
133
- waits.shift.wait while waits.length >= @max_ssh_sessions
134
- end
135
- waits.each(&:wait)
136
- rescue Net::SSH::Exception => ex
137
- raise Train::Transports::SSHFailed, "SCP download failed (#{ex.message})"
138
- end
139
-
140
- # (see Base::Connection#wait_until_ready)
141
- def wait_until_ready
142
- delay = 3
143
- session(
144
- retries: @max_wait_until_ready / delay,
145
- delay: delay,
146
- message: "Waiting for SSH service on #{@hostname}:#{@port}, " \
147
- "retrying in #{delay} seconds"
148
- )
149
- run_command(PING_COMMAND.dup)
150
- end
151
-
152
- def uri
153
- "ssh://#{@username}@#{@hostname}:#{@port}"
154
- end
155
-
156
- # remote_port_forwarding
157
- def forward_remote(port, host, remote_port, remote_host = "127.0.0.1")
158
- @session.forward.remote(port, host, remote_port, remote_host)
159
- end
160
-
161
- def obscured_options
162
- options_to_print = @options.clone
163
- options_to_print[:password] = "<hidden>" if options_to_print.key?(:password)
164
- options_to_print
165
- end
166
-
167
- def with_sudo_pty
168
- old_pty = transport_options[:pty]
169
- transport_options[:pty] = true if @sudo
170
-
171
- yield
172
- ensure
173
- transport_options[:pty] = old_pty
174
- end
175
-
176
- private
177
-
178
- PING_COMMAND = "echo '[SSH] Established'".freeze
179
-
180
- RESCUE_EXCEPTIONS_ON_ESTABLISH = [
181
- Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
182
- Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE,
183
- Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
184
- Timeout::Error
185
- ].freeze
186
-
187
- # Establish an SSH session on the remote host.
188
- #
189
- # @param opts [Hash] retry options
190
- # @option opts [Integer] :retries the number of times to retry before
191
- # failing
192
- # @option opts [Float] :delay the number of seconds to wait until
193
- # attempting a retry
194
- # @option opts [String] :message an optional message to be logged on
195
- # debug (overriding the default) when a rescuable exception is raised
196
- # @return [Net::SSH::Connection::Session] the SSH connection session
197
- # @api private
198
- def establish_connection(opts)
199
- logger.debug("[SSH] opening connection to #{self}")
200
- logger.debug("[SSH] using options %p" % [obscured_options])
201
- if check_proxy
202
- require "net/ssh/proxy/command"
203
- @options[:proxy] = Net::SSH::Proxy::Command.new(generate_proxy_command)
204
- end
205
- Net::SSH.start(@hostname, @username, @options.clone.delete_if { |_key, value| value.nil? })
206
- rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
207
- if (opts[:retries] -= 1) <= 0
208
- logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
209
- raise Train::Transports::SSHFailed, "SSH session could not be established"
210
- end
211
-
212
- if opts[:message]
213
- logger.debug("[SSH] connection failed (#{e.inspect})")
214
- message = opts[:message]
215
- else
216
- message = "[SSH] connection failed, retrying in #{opts[:delay]}"\
217
- " seconds (#{e.inspect})"
218
- end
219
- logger.info(message)
220
-
221
- sleep(opts[:delay])
222
- retry
223
- end
224
-
225
- def file_via_connection(path, *args)
226
- if os.aix?
227
- Train::File::Remote::Aix.new(self, path, *args)
228
- elsif os.solaris?
229
- Train::File::Remote::Unix.new(self, path, *args)
230
- elsif os[:name] == "qnx"
231
- Train::File::Remote::Qnx.new(self, path, *args)
232
- elsif os.windows?
233
- Train::File::Remote::Windows.new(self, path, *args)
234
- else
235
- Train::File::Remote::Linux.new(self, path, *args)
236
- end
237
- end
238
-
239
- def run_command_via_connection(cmd, &data_handler)
240
- cmd.dup.force_encoding("binary") if cmd.respond_to?(:force_encoding)
241
-
242
- reset_session if session.closed?
243
-
244
- exit_status, stdout, stderr = execute_on_channel(cmd, &data_handler)
245
-
246
- # Since `@session.loop` succeeded, reset the IOS command retry counter
247
- @ios_cmd_retries = 0
248
-
249
- CommandResult.new(stdout, stderr, exit_status)
250
- rescue Net::SSH::Exception => ex
251
- raise Train::Transports::SSHFailed, "SSH command failed (#{ex.message})"
252
- rescue IOError
253
- # Cisco IOS occasionally closes the stream prematurely while we are
254
- # running commands to detect if we need to switch to the Cisco IOS
255
- # transport. This retries the command if this is the case.
256
- # See:
257
- # https://github.com/inspec/train/pull/271
258
- logger.debug("[SSH] Possible Cisco IOS race condition, retrying command")
259
-
260
- # Only attempt retry up to 5 times to avoid infinite loop
261
- @ios_cmd_retries += 1
262
- raise if @ios_cmd_retries >= 5
263
-
264
- retry
265
- end
266
-
267
- # Returns a connection session, or establishes one when invoked the
268
- # first time.
269
- #
270
- # @param retry_options [Hash] retry options for the initial connection
271
- # @return [Net::SSH::Connection::Session] the SSH connection session
272
- # @api private
273
- def session(retry_options = {})
274
- @session ||= establish_connection({
275
- retries: @connection_retries.to_i,
276
- delay: @connection_retry_sleep.to_i,
277
- }.merge(retry_options))
278
- end
279
-
280
- def reset_session
281
- @session = nil
282
- end
283
-
284
- # String representation of object, reporting its connection details and
285
- # configuration.
286
- #
287
- # @api private
288
- def to_s
289
- "#{@username}@#{@hostname}"
290
- end
291
-
292
- # Given a channel and a command string, it will execute the command on the channel
293
- # and accumulate results in @stdout/@stderr.
294
- #
295
- # @param channel [Net::SSH::Connection::Channel] an open ssh channel
296
- # @param cmd [String] the command to execute
297
- # @return [Integer] exit status or nil if exit-status/exit-signal requests
298
- # not received.
299
- #
300
- # @api private
301
- def execute_on_channel(cmd)
302
- stdout = ""
303
- stderr = ""
304
- exit_status = nil
305
- session.open_channel do |channel|
306
- # wrap commands if that is configured
307
- cmd = @cmd_wrapper.run(cmd) if @cmd_wrapper
308
-
309
- logger.debug("[SSH] #{self} cmd = #{cmd}")
310
-
311
- if @transport_options[:pty]
312
- channel.request_pty do |_ch, success|
313
- raise Train::Transports::SSHPTYFailed, "Requesting PTY failed" unless success
314
- end
315
- end
316
-
317
- channel.exec(cmd) do |_, success|
318
- abort "Couldn't execute command on SSH." unless success
319
- channel.on_data do |_, data|
320
- yield(data) if block_given?
321
- stdout += data
322
- end
323
-
324
- channel.on_extended_data do |_, _type, data|
325
- yield(data) if block_given?
326
- stderr += data
327
- end
328
-
329
- channel.on_request("exit-status") do |_, data|
330
- exit_status = data.read_long
331
- end
332
-
333
- channel.on_request("exit-signal") do |_, data|
334
- exit_status = data.read_long
335
- end
336
- end
337
- end
338
- session.loop
339
- [exit_status, stdout, stderr]
340
- end
341
- end
342
- end