lib/ztk/ssh.rb in ztk-0.2.4 vs lib/ztk/ssh.rb in ztk-0.2.5
- old
+ new
@@ -99,41 +99,54 @@
# @option config [String] :proxy_host_name Server hostname to proxy through.
# @option config [String] :proxy_user Username to use for proxy
# authentication.
# @option config [String, Array<String>] :proxy_keys A single or series of
# identity files to use for authentication with the proxy.
- def initialize(config={})
+ def initialize(configuration={})
super({
:forward_agent => true,
:compression => false,
- :user_known_hosts_file => '/dev/null'
- }.merge(config))
+ :user_known_hosts_file => '/dev/null',
+ :timeout => 60
+ }.merge(configuration))
+ config.logger.debug { "config(#{config.inspect})" }
end
def inspect
- user_host = "#{@config.user}@#{@config.host_name}"
- port = (@config.port ? ":#{@config.port}" : nil)
- [user_host, port].compact.join
+ tags = Array.new
+
+ if config.proxy_host_name
+ proxy_user_host = "#{config.proxy_user}@#{config.proxy_host_name}"
+ proxy_port = (config.proxy_port ? ":#{config.proxy_port}" : nil)
+ tags << [proxy_user_host, proxy_port].compact.join
+ tags << " >>> "
+ end
+
+ user_host = "#{config.user}@#{config.host_name}"
+ port = (config.port ? ":#{config.port}" : nil)
+ tags << [user_host, port].compact.join
+
+ tags.join.strip
end
# Starts an SSH session. Can also be used to get the Net::SSH object.
#
# Primarily used internally.
def ssh
- @ssh ||= Net::SSH.start(@config.host_name, @config.user, ssh_options)
+ @ssh ||= Net::SSH.start(config.host_name, config.user, ssh_options)
end
# Starts an SFTP session. Can also be used to get the Net::SSH object.
#
# Primarily used internally.
def sftp
- @sftp ||= Net::SFTP.start(@config.host_name, @config.user, ssh_options)
+ @sftp ||= Net::SFTP.start(config.host_name, config.user, ssh_options)
end
# Close our session gracefully.
def close
- log(:debug) { "close" }
+ config.logger.debug { "close" }
ssh and !ssh.closed? and ssh.close
end
# Launches an SSH console, replacing the current process with the console
# process.
@@ -145,12 +158,12 @@
# config.user = ENV["USER"]
# config.host_name = "127.0.0.1"
# end
# ssh.console
def console
- log(:info) { "console(#{console_command.inspect})" }
- log(:debug) { "config(#{@config.inspect})" }
+ config.logger.debug { "config(#{config.inspect})" }
+ config.logger.info { "console(#{console_command.inspect})" }
Kernel.exec(console_command)
end
# Executes a command on the remote host.
@@ -181,67 +194,93 @@
sep = ("=" * count)
header = [sep, "[ #{tag} ]", sep, "[ #{self.inspect} ]", sep, "[ #{tag} ]", sep].join
"#{header}\n"
end
- log(:debug) { "config(#{@config.inspect})" }
- log(:info) { "exec(#{command.inspect}, #{options.inspect})" }
+ options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(options))
- options = OpenStruct.new({ :silence => false }.merge(options))
- log(:debug) { "options(#{options.inspect})" }
+ config.logger.debug { "config(#{config.inspect})" }
+ config.logger.debug { "options(#{options.inspect})" }
+ config.logger.info { "exec(#{command.inspect}, #{options.inspect})" }
output = ""
+ exit_code = nil
+ exit_signal = nil
stdout_header = false
stderr_header = false
- ZTK::RescueRetry.try(:tries => 3, :on => EOFError) do
- @ssh = Net::SSH.start(@config.host_name, @config.user, ssh_options)
+ begin
+ Timeout.timeout(config.timeout) do
+ ZTK::RescueRetry.try(:tries => 3, :on => EOFError) do
+ @ssh = Net::SSH.start(config.host_name, config.user, ssh_options)
- channel = ssh.open_channel do |chan|
- log(:debug) { "Channel opened." }
- direct_log(:debug) { log_header("OPENED") }
+ channel = ssh.open_channel do |chan|
+ config.logger.debug { "Channel opened." }
- chan.exec(command) do |ch, success|
- raise SSHError, "Could not execute '#{command}'." unless success
+ direct_log(:debug) { log_header("COMMAND") }
+ direct_log(:debug) { "#{command}\n" }
+ direct_log(:debug) { log_header("OPENED") }
- ch.on_data do |c, data|
- if !stdout_header
- direct_log(:debug) { log_header("STDOUT") }
- stdout_header = true
- stderr_header = false
- end
- direct_log(:debug) { data }
+ chan.exec(command) do |ch, success|
+ success or log_and_raise(SSHError, "Could not execute '#{command}'.")
- @config.stdout.print(data) unless options.silence
- output += data
- end
+ ch.on_data do |c, data|
+ if !stdout_header
+ direct_log(:debug) { log_header("STDOUT") }
+ stdout_header = true
+ stderr_header = false
+ end
+ direct_log(:debug) { data }
- ch.on_extended_data do |c, type, data|
- if !stderr_header
- direct_log(:warn) { log_header("STDERR") }
- stderr_header = true
- stdout_header = false
- end
- direct_log(:warn) { data }
+ config.stdout.print(data) unless options.silence
+ output += data
+ end
- @config.stderr.print(data) unless options.silence
- output += data
- end
+ ch.on_extended_data do |c, type, data|
+ if !stderr_header
+ direct_log(:warn) { log_header("STDERR") }
+ stderr_header = true
+ stdout_header = false
+ end
+ direct_log(:warn) { data }
- ch.on_open_failed do |c, code, desc|
- log(:fatal) { "Open failed! (#{code.inspect} - #{desc.inspect})" }
+ config.stderr.print(data) unless options.silence
+ output += data
+ end
+
+ ch.on_request("exit-status") do |ch, data|
+ exit_code = data.read_long
+ end
+
+ ch.on_request("exit-signal") do |ch, data|
+ exit_signal = data.read_long
+ end
+
+ ch.on_open_failed do |c, code, desc|
+ config.logger.fatal { "Open failed! (#{code.inspect} - #{desc.inspect})" }
+ end
+
+ end
end
+ channel.wait
+ direct_log(:debug) { log_header("CLOSED") }
+ config.logger.debug { "Channel closed." }
end
end
- channel.wait
- direct_log(:debug) { log_header("CLOSED") }
- log(:debug) { "Channel closed." }
+ rescue Timeout::Error => e
+ direct_log(:debug) { log_header("TIMEOUT") }
+ log_and_raise(SSHError, "Session timed out after #{config.timeout} seconds!")
end
- OpenStruct.new(:output => output, :exit => $?)
+ config.logger.debug { "exit_code(#{exit_code}), exit_signal(#{exit_signal})" }
+
+ if (exit_code != options.exit_code)
+ log_and_raise(SSHError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}, #{exit_signal}]")
+ end
+ OpenStruct.new(:output => output, :exit_code => exit_code, :exit_signal => exit_signal)
end
# Uploads a local file to a remote host.
#
# @param [String] local The local file/path you wish to upload from.
@@ -256,27 +295,27 @@
# end
# local = File.expand_path(File.join(ENV["HOME"], ".ssh", "id_rsa.pub"))
# remote = File.expand_path(File.join("/tmp", "id_rsa.pub"))
# ssh.upload(local, remote)
def upload(local, remote)
- log(:debug) { "config(#{@config.inspect})" }
- log(:info) { "upload(#{local.inspect}, #{remote.inspect})" }
+ config.logger.debug { "config(#{config.inspect})" }
+ config.logger.info { "upload(#{local.inspect}, #{remote.inspect})" }
ZTK::RescueRetry.try(:tries => 3, :on => EOFError) do
- @sftp = Net::SFTP.start(@config.host_name, @config.user, ssh_options)
+ @sftp = Net::SFTP.start(config.host_name, config.user, ssh_options)
sftp.upload!(local.to_s, remote.to_s) do |event, uploader, *args|
case event
when :open
- log(:debug) { "upload(#{args[0].local} -> #{args[0].remote})" }
+ config.logger.debug { "upload(#{args[0].local} -> #{args[0].remote})" }
when :close
- log(:debug) { "close(#{args[0].remote})" }
+ config.logger.debug { "close(#{args[0].remote})" }
when :mkdir
- log(:debug) { "mkdir(#{args[0]})" }
+ config.logger.debug { "mkdir(#{args[0]})" }
when :put
- log(:debug) { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
+ config.logger.debug { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
when :finish
- log(:debug) { "finish" }
+ config.logger.debug { "finish" }
end
end
end
true
@@ -296,27 +335,27 @@
# end
# local = File.expand_path(File.join("/tmp", "id_rsa.pub"))
# remote = File.expand_path(File.join(ENV["HOME"], ".ssh", "id_rsa.pub"))
# ssh.download(remote, local)
def download(remote, local)
- log(:debug) { "config(#{@config.inspect})" }
- log(:info) { "download(#{remote.inspect}, #{local.inspect})" }
+ config.logger.debug { "config(#{config.inspect})" }
+ config.logger.info { "download(#{remote.inspect}, #{local.inspect})" }
ZTK::RescueRetry.try(:tries => 3, :on => EOFError) do
- @sftp = Net::SFTP.start(@config.host_name, @config.user, ssh_options)
+ @sftp = Net::SFTP.start(config.host_name, config.user, ssh_options)
sftp.download!(remote.to_s, local.to_s) do |event, downloader, *args|
case event
when :open
- log(:debug) { "download(#{args[0].remote} -> #{args[0].local})" }
+ config.logger.debug { "download(#{args[0].remote} -> #{args[0].local})" }
when :close
- log(:debug) { "close(#{args[0].local})" }
+ config.logger.debug { "close(#{args[0].local})" }
when :mkdir
- log(:debug) { "mkdir(#{args[0]})" }
+ config.logger.debug { "mkdir(#{args[0]})" }
when :get
- log(:debug) { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
+ config.logger.debug { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
when :finish
- log(:debug) { "finish" }
+ config.logger.debug { "finish" }
end
end
end
true
@@ -332,76 +371,67 @@
command << [ "-A" ]
command << [ "-o", "UserKnownHostsFile=/dev/null" ]
command << [ "-o", "StrictHostKeyChecking=no" ]
command << [ "-o", "KeepAlive=yes" ]
command << [ "-o", "ServerAliveInterval=60" ]
- command << [ "-i", @config.keys ] if @config.keys
- command << [ "-p", @config.port ] if @config.port
- command << [ "-o", "ProxyCommand=\"#{proxy_command}\"" ] if @config.proxy_host_name
- command << "#{@config.user}@#{@config.host_name}"
+ command << [ "-i", config.keys ] if config.keys
+ command << [ "-p", config.port ] if config.port
+ command << [ "-o", "ProxyCommand=\"#{proxy_command}\"" ] if config.proxy_host_name
+ command << "#{config.user}@#{config.host_name}"
command = command.flatten.compact.join(" ")
- log(:debug) { "console_command(#{command.inspect})" }
+ config.logger.debug { "console_command(#{command.inspect})" }
command
end
# Builds our SSH proxy command.
def proxy_command
- if !@config.proxy_user
- message = "You must specify an proxy user in order to SSH proxy."
- log(:fatal) { message }
- raise SSHError, message
- end
+ !config.proxy_user and log_and_raise(SSHError, "You must specify an proxy user in order to SSH proxy.")
+ !config.proxy_host_name and log_and_raise(SSHError, "You must specify an proxy host_name in order to SSH proxy.")
- if !@config.proxy_host_name
- message = "You must specify an proxy host_name in order to SSH proxy."
- log(:fatal) { message }
- raise SSHError, message
- end
-
command = ["ssh"]
command << [ "-q" ]
command << [ "-A" ]
command << [ "-o", "UserKnownHostsFile=/dev/null" ]
command << [ "-o", "StrictHostKeyChecking=no" ]
command << [ "-o", "KeepAlive=yes" ]
command << [ "-o", "ServerAliveInterval=60" ]
- command << [ "-i", @config.proxy_keys ] if @config.proxy_keys
- command << [ "-p", @config.proxy_port ] if @config.proxy_port
- command << "#{@config.proxy_user}@#{@config.proxy_host_name}"
+ command << [ "-i", config.proxy_keys ] if config.proxy_keys
+ command << [ "-p", config.proxy_port ] if config.proxy_port
+ command << "#{config.proxy_user}@#{config.proxy_host_name}"
command << "nc %h %p"
command = command.flatten.compact.join(" ")
- log(:debug) { "proxy_command(#{command.inspect})" }
+ config.logger.debug { "proxy_command(#{command.inspect})" }
command
end
# Builds our SSH options hash.
def ssh_options
options = {}
# These are plainly documented on the Net::SSH config class.
- options.merge!(:encryption => @config.encryption) if @config.encryption
- options.merge!(:compression => @config.compression) if @config.compression
- options.merge!(:compression_level => @config.compression_level) if @config.compression_level
- options.merge!(:timeout => @config.timeout) if @config.timeout
- options.merge!(:forward_agent => @config.forward_agent) if @config.forward_agent
- options.merge!(:global_known_hosts_file => @config.global_known_hosts_file) if @config.global_known_hosts_file
- options.merge!(:auth_methods => @config.auth_methods) if @config.auth_methods
- options.merge!(:host_key => @config.host_key) if @config.host_key
- options.merge!(:host_key_alias => @config.host_key_alias) if @config.host_key_alias
- options.merge!(:host_name => @config.host_name) if @config.host_name
- options.merge!(:keys => @config.keys) if @config.keys
- options.merge!(:keys_only => @config.keys_only) if @config.keys_only
- options.merge!(:hmac => @config.hmac) if @config.hmac
- options.merge!(:port => @config.port) if @config.port
- options.merge!(:proxy => Net::SSH::Proxy::Command.new(proxy_command)) if @config.proxy_host_name
- options.merge!(:rekey_limit => @config.rekey_limit) if @config.rekey_limit
- options.merge!(:user => @config.user) if @config.user
- options.merge!(:user_known_hosts_file => @config.user_known_hosts_file) if @config.user_known_hosts_file
+ options.merge!(:encryption => config.encryption) if config.encryption
+ options.merge!(:compression => config.compression) if config.compression
+ options.merge!(:compression_level => config.compression_level) if config.compression_level
+ options.merge!(:timeout => config.timeout) if config.timeout
+ options.merge!(:forward_agent => config.forward_agent) if config.forward_agent
+ options.merge!(:global_known_hosts_file => config.global_known_hosts_file) if config.global_known_hosts_file
+ options.merge!(:auth_methods => config.auth_methods) if config.auth_methods
+ options.merge!(:host_key => config.host_key) if config.host_key
+ options.merge!(:host_key_alias => config.host_key_alias) if config.host_key_alias
+ options.merge!(:host_name => config.host_name) if config.host_name
+ options.merge!(:keys => config.keys) if config.keys
+ options.merge!(:keys_only => config.keys_only) if config.keys_only
+ options.merge!(:hmac => config.hmac) if config.hmac
+ options.merge!(:port => config.port) if config.port
+ options.merge!(:proxy => Net::SSH::Proxy::Command.new(proxy_command)) if config.proxy_host_name
+ options.merge!(:rekey_limit => config.rekey_limit) if config.rekey_limit
+ options.merge!(:user => config.user) if config.user
+ options.merge!(:user_known_hosts_file => config.user_known_hosts_file) if config.user_known_hosts_file
# This is not plainly documented on the Net::SSH config class.
- options.merge!(:password => @config.password) if @config.password
+ options.merge!(:password => config.password) if config.password
- log(:debug) { "ssh_options(#{options.inspect})" }
+ config.logger.debug { "ssh_options(#{options.inspect})" }
options
end
end