class Train::Transports::SSH::Connection
A Connection
instance can be generated and re-generated, given new connection details such as connection port, hostname, credentials, etc. This object is responsible for carrying out the actions on the remote host such as executing commands, transferring files, etc.
@author Fletcher Nichol <fnichol@nichol.ca>
Constants
- PING_COMMAND
- RESCUE_EXCEPTIONS_ON_ESTABLISH
Public Class Methods
# File lib/train/transports/ssh_connection.rb, line 33 def initialize(options) super(options) @username = @options.delete(:username) @hostname = @options.delete(:hostname) @port = @options[:port] # don't delete from options @connection_retries = @options.delete(:connection_retries) @connection_retry_sleep = @options.delete(:connection_retry_sleep) @max_wait_until_ready = @options.delete(:max_wait_until_ready) @files = {} @session = nil @transport_options = @options.delete(:transport_options) @cmd_wrapper = nil @cmd_wrapper = CommandWrapper.load(self, @transport_options) end
Public Instance Methods
(see Base::Connection#close)
# File lib/train/transports/ssh_connection.rb, line 49 def close return if @session.nil? logger.debug("[SSH] closing connection to #{self}") session.close ensure @session = nil end
# File lib/train/transports/ssh_connection.rb, line 61 def file(path) @files[path] ||= \ if os.aix? AixFile.new(self, path) elsif os.solaris? UnixFile.new(self, path) else LinuxFile.new(self, path) end end
(see Base::Connection#login_command)
# File lib/train/transports/ssh_connection.rb, line 111 def login_command level = logger.debug? ? 'VERBOSE' : 'ERROR' fwd_agent = options[:forward_agent] ? 'yes' : 'no' args = %w{ -o UserKnownHostsFile=/dev/null } args += %w{ -o StrictHostKeyChecking=no } args += %w{ -o IdentitiesOnly=yes } if options[:keys] args += %W( -o LogLevel=#{level} ) args += %W( -o ForwardAgent=#{fwd_agent} ) if options.key?(:forward_agent) Array(options[:keys]).each do |ssh_key| args += %W( -i #{ssh_key} ) end args += %W( -p #{@port} ) args += %W( #{@username}@#{@hostname} ) LoginCommand.new('ssh', args) end
# File lib/train/transports/ssh_connection.rb, line 57 def os @os ||= OS.new(self) end
(see Base::Connection#run_command)
# File lib/train/transports/ssh_connection.rb, line 73 def run_command(cmd) stdout = stderr = '' exit_status = nil cmd.force_encoding('binary') if cmd.respond_to?(:force_encoding) logger.debug("[SSH] #{self} (#{cmd})") session.open_channel do |channel| # wrap commands if that is configured cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil? channel.exec(cmd) do |_, success| abort 'Couldn\'t execute command on SSH.' unless success channel.on_data do |_, data| stdout += data end channel.on_extended_data do |_, _type, data| stderr += data end channel.on_request('exit-status') do |_, data| exit_status = data.read_long end channel.on_request('exit-signal') do |_, data| exit_status = data.read_long end end end @session.loop CommandResult.new(stdout, stderr, exit_status) rescue Net::SSH::Exception => ex raise Train::Transports::SSHFailed, "SSH command failed (#{ex.message})" end
(see Base::Connection#upload)
# File lib/train/transports/ssh_connection.rb, line 130 def upload(locals, remote) Array(locals).each do |local| opts = File.directory?(local) ? { recursive: true } : {} session.scp.upload!(local, remote, opts) do |_ch, name, sent, total| logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total end end rescue Net::SSH::Exception => ex raise Train::Transports::SSHFailed, "SCP upload failed (#{ex.message})" end
(see Base::Connection#wait_until_ready)
# File lib/train/transports/ssh_connection.rb, line 143 def wait_until_ready delay = 3 session( retries: @max_wait_until_ready / delay, delay: delay, message: "Waiting for SSH service on #{@hostname}:#{@port}, " \ "retrying in #{delay} seconds", ) execute(PING_COMMAND.dup) end
Private Instance Methods
Establish an SSH
session on the remote host.
@param opts [Hash] retry options @option opts [Integer] :retries the number of times to retry before
failing
@option opts [Float] :delay the number of seconds to wait until
attempting a retry
@option opts [String] :message an optional message to be logged on
debug (overriding the default) when a rescuable exception is raised
@return [Net::SSH::Connection::Session] the SSH
connection session @api private
# File lib/train/transports/ssh_connection.rb, line 176 def establish_connection(opts) logger.debug("[SSH] opening connection to #{self}") Net::SSH.start(@hostname, @username, @options) rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e if (opts[:retries] -= 1) <= 0 logger.warn("[SSH] connection failed, terminating (#{e.inspect})") raise Train::Transports::SSHFailed, 'SSH session could not be established' end if opts[:message] logger.debug("[SSH] connection failed (#{e.inspect})") message = opts[:message] else message = "[SSH] connection failed, retrying in #{opts[:delay]}"\ " seconds (#{e.inspect})" end logger.info(message) sleep(opts[:delay]) retry end
Returns a connection session, or establishes one when invoked the first time.
@param retry_options [Hash] retry options for the initial connection @return [Net::SSH::Connection::Session] the SSH
connection session @api private
# File lib/train/transports/ssh_connection.rb, line 204 def session(retry_options = {}) @session ||= establish_connection({ retries: @connection_retries.to_i, delay: @connection_retry_sleep.to_i, }.merge(retry_options)) end
String representation of object, reporting its connection details and configuration.
@api private
# File lib/train/transports/ssh_connection.rb, line 215 def to_s "#{@username}@#{@hostname}<#{@options.inspect}>" end