lib/ztk/ssh.rb in ztk-1.1.2 vs lib/ztk/ssh.rb in ztk-1.2.0
- old
+ new
@@ -82,11 +82,10 @@
# config.host_key_verify = true
# end
#
# @author Zachary Patten <zachary@jovelabs.net>
class SSH < ZTK::Base
-
# Exit Signal Mappings
EXIT_SIGNALS = {
1 => "SIGHUP",
2 => "SIGINT",
3 => "SIGQUIT",
@@ -114,10 +113,22 @@
25 => "SIGXFSZ",
26 => "SIGVTALRM",
27 => "SIGPROF"
}
+ autoload :Bootstrap, 'ztk/ssh/bootstrap'
+ autoload :Command, 'ztk/ssh/command'
+ autoload :Download, 'ztk/ssh/download'
+ autoload :Exec, 'ztk/ssh/exec'
+ autoload :Upload, 'ztk/ssh/upload'
+
+ include ZTK::SSH::Bootstrap
+ include ZTK::SSH::Command
+ include ZTK::SSH::Download
+ include ZTK::SSH::Exec
+ include ZTK::SSH::Upload
+
# @param [Hash] configuration Configuration options hash.
# @option config [String] :host_name Server hostname to connect to.
# @option config [String] :user Username to use for authentication.
# @option config [String, Array<String>] :keys A single or series of
# identity files to use for authentication.
@@ -183,256 +194,11 @@
config.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
Kernel.exec(console_command)
end
- # Executes a command on the remote host.
- #
- # @param [String] command The command to execute.
- # @param [Hash] options The options hash for executing the command.
- # @option options [Boolean] :silence Squelch output to STDOUT and STDERR.
- # If the log level is :debug, STDOUT and STDERR will go to the log file
- # regardless of this setting. STDOUT and STDERR are always returned in
- # the output return value regardless of this setting.
- #
- # @return [OpenStruct#output] The output of the command, both STDOUT and
- # STDERR.
- # @return [OpenStruct#exit] The exit status (i.e. $?).
- #
- # @example Execute a command:
- #
- # ssh = ZTK::SSH.new
- # ssh.config do |config|
- # config.user = ENV["USER"]
- # config.host_name = "127.0.0.1"
- # end
- # puts ssh.exec("hostname").inspect
- def exec(command, options={})
- options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(config.send(:table)).merge(options))
- options.ui.logger.debug { "config=#{options.send(:table).inspect}" }
- options.ui.logger.debug { "options=#{options.send(:table).inspect}" }
- options.ui.logger.info { "exec(#{command.inspect})" }
-
- output = ""
- exit_code = -1
- exit_signal = nil
- stdout_header = false
- stderr_header = false
-
- begin
- Timeout.timeout(options.timeout) do
- ZTK::RescueRetry.try(:tries => 3, :on => EOFError) do
- @ssh = Net::SSH.start(options.host_name, options.user, ssh_options)
-
- channel = ssh.open_channel do |chan|
- options.ui.logger.debug { "Channel opened." }
-
- (options.request_pty == true) and chan.request_pty do |ch, success|
- if success
- options.ui.logger.debug { "PTY obtained." }
- else
- options.ui.logger.warn { "Could not obtain PTY." }
- end
- end
-
- direct_log(:info) { log_header("COMMAND") }
- direct_log(:info) { "#{command}\n" }
- direct_log(:info) { log_header("OPENED") }
-
- chan.exec(command) do |ch, success|
- success or log_and_raise(SSHError, "Could not execute '#{command}'.")
-
- ch.on_data do |c, data|
- if !stdout_header
- direct_log(:info) { log_header("STDOUT") }
- stdout_header = true
- stderr_header = false
- end
- direct_log(:info) { data }
-
- options.ui.stdout.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 }
-
- options.ui.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|
- options.ui.logger.fatal { "Open failed! (#{code.inspect} - #{desc.inspect})" }
- end
-
- end
- end
- channel.wait
-
- direct_log(:info) { log_header("CLOSED") }
- options.ui.logger.debug { "Channel closed." }
- end
- end
-
- rescue Timeout::Error => e
- direct_log(:fatal) { log_header("TIMEOUT") }
- log_and_raise(SSHError, "Session timed out after #{options.timeout} seconds!")
- end
-
- message = [
- "exit_code=#{exit_code}",
- (exit_signal.nil? ? nil : "exit_signal=#{exit_signal} (#{EXIT_SIGNALS[exit_signal]})")
- ].compact.join(", ")
-
- options.ui.logger.debug { message }
-
- if !options.ignore_exit_status && (exit_code != options.exit_code)
- log_and_raise(SSHError, message)
- end
- OpenStruct.new(:command => command, :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.
- # @param [String] remote The remote file/path you with to upload to.
- #
- # @example Upload a file:
- # $logger = ZTK::Logger.new(STDOUT)
- # ssh = ZTK::SSH.new
- # ssh.config do |config|
- # config.user = ENV["USER"]
- # config.host_name = "127.0.0.1"
- # 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)
- config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
- config.ui.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.upload!(local.to_s, remote.to_s) do |event, uploader, *args|
- case event
- when :open
- config.ui.logger.debug { "upload(#{args[0].local} -> #{args[0].remote})" }
- when :close
- config.ui.logger.debug { "close(#{args[0].remote})" }
- when :mkdir
- config.ui.logger.debug { "mkdir(#{args[0]})" }
- when :put
- config.ui.logger.debug { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
- when :finish
- config.ui.logger.debug { "finish" }
- end
- end
- end
-
- true
- end
-
- # Downloads a remote file to the local host.
- #
- # @param [String] remote The remote file/path you with to download from.
- # @param [String] local The local file/path you wish to download to.
- #
- # @example Download a file:
- # $logger = ZTK::Logger.new(STDOUT)
- # ssh = ZTK::SSH.new
- # ssh.config do |config|
- # config.user = ENV["USER"]
- # config.host_name = "127.0.0.1"
- # 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)
- config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
- config.ui.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.download!(remote.to_s, local.to_s) do |event, downloader, *args|
- case event
- when :open
- config.ui.logger.debug { "download(#{args[0].remote} -> #{args[0].local})" }
- when :close
- config.ui.logger.debug { "close(#{args[0].local})" }
- when :mkdir
- config.ui.logger.debug { "mkdir(#{args[0]})" }
- when :get
- config.ui.logger.debug { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
- when :finish
- config.ui.logger.debug { "finish" }
- end
- end
- end
-
- true
- end
-
-
private
-
- # Builds our SSH console command.
- def console_command
- command = [ "/usr/bin/env ssh" ]
- command << [ "-q" ]
- command << [ "-4" ]
- command << [ "-x" ]
- command << [ "-a" ]
- command << [ "-o", "UserKnownHostsFile=/dev/null" ]
- command << [ "-o", "StrictHostKeyChecking=no" ]
- command << [ "-o", "KeepAlive=yes" ]
- command << [ "-o", "ServerAliveInterval=60" ]
- command << [ "-o", %(ProxyCommand="#{proxy_command}") ] if config.proxy_host_name
- command << [ "-i", config.keys ] if config.keys
- command << [ "-p", config.port ] if config.port
- # command << [ "-vv" ]
- command << "#{config.user}@#{config.host_name}"
- command = command.flatten.compact.join(' ')
- config.ui.logger.debug { "console_command(#{command.inspect})" }
- command
- end
-
- # Builds our SSH proxy command.
- def proxy_command
- !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.")
-
- command = ["/usr/bin/env ssh"]
- command << [ "-q" ]
- command << [ "-4" ]
- command << [ "-x" ]
- 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 << [ "-vv" ]
- command << "#{config.proxy_user}@#{config.proxy_host_name}"
- command << "'/usr/bin/env nc %h %p'"
- command = command.flatten.compact.join(' ')
- config.ui.logger.debug { "proxy_command(#{command.inspect})" }
- command
- end
# Builds our SSH options hash.
def ssh_options
options = {}