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