lib/rye/box.rb in rye-0.8.19 vs lib/rye/box.rb in rye-0.9.0
- old
+ new
@@ -1,8 +1,10 @@
+require 'annoy'
+require 'readline'
-
module Rye
+ DEBUG = false unless defined?(Rye::DEBUG)
# = Rye::Box
#
# The Rye::Box class represents a machine. All system
# commands are made through this class.
@@ -27,16 +29,22 @@
# Net::SSH channel is waiting on_extended_data (a prompt).
#++
class Box
include Rye::Cmd
+ attr_accessor :rye_shell
+ attr_accessor :rye_pty
+
def host; @rye_host; end
def opts; @rye_opts; end
def safe; @rye_safe; end
def user; @rye_user; end
def root?; user.to_s == "root" end
+ def templates; @rye_templates; end
+ def templates?; !@rye_templates.nil?; end
+
def enable_sudo; @rye_sudo = true; end
def disable_sudo; @rye_sudo = false; end
def sudo?; @rye_sudo == true end
# Returns the current value of the stash +@rye_stash+
@@ -62,17 +70,18 @@
def current_working_directory; @rye_current_working_directory; end
# The most recent valud for umask (or 0022)
def current_umask; @rye_current_umask; end
- def info; @rye_info; end
- def debug; @rye_debug; end
- def error; @rye_error; end
+ def info?; !@rye_info.nil?; end
+ def debug?; !@rye_debug.nil?; end
+ def error?; !@rye_error.nil?; end
def ostype=(val); @rye_ostype = val; end
def impltype=(val); @rye_impltype = val; end
def pre_command_hook=(val); @rye_pre_command_hook = val; end
+ def stdout_hook=(val); @rye_stdout_hook = val; end
def post_command_hook=(val); @rye_post_command_hook = val; end
# A Hash. The keys are exception classes, the values are Procs to execute
def exception_hook=(val); @rye_exception_hook = val; end
# * +host+ The hostname to connect to. Default: localhost.
@@ -87,10 +96,11 @@
# * :info => an IO object to print Rye::Box command info to. Default: nil
# * :debug => an IO object to print Rye::Box debugging info to. Default: nil
# * :error => an IO object to print Rye::Box errors to. Default: STDERR
# * :getenv => pre-fetch +host+ environment variables? (default: true)
# * :password => the user's password (ignored if there's a valid private key)
+ # * :templates => the template engine to use for uploaded files. One of: :erb (default)
# * :sudo => Run all commands via sudo (default: false)
#
# NOTE: +opts+ can also contain any parameter supported by
# Net::SSH.start that is not already mentioned above.
#
@@ -107,15 +117,16 @@
# These opts are use by Rye::Box and also passed to Net::SSH
@rye_opts = {
:safe => true,
:port => ssh_opts[:port],
- :keys => [],
+ :keys => Rye.keys,
:info => nil,
:debug => nil,
:error => STDERR,
:getenv => true,
+ :templates => :erb,
:quiet => false
}.merge(opts)
# Close the SSH session before Ruby exits. This will do nothing
# if disconnect has already been called explicitly.
@@ -126,20 +137,28 @@
@rye_safe, @rye_debug = @rye_opts.delete(:safe), @rye_opts.delete(:debug)
@rye_info, @rye_error = @rye_opts.delete(:info), @rye_opts.delete(:error)
@rye_getenv = {} if @rye_opts.delete(:getenv) # Enable getenv with a hash
@rye_ostype, @rye_impltype = @rye_opts.delete(:ostype), @rye_opts.delete(:impltype)
@rye_quiet, @rye_sudo = @rye_opts.delete(:quiet), @rye_opts.delete(:sudo)
+ @rye_templates = @rye_opts.delete(:templates)
- # Just in case someone sends a true value rather than IO object
- @rye_debug = STDERR if @rye_debug == true
- @rye_error = STDERR if @rye_error == true
- @rye_info = STDOUT if @rye_info == true
+ # Store the state of the terminal
+ @rye_stty_save = `stty -g`.chomp rescue nil
+ unless @rye_templates.nil?
+ require @rye_templates.to_s # should be :erb
+ end
+
@rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
@rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
@rye_opts[:keys] = [@rye_opts[:keys]].flatten.compact
+ # Just in case someone sends a true value rather than IO object
+ @rye_debug = STDERR if @rye_debug == true || DEBUG
+ @rye_error = STDERR if @rye_error == true
+ @rye_info = STDOUT if @rye_info == true
+
# Add the given private keys to the keychain that will be used for @rye_host
add_keys(@rye_opts[:keys])
# We don't want Net::SSH to handle the keypairs. This may change
# but for we're letting ssh-agent do it.
@@ -221,16 +240,27 @@
@rye_user = newuser
disconnect
end
- # Open an interactive SSH session. This only works if STDIN.tty?
- # returns true. Otherwise it returns the SSH command that would
- # have been run. This requires the SSH command-line executable (ssh).
- # * +run+ when set to false, it will return the SSH command as a String
- # and not open an SSH session.
+ # If STDIN.tty? is true (i.e. if we're connected to a terminal
+ # with a human at the helm), this will open an SSH connection
+ # via the regular SSH command (via a call to system). This
+ # requires the SSH command-line executable (ssh).
#
+ # If STDIN.tty? is false or +run+ is false, this will return
+ # the SSH command (a String) that would have been run.
+ #
+ # NOTE: As of Rye 0.9 you can run interactive sessions with
+ # rye by calling any shell method without arguments.
+ #
+ # e.g.
+ #
+ # rbox = Rye::Box.new 'somemachine'
+ # rbox.bash
+ #
+ # TODO: refactor to use net_ssh_exec! in 0.9
def interactive_ssh(run=true)
debug "interactive_ssh with keys: #{Rye.keys.inspect}"
run = false unless STDIN.tty?
cmd = Rye.prepare_command("ssh", "#{@rye_user}@#{@rye_host}")
return cmd unless run
@@ -247,15 +277,15 @@
return @rye_opts[:keys]
end
additional_keys = [additional_keys].flatten.compact || []
return if additional_keys.empty?
ret = Rye.add_keys(additional_keys)
- if ret.is_a?(Rye::Rap)
- debug "ssh-add exit_code: #{ret.exit_code}"
- debug "ssh-add stdout: #{ret.stdout}"
- debug "ssh-add stderr: #{ret.stderr}"
- end
+ #if ret.is_a?(Rye::Rap)
+ # debug "ssh-add exit_status: #{ret.exit_status}"
+ # debug "ssh-add stdout: #{ret.stdout}"
+ # debug "ssh-add stderr: #{ret.stderr}"
+ #end
self # MUST RETURN self
end
alias :add_key :add_keys
# Return the value of uname in lowercase
@@ -377,114 +407,10 @@
end
@rye_guessed_homes[this_user] = "#{user_defaults['HOME']}/#{this_user}"
end
- # Copy the local public keys (as specified by Rye.keys) to
- # this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
- # Returns a Rye::Rap object. The private keys files used to generate
- # the public keys are contained in stdout.
- # Raises a Rye::ComandError if the home directory doesn't exit.
- # NOTE: authorize_keys_remote disables safe-mode for this box while it runs
- # which will hit you funky style if your using a single instance
- # of Rye::Box in a multithreaded situation.
- #
- def authorize_keys_remote(other_user=nil)
- if other_user.nil?
- this_user = opts[:user]
- homedir = self.quietly { pwd }.first
- else
- this_user = other_user
- # The homedir path is important b/c this is where we're going to
- # look for the .ssh directory. That's where auth love is stored.
- homedir = self.guess_user_home(this_user)
- end
-
- added_keys = []
- rap = Rye::Rap.new(self)
-
- prevdir = self.current_working_directory
-
- unless self.file_exists?(homedir)
- rap.add_exit_code(1)
- rap.add_stderr("Path does not exist: #{homedir}")
- raise Rye::CommandError.new(rap)
- end
-
- # Let's go into the user's home directory that we now know exists.
- self.cd homedir
-
- files = ['.ssh/authorized_keys', '.ssh/authorized_keys2', '.ssh/identity']
- files.each do |akey_path|
- if self.file_exists?(akey_path)
- # TODO: Make Rye::Cmd.incremental_backup
- self.cp(akey_path, "#{akey_path}-previous")
- authorized_keys = self.file_download("#{homedir}/#{akey_path}")
- end
- authorized_keys ||= StringIO.new
-
- Rye.keys.each do |path|
-
- info "# Adding public key for #{path}"
- k = Rye::Key.from_file(path).public_key.to_ssh2
- authorized_keys.puts k
- end
-
- # Remove duplicate authorized keys
- authorized_keys.rewind
- uniqlines = authorized_keys.readlines.uniq.join
- authorized_keys = StringIO.new(uniqlines)
- # We need to rewind so that all of the StringIO object is uploaded
- authorized_keys.rewind
-
- full_path = "#{homedir}/#{akey_path}"
- temp_path = "/tmp/rye-#{user}-#{File.basename(akey_path)}"
-
- sudo do
- mkdir :p, :m, '700', File.dirname(akey_path)
- file_upload authorized_keys, temp_path
- mv temp_path, full_path
- chmod '0600', akey_path
- chown :R, this_user.to_s, File.dirname(akey_path)
- end
- end
-
- # And let's return to the directory we came from.
- self.cd prevdir
-
- rap.add_exit_code(0)
- rap
- end
- require 'fileutils'
- # Authorize the current user to login to the local machine via
- # SSH without a password. This is the same functionality as
- # authorize_keys_remote except run with local shell commands.
- def authorize_keys_local
- added_keys = []
- ssh_dir = File.join(Rye.sysinfo.home, '.ssh')
- Rye.keys.each do |path|
- debug "# Public key for #{path}"
- k = Rye::Key.from_file(path).public_key.to_ssh2
- FileUtils.mkdir ssh_dir unless File.exists? ssh_dir
-
- authkeys_file = File.join(ssh_dir, 'authorized_keys')
-
- debug "Writing to #{authkeys_file}"
- File.open(authkeys_file, 'a') {|f| f.write("#{$/}#{k}") }
- File.open("#{authkeys_file}2", 'a') {|f| f.write("#{$/}#{k}") }
-
- unless Rye.sysinfo.os == :windows
- Rye.shell(:chmod, '700', ssh_dir)
- Rye.shell(:chmod, '0600', authkeys_file)
- Rye.shell(:chmod, '0600', "#{authkeys_file}2")
- end
-
- added_keys << path
- end
- added_keys
- end
-
# A handler for undefined commands.
# Raises Rye::CommandNotFound exception.
def method_missing(cmd, *args, &block)
if @rye_safe
ex = Rye::CommandNotFound.new(cmd.to_s)
@@ -516,10 +442,21 @@
def pre_command_hook(&block)
@rye_pre_command_hook = block if block
@rye_pre_command_hook
end
+ # Supply a block to be called every time a command receives STDOUT data.
+ #
+ # e.g.
+ # rbox.stdout_hook do |content|
+ # ...
+ # end
+ def stdout_hook(&block)
+ @rye_stdout_hook = block if block
+ @rye_stdout_hook
+ end
+
# Supply a block to be called whenever there's an Exception. It's called
# with 1 argument: the exception class. If the exception block returns
# :retry, the command will be executed again.
#
# e.g.
@@ -557,11 +494,11 @@
# end
#
# Returns the return value of the block.
#
def batch(*args, &block)
- self.instance_exec *args, &block
+ self.instance_exec(*args, &block)
end
# Like batch, except it disables safe mode before executing the block.
# After executing the block, safe mode is returned back to whichever
# state it was previously in. In other words, this method won't enable
@@ -571,10 +508,11 @@
disable_safe_mode
ret = self.instance_exec *args, &block
@rye_safe = previous_state
ret
end
+ alias_method :wildly, :unsafely
# See unsafely (except in reverse)
def safely(*args, &block)
previous_state = @rye_safe
enable_safe_mode
@@ -605,11 +543,11 @@
#
# If no block is specified then sudo is called just like a regular
# command.
def sudo(*args, &block)
if block.nil?
- __allow('sudo', args);
+ run_command('sudo', args);
else
previous_state = @rye_sudo
enable_sudo
ret = self.instance_exec *args, &block
@rye_sudo = previous_state
@@ -701,19 +639,23 @@
# Close the SSH session with +@rye_host+. This is called
# automatically at exit if the connection is open.
def disconnect
return unless @rye_ssh && !@rye_ssh.closed?
begin
- Timeout::timeout(10) do
- @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
+ if @rye_ssh.busy?;
+ info "Is something still running? (ctrl-C to exit)"
+ Timeout::timeout(10) do
+ @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
+ end
end
+ debug "Closing connection to #{@rye_ssh.host}"
+ @rye_ssh.close
rescue SystemCallError, Timeout::Error => ex
- error "Disconnect timeout (was something still running?)"
+ error "Disconnect timeout"
+ rescue Interrupt
+ debug "Exiting..."
end
-
- debug "Closing connection to #{@rye_ssh.host}"
- @rye_ssh.close
end
private
@@ -749,15 +691,17 @@
# $ ls -l 'arg1' 'arg2'
#
# This method will try to connect to the host automatically
# but if it fails it will raise a Rye::NotConnected exception.
#
- def run_command(*args)
- debug "run_command with keys: #{Rye.keys.inspect}"
+ def run_command(*args, &blk)
+ debug "run_command"
cmd, args = prep_args(*args)
+ #p [:run_command, cmd, blk.nil?]
+
connect if !@rye_ssh || @rye_ssh.closed?
raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
cmd_clean = Rye.escape(@rye_safe, cmd, args)
@@ -767,11 +711,11 @@
# Add the current working directory before the command if supplied.
# The command will otherwise run in the user's home directory.
if @rye_current_working_directory
cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
- cmd_internal = [cwd, cmd_internal].join(' && ')
+ cmd_internal = '(%s; %s)' % [cwd, cmd_internal]
end
# ditto (same explanation as cwd)
if @rye_current_umask
cwd = Rye.escape(@rye_safe, 'umask', @rye_current_umask)
@@ -795,26 +739,28 @@
end
rap = Rye::Rap.new(self)
rap.cmd = cmd_clean
- stdout, stderr, ecode, esignal = net_ssh_exec!(cmd_internal)
+ channel = net_ssh_exec!(cmd_internal, &blk)
- rap.add_stdout(stdout || '')
- rap.add_stderr(stderr || '')
- rap.add_exit_code(ecode)
- rap.exit_signal = esignal
+ if channel[:exception]
+ rap = channel[:exception].rap
+ else
+ rap.add_stdout(channel[:stdout].read || '')
+ rap.add_stderr(channel[:stderr].read || '')
+ rap.add_exit_status(channel[:exit_status])
+ rap.exit_signal = channel[:exit_signal]
+ end
- #info "stdout: #{rap.stdout}"
- #info "stderr: #{rap.stderr}"
- #info "exit_code: #{rap.exit_code}"
+ debug "RESULT: %s " % [rap.inspect]
# It seems a convention for various commands to return -1
- # when something only mildly concerning happens. ls even
- # returns -1 for apparently no reason sometimes. In any
- # case, the real errors are the ones greater than zero
- raise Rye::CommandError.new(rap) if ecode != 0
+ # when something only mildly concerning happens. (ls even
+ # returns -1 for apparently no reason sometimes). Anyway,
+ # the real errors are the ones that are greater than zero.
+ raise Rye::Err.new(rap) if rap.exit_status != 0
rescue Exception => ex
return rap if @rye_quiet
choice = nil
@rye_exception_hook.each_pair do |klass,act|
@@ -824,11 +770,18 @@
end
if choice == :retry
retry
elsif choice == :skip
# do nothing
- else
+ elsif choice == :interactive && !@rye_shell
+ @rye_shell = true
+ previous_state = @rye_sudo
+ disable_sudo
+ bash
+ @rye_sudo = previous_state
+ @rye_shell = false
+ elsif !ex.is_a?(Interrupt)
raise ex, ex.message
end
end
if !@rye_quiet && @rye_post_command_hook.is_a?(Proc)
@@ -856,92 +809,237 @@
a
end
[cmd, args]
end
- # Executes +command+ via SSH
- # Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
- # NOTE: This method needs to be replaced to fully support interactive
- # commands. This implementation is weird because it's getting just STDOUT and
- # STDERR responses (check value of "type"). on_data and on_extended_data method
- # hooks are not used. See the following threads for implementation ideas:
- #
- # http://www.ruby-forum.com/topic/141814
- # http://www.ruby-forum.com/topic/169997
- #
- def net_ssh_exec!(command)
+ def net_ssh_exec!(cmd, &blk)
+ debug ":net_ssh_exec #{cmd} (has blk: #{!blk.nil?}; pty: #{@rye_pty}; shell: #{@rye_shell})"
+
+ pty_opts = { :term => "xterm",
+ :chars_wide => 80,
+ :chars_high => 24,
+ :pixels_wide => 640,
+ :pixels_high => 480,
+ :modes => {} }
+
+ channel = @rye_ssh.open_channel do |channel|
+ if self.rye_shell && blk.nil?
+ channel.request_pty(pty_opts) do |ch,success|
+ self.rye_pty = success
+ raise Rye::NoPty if !success
+ end
+ end
+ channel.exec(cmd, &create_channel)
+ channel[:state] = :start_session
+ channel[:block] = blk
+ end
+
+ @rye_channels ||= []
+ @rye_channels << channel
+
+ @rye_ssh.loop(0.1) do
+ break if channel.nil? || !channel.active?
+ !channel.eof? # otherwise keep returning true
+ end
+
+ channel
+ end
+
+
+ def state_wait_for_command(channel)
+ debug :wait_for_command
+ end
- block ||= Proc.new do |channel, type, data, tt|
+ def state_start_session(channel)
+ debug "#{:start_session} [blk: #{!channel[:block].nil?}] [pty: #{@rye_pty}] [shell: #{@rye_shell}]"
+ channel[:state] = nil
+ channel[:state] = :run_block if channel[:block]
+ channel[:state] = :await_response if @rye_pty
+ end
+
+ def state_await_response(channel)
+ debug :await_response
+ @await_response_counter ||= 0
+ if channel[:stdout].available > 0 || channel[:stderr].available > 0
+ channel[:state] = :read_response
+ elsif @await_response_counter > 50
+ @await_response_counter = 0
+ channel[:state] = :await_input
+ end
+ @await_response_counter += 1
+ end
+
+ def state_read_response(channel)
+ debug :read_response
+ if channel[:stdout].available > 0 || channel[:stderr].available > 0
- channel[:stdout] ||= ""
- channel[:stderr] ||= ""
+ stdout = channel[:stdout].read if channel[:stdout].available > 0
+ stderr = channel[:stderr].read if channel[:stderr].available > 0
+ print stdout if stdout
+ print stderr if stderr
- if type == :stderr
- # NOTE: Use sudo to test this since it prompts for a passwords.
- # Use sudo -K to kill the user's timestamp (ask for a password every time)
- if data =~ /password:/i
- ret = Annoy.get_user_input("Password: ", '*')
- raise Rye::NoPassword if ret.nil?
- channel.send_data "#{ret}\n"
- else
- channel[:stderr] << data
+ if channel[:stack].empty?
+ channel[:state] = :await_input
+ elsif channel[:stdout].available > 0 || channel[:stderr].available > 0
+ channel[:state] = :read_response
+ else
+ channel[:state] = :send_data
+ end
+ else
+ channel[:state] = :await_response
+ end
+
+ end
+
+ def state_send_data(channel)
+ debug :send_data
+ cmd = channel[:stack].shift
+ debug "sending #{cmd.inspect}"
+ channel[:state] = :await_response
+ channel.send_data("#{cmd}\n") unless channel.eof?
+ end
+
+ def state_await_input(channel)
+ debug :await_input
+ if channel[:stdout].available > 0
+ channel[:state] = :read_response
+ else
+ ret = nil
+ if channel[:prompt] && (channel[:prompt] =~ /pass/i)
+ ret = Annoy.get_user_input("#{channel[:prompt]} ", echo='*', period=30)
+ channel[:prompt] = nil
end
+ begin
+ list = self.commands.sort
- # If someone tries to open an interactive ssh session
- # through a regular Rye::Box command, Net::SSH will
- # return the following error and appear to hang. We
- # catch it and raise the appropriate exception.
- raise Rye::NoPty if data =~ /Pseudo-terminal will not/
- elsif type == :stdout
- if data =~ /Select gem to uninstall/i
- puts data
- ret = Annoy.get_user_input('')
- raise "No input given" if ret.nil?
- channel.send_data "#{ret}\n"
- else
- channel[:stdout] << data
+ comp = proc { |s|
+ # TODO: Something here for files
+ list.grep( /^#{Regexp.escape(s)}/ )
+ }
+
+ Readline.completion_append_character = " "
+ Readline.completion_proc = comp
+
+ ret = Readline.readline(channel[:prompt] || '', true)
+ #ret = STDIN.gets
+
+ if ret.nil?
+ channel[:state] = :exit
+ else
+ channel[:stack] << ret.chomp
+ channel[:state] = :send_data
+ end
+ rescue Interrupt => e
+ channel[:state] = :exit
end
+ channel[:prompt] = nil
end
-
+ end
+
+ def state_ignore_response(channel)
+ debug :ignore_response
+ @ignore_response_counter ||= 0
+ if channel[:stdout].available > 0
+ @await_response_counter = 0
+ channel[:stdout].read
+ channel[:state] = :process
+ elsif @ignore_response_counter > 2
+ @await_response_counter = 0
+ channel[:state] = :process
end
-
- channel = @rye_ssh.exec(command, &block)
-
- channel.on_request("exit-status") do |ch, data|
- # Anything greater than 0 is an error
- channel[:exit_code] = data.read_long
+ @ignore_response_counter += 1
+ end
+
+ def state_exit(channel)
+ debug :exit_state
+ channel[:state] = nil
+ if rye_shell && (!channel.eof? || !channel.closing?)
+ puts
+ channel.send_data("exit\n")
+ else
+ channel.eof!
end
- channel.on_request("exit-signal") do |ch, data|
- # This should be the POSIX SIGNAL that ended the process
- channel[:exit_signal] = data.read_long
+ end
+
+ # TODO: implement callback in create_channel Proc
+ ##def state_handle_error(channel)
+ ## debug :handle_error
+ ## channel[:state] = nil
+ ## if rye_shell && (!channel.eof? || !channel.closing?)
+ ## puts
+ ## channel.send_data("exit\n")
+ ## else
+ ## channel.eof!
+ ## end
+ ##end
+
+
+ def state_run_block(channel)
+ debug :run_block
+ channel[:state] = nil
+ blk = channel[:block]
+ channel[:block] = nil
+ begin
+ instance_eval &blk
+ rescue => ex
+ channel[:exception] = ex
end
-
- channel.wait # block until we get a response
- channel.request_pty do |ch, success|
- raise Rye::NoPty if !success
+ channel[:state] = :exit
+ end
+
+ def create_channel()
+ Proc.new do |channel,success|
+ channel[:stdout ] = Net::SSH::Buffer.new
+ channel[:stderr ] = Net::SSH::Buffer.new
+ channel[:stack] ||= []
+ channel.on_close { |ch|
+ channel[:handler] = ":on_close"
+ }
+ channel.on_data { |ch, data|
+ channel[:handler] = ":on_data"
+ @rye_stdout_hook.call(data) if !@rye_pty && !@rye_quiet && @rye_stdout_hook.kind_of?(Proc)
+ if rye_pty && data =~ /password/i
+ channel[:prompt] = data
+ channel[:state] = :await_input
+ else
+ channel[:stdout].append(data)
+ end
+ }
+ channel.on_extended_data { |ch, type, data|
+ channel[:handler] = ":on_extended_data"
+ if rye_pty && data =~ /\Apassword/i
+ channel[:prompt] = data
+ channel[:state] = :await_input
+ else
+ channel[:stderr].append(data)
+ end
+ }
+ channel.on_request("exit-status") { |ch, data|
+ channel[:handler] = ":on_request (exit-status)"
+ channel[:exit_status] = data.read_long
+ }
+ channel.on_request("exit-signal") do |ch, data|
+ channel[:handler] = ":on_request (exit-signal)"
+ # This should be the POSIX SIGNAL that ended the process
+ channel[:exit_signal] = data.read_long
+ end
+ channel.on_process {
+ channel[:handler] = :on_process
+ print channel[:stderr].read if channel[:stderr].available > 0
+ begin
+ send("state_#{channel[:state]}", channel) unless channel[:state].nil?
+ rescue Interrupt
+ debug :on_process_interrupt
+ channel[:state] = :exit
+ end
+ }
end
-
- ## I'm getting weird behavior with exit codes. Sometimes
- ## a command which usually returns an exit code will not
- ## return one the next time it's run. The following crap
- ## was from the debugging.
- ##Kernel.sleep 5
- ###channel.close
- #channel.eof!
- ##p [:active, channel.active?]
- ##p [:closing, channel.closing?]
- ##p [:eof, channel.eof?]
-
- channel[:exit_code] ||= 0
- channel[:exit_code] &&= channel[:exit_code].to_i
- channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]
-
- [channel[:stdout], channel[:stderr], channel[:exit_code], channel[:exit_signal]]
end
+
# * +direction+ is one of :upload, :download
# * +recursive+ should be true for directories and false for files.
# * +files+ is an Array of file paths, the content is direction specific.
# For downloads, +files+ is a list of files to download. The last element
# must be the local directory to download to. If downloading a single file
@@ -976,10 +1074,11 @@
else
target = files.pop # The last path is the download target.
end
elsif direction == :upload
+# p :UPLOAD, @rye_templates
raise "Cannot upload to a StringIO object" if target.is_a?(StringIO)
if files.size == 1
target = self.getenv['HOME'] || guess_user_home
debug "Assuming upload to #{target}"
else
@@ -987,11 +1086,13 @@
end
# Expand fileglobs (e.g. path/*.rb becomes [path/1.rb, path/2.rb]).
# This should happen after checking files.size to determine the target
unless @rye_safe
- files.collect! { |file| Dir.glob File.expand_path(file) }
+ files.collect! { |file|
+ file.is_a?(StringIO) ? file : Dir.glob(File.expand_path(file))
+ }
files.flatten!
end
end
# Fail early. We check whether the StringIO object is available to read
@@ -1002,11 +1103,10 @@
# If a StringIO object is at end of file, SCP will hang. (TODO: SCP)
file.rewind if file.eof?
end
end
- info "#{direction.to_s} to: #{target}"
debug "FILES: " << files.join(', ')
# Make sure the target directory exists. We can do this only when
# there's more than one file because "target" could be a file name
if files.size > 1 && !target.is_a?(StringIO)
@@ -1018,25 +1118,19 @@
transfers = []
prev = ""
files.each do |file|
debug file.to_s
prev = ""
- unless @rye_quiet
- tmp_file = file.is_a?(StringIO) ? '<string>' : file
- tmp_target = target.is_a?(StringIO) ? '<string>' : target
- STDOUT.puts "[#{direction}] #{tmp_file} #{tmp_target}"
- end
+ line = nil
transfers << scp.send(direction, file, target, :recursive => recursive) do |ch, n, s, t|
line = "%-50s %6d/%-6d bytes" % [n, s, t]
spaces = (prev.size > line.size) ? ' '*(prev.size - line.size) : ''
- pinfo "%s %s \r" % [line, spaces] # update line: "file: sent/total"
+ pinfo "[%s] %s %s %s" % [direction, line, spaces, s == t ? "\n" : "\r"] # update line: "file: sent/total"
@rye_info.flush if @rye_info # make sure every line is printed
prev = line
end
end
transfers.each { |t| t.wait } # Run file transfers in parallel
- pinfo (' '*prev.size) << "\r"
- info $/
end
target.is_a?(StringIO) ? target : nil
end