rye 0.8.19 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES.txt +17 -1
- data/README.rdoc +2 -29
- data/bin/try +42 -13
- data/lib/rye/box.rb +318 -224
- data/lib/rye/cmd.rb +156 -78
- data/lib/rye/rap.rb +10 -6
- data/lib/rye.rb +32 -5
- data/rye.gemspec +4 -7
- metadata +68 -49
- data/bin/rye +0 -147
    
        data/lib/rye/box.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            require 'annoy'
         | 
| 2 | 
            +
            require 'readline'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module Rye
         | 
| 5 | 
            +
              DEBUG = false unless defined?(Rye::DEBUG)
         | 
| 4 6 |  | 
| 5 7 | 
             
              # = Rye::Box
         | 
| 6 8 | 
             
              #
         | 
| @@ -29,12 +31,18 @@ module Rye | |
| 29 31 | 
             
              class Box 
         | 
| 30 32 | 
             
                include Rye::Cmd
         | 
| 31 33 |  | 
| 34 | 
            +
                attr_accessor :rye_shell
         | 
| 35 | 
            +
                attr_accessor :rye_pty
         | 
| 36 | 
            +
                
         | 
| 32 37 | 
             
                def host; @rye_host; end
         | 
| 33 38 | 
             
                def opts; @rye_opts; end
         | 
| 34 39 | 
             
                def safe; @rye_safe; end
         | 
| 35 40 | 
             
                def user; @rye_user; end
         | 
| 36 41 | 
             
                def root?; user.to_s == "root" end
         | 
| 37 42 |  | 
| 43 | 
            +
                def templates; @rye_templates; end
         | 
| 44 | 
            +
                def templates?; !@rye_templates.nil?; end 
         | 
| 45 | 
            +
                
         | 
| 38 46 | 
             
                def enable_sudo; @rye_sudo = true; end
         | 
| 39 47 | 
             
                def disable_sudo; @rye_sudo = false; end
         | 
| 40 48 | 
             
                def sudo?; @rye_sudo == true end
         | 
| @@ -64,13 +72,14 @@ module Rye | |
| 64 72 | 
             
                # The most recent valud for umask (or 0022)
         | 
| 65 73 | 
             
                def current_umask; @rye_current_umask; end
         | 
| 66 74 |  | 
| 67 | 
            -
                def info | 
| 68 | 
            -
                def debug | 
| 69 | 
            -
                def error | 
| 75 | 
            +
                def info?; !@rye_info.nil?; end
         | 
| 76 | 
            +
                def debug?; !@rye_debug.nil?; end
         | 
| 77 | 
            +
                def error?; !@rye_error.nil?; end
         | 
| 70 78 |  | 
| 71 79 | 
             
                def ostype=(val); @rye_ostype = val; end 
         | 
| 72 80 | 
             
                def impltype=(val); @rye_impltype = val; end 
         | 
| 73 81 | 
             
                def pre_command_hook=(val); @rye_pre_command_hook = val; end
         | 
| 82 | 
            +
                def stdout_hook=(val); @rye_stdout_hook = val; end
         | 
| 74 83 | 
             
                def post_command_hook=(val); @rye_post_command_hook = val; end
         | 
| 75 84 | 
             
                # A Hash. The keys are exception classes, the values are Procs to execute
         | 
| 76 85 | 
             
                def exception_hook=(val); @rye_exception_hook = val; end
         | 
| @@ -89,6 +98,7 @@ module Rye | |
| 89 98 | 
             
                # * :error => an IO object to print Rye::Box errors to. Default: STDERR
         | 
| 90 99 | 
             
                # * :getenv => pre-fetch +host+ environment variables? (default: true)
         | 
| 91 100 | 
             
                # * :password => the user's password (ignored if there's a valid private key)
         | 
| 101 | 
            +
                # * :templates => the template engine to use for uploaded files. One of: :erb (default)
         | 
| 92 102 | 
             
                # * :sudo => Run all commands via sudo (default: false)
         | 
| 93 103 | 
             
                #
         | 
| 94 104 | 
             
                # NOTE: +opts+ can also contain any parameter supported by 
         | 
| @@ -109,11 +119,12 @@ module Rye | |
| 109 119 | 
             
                  @rye_opts = {
         | 
| 110 120 | 
             
                    :safe => true,
         | 
| 111 121 | 
             
                    :port => ssh_opts[:port],
         | 
| 112 | 
            -
                    :keys =>  | 
| 122 | 
            +
                    :keys => Rye.keys,
         | 
| 113 123 | 
             
                    :info => nil,
         | 
| 114 124 | 
             
                    :debug => nil,
         | 
| 115 125 | 
             
                    :error => STDERR,
         | 
| 116 126 | 
             
                    :getenv => true,
         | 
| 127 | 
            +
                    :templates => :erb,
         | 
| 117 128 | 
             
                    :quiet => false
         | 
| 118 129 | 
             
                  }.merge(opts)
         | 
| 119 130 |  | 
| @@ -128,16 +139,24 @@ module Rye | |
| 128 139 | 
             
                  @rye_getenv = {} if @rye_opts.delete(:getenv) # Enable getenv with a hash
         | 
| 129 140 | 
             
                  @rye_ostype, @rye_impltype = @rye_opts.delete(:ostype), @rye_opts.delete(:impltype)
         | 
| 130 141 | 
             
                  @rye_quiet, @rye_sudo = @rye_opts.delete(:quiet), @rye_opts.delete(:sudo)
         | 
| 142 | 
            +
                  @rye_templates = @rye_opts.delete(:templates)
         | 
| 131 143 |  | 
| 132 | 
            -
                  #  | 
| 133 | 
            -
                  @ | 
| 134 | 
            -
                   | 
| 135 | 
            -
                   | 
| 144 | 
            +
                  # Store the state of the terminal 
         | 
| 145 | 
            +
                  @rye_stty_save = `stty -g`.chomp rescue nil
         | 
| 146 | 
            +
                  
         | 
| 147 | 
            +
                  unless @rye_templates.nil?
         | 
| 148 | 
            +
                    require @rye_templates.to_s   # should be :erb
         | 
| 149 | 
            +
                  end
         | 
| 136 150 |  | 
| 137 151 | 
             
                  @rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
         | 
| 138 152 | 
             
                  @rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
         | 
| 139 153 | 
             
                  @rye_opts[:keys] = [@rye_opts[:keys]].flatten.compact
         | 
| 140 154 |  | 
| 155 | 
            +
                  # Just in case someone sends a true value rather than IO object
         | 
| 156 | 
            +
                  @rye_debug = STDERR if @rye_debug == true || DEBUG
         | 
| 157 | 
            +
                  @rye_error = STDERR if @rye_error == true
         | 
| 158 | 
            +
                  @rye_info = STDOUT if @rye_info == true
         | 
| 159 | 
            +
                  
         | 
| 141 160 | 
             
                  # Add the given private keys to the keychain that will be used for @rye_host
         | 
| 142 161 | 
             
                  add_keys(@rye_opts[:keys])
         | 
| 143 162 |  | 
| @@ -223,12 +242,23 @@ module Rye | |
| 223 242 | 
             
                end
         | 
| 224 243 |  | 
| 225 244 |  | 
| 226 | 
            -
                #  | 
| 227 | 
            -
                #  | 
| 228 | 
            -
                #  | 
| 229 | 
            -
                #  | 
| 230 | 
            -
                # | 
| 245 | 
            +
                # If STDIN.tty? is true (i.e. if we're connected to a terminal
         | 
| 246 | 
            +
                # with a human at the helm), this will open an SSH connection
         | 
| 247 | 
            +
                # via the regular SSH command (via a call to system). This 
         | 
| 248 | 
            +
                # requires the SSH command-line executable (ssh).
         | 
| 249 | 
            +
                #
         | 
| 250 | 
            +
                # If STDIN.tty? is false or +run+ is false, this will return 
         | 
| 251 | 
            +
                # the SSH command (a String) that would have been run. 
         | 
| 252 | 
            +
                # 
         | 
| 253 | 
            +
                # NOTE: As of Rye 0.9 you can run interactive sessions with
         | 
| 254 | 
            +
                # rye by calling any shell method without arguments. 
         | 
| 255 | 
            +
                #
         | 
| 256 | 
            +
                # e.g.
         | 
| 257 | 
            +
                #
         | 
| 258 | 
            +
                #     rbox = Rye::Box.new 'somemachine'
         | 
| 259 | 
            +
                #     rbox.bash
         | 
| 231 260 | 
             
                #
         | 
| 261 | 
            +
                # TODO: refactor to use net_ssh_exec! in 0.9
         | 
| 232 262 | 
             
                def interactive_ssh(run=true)
         | 
| 233 263 | 
             
                  debug "interactive_ssh with keys: #{Rye.keys.inspect}"
         | 
| 234 264 | 
             
                  run = false unless STDIN.tty?      
         | 
| @@ -249,11 +279,11 @@ module Rye | |
| 249 279 | 
             
                  additional_keys = [additional_keys].flatten.compact || []
         | 
| 250 280 | 
             
                  return if additional_keys.empty?
         | 
| 251 281 | 
             
                  ret = Rye.add_keys(additional_keys) 
         | 
| 252 | 
            -
                  if ret.is_a?(Rye::Rap)
         | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
                  end
         | 
| 282 | 
            +
                  #if ret.is_a?(Rye::Rap)
         | 
| 283 | 
            +
                  #  debug "ssh-add exit_status: #{ret.exit_status}" 
         | 
| 284 | 
            +
                  #  debug "ssh-add stdout: #{ret.stdout}"
         | 
| 285 | 
            +
                  #  debug "ssh-add stderr: #{ret.stderr}"
         | 
| 286 | 
            +
                  #end
         | 
| 257 287 | 
             
                  self # MUST RETURN self
         | 
| 258 288 | 
             
                end
         | 
| 259 289 | 
             
                alias :add_key :add_keys
         | 
| @@ -379,110 +409,6 @@ module Rye | |
| 379 409 | 
             
                  @rye_guessed_homes[this_user] = "#{user_defaults['HOME']}/#{this_user}"
         | 
| 380 410 | 
             
                end
         | 
| 381 411 |  | 
| 382 | 
            -
                # Copy the local public keys (as specified by Rye.keys) to 
         | 
| 383 | 
            -
                # this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2. 
         | 
| 384 | 
            -
                # Returns a Rye::Rap object. The private keys files used to generate 
         | 
| 385 | 
            -
                # the public keys are contained in stdout.
         | 
| 386 | 
            -
                # Raises a Rye::ComandError if the home directory doesn't exit. 
         | 
| 387 | 
            -
                # NOTE: authorize_keys_remote disables safe-mode for this box while it runs
         | 
| 388 | 
            -
                # which will hit you funky style if your using a single instance
         | 
| 389 | 
            -
                # of Rye::Box in a multithreaded situation. 
         | 
| 390 | 
            -
                #
         | 
| 391 | 
            -
                def authorize_keys_remote(other_user=nil)
         | 
| 392 | 
            -
                  if other_user.nil?
         | 
| 393 | 
            -
                    this_user = opts[:user]
         | 
| 394 | 
            -
                    homedir = self.quietly { pwd }.first
         | 
| 395 | 
            -
                  else
         | 
| 396 | 
            -
                    this_user = other_user 
         | 
| 397 | 
            -
                    # The homedir path is important b/c this is where we're going to 
         | 
| 398 | 
            -
                    # look for the .ssh directory. That's where auth love is stored.
         | 
| 399 | 
            -
                    homedir = self.guess_user_home(this_user)
         | 
| 400 | 
            -
                  end
         | 
| 401 | 
            -
                  
         | 
| 402 | 
            -
                  added_keys = []
         | 
| 403 | 
            -
                  rap = Rye::Rap.new(self)
         | 
| 404 | 
            -
                  
         | 
| 405 | 
            -
                  prevdir = self.current_working_directory
         | 
| 406 | 
            -
                  
         | 
| 407 | 
            -
                  unless self.file_exists?(homedir)
         | 
| 408 | 
            -
                    rap.add_exit_code(1)
         | 
| 409 | 
            -
                    rap.add_stderr("Path does not exist: #{homedir}")
         | 
| 410 | 
            -
                    raise Rye::CommandError.new(rap)
         | 
| 411 | 
            -
                  end
         | 
| 412 | 
            -
                  
         | 
| 413 | 
            -
                  # Let's go into the user's home directory that we now know exists.
         | 
| 414 | 
            -
                  self.cd homedir
         | 
| 415 | 
            -
                  
         | 
| 416 | 
            -
                  files = ['.ssh/authorized_keys', '.ssh/authorized_keys2', '.ssh/identity']
         | 
| 417 | 
            -
                  files.each do |akey_path|
         | 
| 418 | 
            -
                    if self.file_exists?(akey_path)
         | 
| 419 | 
            -
                      # TODO: Make Rye::Cmd.incremental_backup
         | 
| 420 | 
            -
                      self.cp(akey_path, "#{akey_path}-previous")
         | 
| 421 | 
            -
                      authorized_keys = self.file_download("#{homedir}/#{akey_path}")
         | 
| 422 | 
            -
                    end
         | 
| 423 | 
            -
                    authorized_keys ||= StringIO.new
         | 
| 424 | 
            -
                    
         | 
| 425 | 
            -
                    Rye.keys.each do |path|
         | 
| 426 | 
            -
                      
         | 
| 427 | 
            -
                      info "# Adding public key for #{path}"
         | 
| 428 | 
            -
                      k = Rye::Key.from_file(path).public_key.to_ssh2
         | 
| 429 | 
            -
                      authorized_keys.puts k
         | 
| 430 | 
            -
                    end
         | 
| 431 | 
            -
                    
         | 
| 432 | 
            -
                    # Remove duplicate authorized keys
         | 
| 433 | 
            -
                    authorized_keys.rewind
         | 
| 434 | 
            -
                    uniqlines = authorized_keys.readlines.uniq.join
         | 
| 435 | 
            -
                    authorized_keys = StringIO.new(uniqlines)
         | 
| 436 | 
            -
                    # We need to rewind so that all of the StringIO object is uploaded
         | 
| 437 | 
            -
                    authorized_keys.rewind
         | 
| 438 | 
            -
                    
         | 
| 439 | 
            -
                    full_path = "#{homedir}/#{akey_path}"
         | 
| 440 | 
            -
                    temp_path = "/tmp/rye-#{user}-#{File.basename(akey_path)}"
         | 
| 441 | 
            -
                    
         | 
| 442 | 
            -
                    sudo do
         | 
| 443 | 
            -
                      mkdir :p, :m, '700', File.dirname(akey_path)
         | 
| 444 | 
            -
                      file_upload authorized_keys, temp_path
         | 
| 445 | 
            -
                      mv temp_path, full_path
         | 
| 446 | 
            -
                      chmod '0600', akey_path
         | 
| 447 | 
            -
                      chown :R, this_user.to_s, File.dirname(akey_path)
         | 
| 448 | 
            -
                    end
         | 
| 449 | 
            -
                  end
         | 
| 450 | 
            -
                  
         | 
| 451 | 
            -
                  # And let's return to the directory we came from.
         | 
| 452 | 
            -
                  self.cd prevdir
         | 
| 453 | 
            -
                  
         | 
| 454 | 
            -
                  rap.add_exit_code(0)
         | 
| 455 | 
            -
                  rap
         | 
| 456 | 
            -
                end
         | 
| 457 | 
            -
                require 'fileutils'
         | 
| 458 | 
            -
                # Authorize the current user to login to the local machine via
         | 
| 459 | 
            -
                # SSH without a password. This is the same functionality as
         | 
| 460 | 
            -
                # authorize_keys_remote except run with local shell commands. 
         | 
| 461 | 
            -
                def authorize_keys_local
         | 
| 462 | 
            -
                  added_keys = []
         | 
| 463 | 
            -
                  ssh_dir = File.join(Rye.sysinfo.home, '.ssh')
         | 
| 464 | 
            -
                  Rye.keys.each do |path|
         | 
| 465 | 
            -
                    debug "# Public key for #{path}"
         | 
| 466 | 
            -
                    k = Rye::Key.from_file(path).public_key.to_ssh2
         | 
| 467 | 
            -
                    FileUtils.mkdir ssh_dir unless File.exists? ssh_dir
         | 
| 468 | 
            -
                    
         | 
| 469 | 
            -
                    authkeys_file = File.join(ssh_dir, 'authorized_keys')
         | 
| 470 | 
            -
                    
         | 
| 471 | 
            -
                    debug "Writing to #{authkeys_file}"
         | 
| 472 | 
            -
                    File.open(authkeys_file, 'a')       {|f| f.write("#{$/}#{k}") }
         | 
| 473 | 
            -
                    File.open("#{authkeys_file}2", 'a') {|f| f.write("#{$/}#{k}") }
         | 
| 474 | 
            -
                    
         | 
| 475 | 
            -
                    unless Rye.sysinfo.os == :windows
         | 
| 476 | 
            -
                      Rye.shell(:chmod, '700', ssh_dir)
         | 
| 477 | 
            -
                      Rye.shell(:chmod, '0600', authkeys_file)
         | 
| 478 | 
            -
                      Rye.shell(:chmod, '0600', "#{authkeys_file}2")
         | 
| 479 | 
            -
                    end
         | 
| 480 | 
            -
                    
         | 
| 481 | 
            -
                    added_keys << path
         | 
| 482 | 
            -
                  end
         | 
| 483 | 
            -
                  added_keys
         | 
| 484 | 
            -
                end
         | 
| 485 | 
            -
                
         | 
| 486 412 | 
             
                # A handler for undefined commands. 
         | 
| 487 413 | 
             
                # Raises Rye::CommandNotFound exception.
         | 
| 488 414 | 
             
                def method_missing(cmd, *args, &block)
         | 
| @@ -518,6 +444,17 @@ module Rye | |
| 518 444 | 
             
                  @rye_pre_command_hook
         | 
| 519 445 | 
             
                end
         | 
| 520 446 |  | 
| 447 | 
            +
                # Supply a block to be called every time a command receives STDOUT data.
         | 
| 448 | 
            +
                # 
         | 
| 449 | 
            +
                # e.g.
         | 
| 450 | 
            +
                #     rbox.stdout_hook do |content|
         | 
| 451 | 
            +
                #       ...
         | 
| 452 | 
            +
                #     end
         | 
| 453 | 
            +
                def stdout_hook(&block)
         | 
| 454 | 
            +
                  @rye_stdout_hook = block if block
         | 
| 455 | 
            +
                  @rye_stdout_hook
         | 
| 456 | 
            +
                end
         | 
| 457 | 
            +
                
         | 
| 521 458 | 
             
                # Supply a block to be called whenever there's an Exception. It's called
         | 
| 522 459 | 
             
                # with 1 argument: the exception class. If the exception block returns 
         | 
| 523 460 | 
             
                # :retry, the command will be executed again. 
         | 
| @@ -559,7 +496,7 @@ module Rye | |
| 559 496 | 
             
                # Returns the return value of the block. 
         | 
| 560 497 | 
             
                #
         | 
| 561 498 | 
             
                def batch(*args, &block)
         | 
| 562 | 
            -
                  self.instance_exec | 
| 499 | 
            +
                  self.instance_exec(*args, &block)
         | 
| 563 500 | 
             
                end
         | 
| 564 501 |  | 
| 565 502 | 
             
                # Like batch, except it disables safe mode before executing the block. 
         | 
| @@ -573,6 +510,7 @@ module Rye | |
| 573 510 | 
             
                  @rye_safe = previous_state
         | 
| 574 511 | 
             
                  ret
         | 
| 575 512 | 
             
                end
         | 
| 513 | 
            +
                alias_method :wildly, :unsafely
         | 
| 576 514 |  | 
| 577 515 | 
             
                # See unsafely (except in reverse)
         | 
| 578 516 | 
             
                def safely(*args, &block)
         | 
| @@ -607,7 +545,7 @@ module Rye | |
| 607 545 | 
             
                # command.
         | 
| 608 546 | 
             
                def sudo(*args, &block)
         | 
| 609 547 | 
             
                  if block.nil?
         | 
| 610 | 
            -
                     | 
| 548 | 
            +
                    run_command('sudo', args);
         | 
| 611 549 | 
             
                  else
         | 
| 612 550 | 
             
                    previous_state = @rye_sudo
         | 
| 613 551 | 
             
                    enable_sudo
         | 
| @@ -703,15 +641,19 @@ module Rye | |
| 703 641 | 
             
                def disconnect
         | 
| 704 642 | 
             
                  return unless @rye_ssh && !@rye_ssh.closed?
         | 
| 705 643 | 
             
                  begin
         | 
| 706 | 
            -
                     | 
| 707 | 
            -
                       | 
| 644 | 
            +
                    if @rye_ssh.busy?;
         | 
| 645 | 
            +
                      info "Is something still running? (ctrl-C to exit)"
         | 
| 646 | 
            +
                      Timeout::timeout(10) do
         | 
| 647 | 
            +
                        @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
         | 
| 648 | 
            +
                      end
         | 
| 708 649 | 
             
                    end
         | 
| 650 | 
            +
                    debug "Closing connection to #{@rye_ssh.host}"
         | 
| 651 | 
            +
                    @rye_ssh.close
         | 
| 709 652 | 
             
                  rescue SystemCallError, Timeout::Error => ex
         | 
| 710 | 
            -
                    error "Disconnect timeout | 
| 653 | 
            +
                    error "Disconnect timeout"
         | 
| 654 | 
            +
                  rescue Interrupt
         | 
| 655 | 
            +
                    debug "Exiting..."
         | 
| 711 656 | 
             
                  end
         | 
| 712 | 
            -
                  
         | 
| 713 | 
            -
                  debug "Closing connection to #{@rye_ssh.host}"
         | 
| 714 | 
            -
                  @rye_ssh.close
         | 
| 715 657 | 
             
                end
         | 
| 716 658 |  | 
| 717 659 |  | 
| @@ -751,11 +693,13 @@ module Rye | |
| 751 693 | 
             
                # This method will try to connect to the host automatically
         | 
| 752 694 | 
             
                # but if it fails it will raise a Rye::NotConnected exception. 
         | 
| 753 695 | 
             
                # 
         | 
| 754 | 
            -
                def run_command(*args)
         | 
| 755 | 
            -
                  debug "run_command | 
| 696 | 
            +
                def run_command(*args, &blk)
         | 
| 697 | 
            +
                  debug "run_command"
         | 
| 756 698 |  | 
| 757 699 | 
             
                  cmd, args = prep_args(*args)
         | 
| 758 700 |  | 
| 701 | 
            +
                  #p [:run_command, cmd, blk.nil?]
         | 
| 702 | 
            +
                  
         | 
| 759 703 | 
             
                  connect if !@rye_ssh || @rye_ssh.closed?
         | 
| 760 704 | 
             
                  raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
         | 
| 761 705 |  | 
| @@ -769,7 +713,7 @@ module Rye | |
| 769 713 | 
             
                  # The command will otherwise run in the user's home directory.
         | 
| 770 714 | 
             
                  if @rye_current_working_directory
         | 
| 771 715 | 
             
                    cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
         | 
| 772 | 
            -
                    cmd_internal = [cwd, cmd_internal] | 
| 716 | 
            +
                    cmd_internal = '(%s; %s)' % [cwd, cmd_internal]
         | 
| 773 717 | 
             
                  end
         | 
| 774 718 |  | 
| 775 719 | 
             
                  # ditto (same explanation as cwd)
         | 
| @@ -797,22 +741,24 @@ module Rye | |
| 797 741 | 
             
                    rap = Rye::Rap.new(self)
         | 
| 798 742 | 
             
                    rap.cmd = cmd_clean
         | 
| 799 743 |  | 
| 800 | 
            -
                     | 
| 744 | 
            +
                    channel = net_ssh_exec!(cmd_internal, &blk)
         | 
| 801 745 |  | 
| 802 | 
            -
                     | 
| 803 | 
            -
             | 
| 804 | 
            -
                     | 
| 805 | 
            -
             | 
| 746 | 
            +
                    if channel[:exception]
         | 
| 747 | 
            +
                      rap = channel[:exception].rap
         | 
| 748 | 
            +
                    else
         | 
| 749 | 
            +
                      rap.add_stdout(channel[:stdout].read || '')
         | 
| 750 | 
            +
                      rap.add_stderr(channel[:stderr].read || '')
         | 
| 751 | 
            +
                      rap.add_exit_status(channel[:exit_status])
         | 
| 752 | 
            +
                      rap.exit_signal = channel[:exit_signal]
         | 
| 753 | 
            +
                    end
         | 
| 806 754 |  | 
| 807 | 
            -
                     | 
| 808 | 
            -
                    #info "stderr: #{rap.stderr}"
         | 
| 809 | 
            -
                    #info "exit_code: #{rap.exit_code}"
         | 
| 755 | 
            +
                    debug "RESULT: %s " % [rap.inspect]
         | 
| 810 756 |  | 
| 811 757 | 
             
                    # It seems a convention for various commands to return -1
         | 
| 812 | 
            -
                    # when something only mildly concerning happens. ls even 
         | 
| 813 | 
            -
                    # returns -1 for apparently no reason sometimes.  | 
| 814 | 
            -
                    #  | 
| 815 | 
            -
                    raise Rye:: | 
| 758 | 
            +
                    # when something only mildly concerning happens. (ls even 
         | 
| 759 | 
            +
                    # returns -1 for apparently no reason sometimes). Anyway,
         | 
| 760 | 
            +
                    # the real errors are the ones that are greater than zero.
         | 
| 761 | 
            +
                    raise Rye::Err.new(rap) if rap.exit_status != 0
         | 
| 816 762 |  | 
| 817 763 | 
             
                  rescue Exception => ex
         | 
| 818 764 | 
             
                    return rap if @rye_quiet
         | 
| @@ -826,7 +772,14 @@ module Rye | |
| 826 772 | 
             
                      retry
         | 
| 827 773 | 
             
                    elsif choice == :skip
         | 
| 828 774 | 
             
                      # do nothing
         | 
| 829 | 
            -
                     | 
| 775 | 
            +
                    elsif choice == :interactive && !@rye_shell
         | 
| 776 | 
            +
                      @rye_shell = true
         | 
| 777 | 
            +
                      previous_state = @rye_sudo
         | 
| 778 | 
            +
                      disable_sudo
         | 
| 779 | 
            +
                      bash
         | 
| 780 | 
            +
                      @rye_sudo = previous_state
         | 
| 781 | 
            +
                      @rye_shell = false
         | 
| 782 | 
            +
                    elsif !ex.is_a?(Interrupt)
         | 
| 830 783 | 
             
                      raise ex, ex.message
         | 
| 831 784 | 
             
                    end
         | 
| 832 785 | 
             
                  end
         | 
| @@ -858,88 +811,233 @@ module Rye | |
| 858 811 | 
             
                  [cmd, args]
         | 
| 859 812 | 
             
                end
         | 
| 860 813 |  | 
| 861 | 
            -
                 | 
| 862 | 
            -
             | 
| 863 | 
            -
             | 
| 864 | 
            -
             | 
| 865 | 
            -
             | 
| 866 | 
            -
             | 
| 867 | 
            -
             | 
| 868 | 
            -
             | 
| 869 | 
            -
             | 
| 870 | 
            -
             | 
| 871 | 
            -
             | 
| 814 | 
            +
                def net_ssh_exec!(cmd, &blk)
         | 
| 815 | 
            +
                  debug ":net_ssh_exec #{cmd} (has blk: #{!blk.nil?}; pty: #{@rye_pty}; shell: #{@rye_shell})"
         | 
| 816 | 
            +
                  
         | 
| 817 | 
            +
                  pty_opts =   { :term => "xterm",
         | 
| 818 | 
            +
                                          :chars_wide  => 80,
         | 
| 819 | 
            +
                                          :chars_high  => 24,
         | 
| 820 | 
            +
                                          :pixels_wide => 640,
         | 
| 821 | 
            +
                                          :pixels_high => 480,
         | 
| 822 | 
            +
                                          :modes       => {} }
         | 
| 823 | 
            +
                  
         | 
| 824 | 
            +
                  channel = @rye_ssh.open_channel do |channel|
         | 
| 825 | 
            +
                    if self.rye_shell && blk.nil?
         | 
| 826 | 
            +
                      channel.request_pty(pty_opts) do |ch,success|
         | 
| 827 | 
            +
                        self.rye_pty = success
         | 
| 828 | 
            +
                        raise Rye::NoPty if !success
         | 
| 829 | 
            +
                      end
         | 
| 830 | 
            +
                    end
         | 
| 831 | 
            +
                    channel.exec(cmd, &create_channel)
         | 
| 832 | 
            +
                    channel[:state] = :start_session
         | 
| 833 | 
            +
                    channel[:block] = blk
         | 
| 834 | 
            +
                  end
         | 
| 835 | 
            +
                  
         | 
| 836 | 
            +
                  @rye_channels ||= []
         | 
| 837 | 
            +
                  @rye_channels << channel
         | 
| 838 | 
            +
                  
         | 
| 839 | 
            +
                  @rye_ssh.loop(0.1) do
         | 
| 840 | 
            +
                    break if channel.nil? || !channel.active?
         | 
| 841 | 
            +
                    !channel.eof?   # otherwise keep returning true
         | 
| 842 | 
            +
                  end
         | 
| 843 | 
            +
                  
         | 
| 844 | 
            +
                  channel
         | 
| 845 | 
            +
                end
         | 
| 846 | 
            +
                
         | 
| 847 | 
            +
                
         | 
| 848 | 
            +
                def state_wait_for_command(channel)
         | 
| 849 | 
            +
                  debug :wait_for_command
         | 
| 850 | 
            +
                end
         | 
| 872 851 |  | 
| 873 | 
            -
             | 
| 852 | 
            +
                def state_start_session(channel)
         | 
| 853 | 
            +
                  debug "#{:start_session} [blk: #{!channel[:block].nil?}] [pty: #{@rye_pty}] [shell: #{@rye_shell}]"
         | 
| 854 | 
            +
                  channel[:state] = nil
         | 
| 855 | 
            +
                  channel[:state] = :run_block if channel[:block] 
         | 
| 856 | 
            +
                  channel[:state] = :await_response if @rye_pty
         | 
| 857 | 
            +
                end
         | 
| 858 | 
            +
                
         | 
| 859 | 
            +
                def state_await_response(channel)
         | 
| 860 | 
            +
                  debug :await_response
         | 
| 861 | 
            +
                  @await_response_counter ||= 0
         | 
| 862 | 
            +
                  if channel[:stdout].available > 0 || channel[:stderr].available > 0
         | 
| 863 | 
            +
                    channel[:state] = :read_response
         | 
| 864 | 
            +
                  elsif @await_response_counter > 50
         | 
| 865 | 
            +
                    @await_response_counter = 0
         | 
| 866 | 
            +
                    channel[:state] = :await_input
         | 
| 867 | 
            +
                  end
         | 
| 868 | 
            +
                  @await_response_counter += 1
         | 
| 869 | 
            +
                end
         | 
| 870 | 
            +
                
         | 
| 871 | 
            +
                def state_read_response(channel)
         | 
| 872 | 
            +
                  debug :read_response
         | 
| 873 | 
            +
                  if channel[:stdout].available > 0 || channel[:stderr].available > 0
         | 
| 874 874 |  | 
| 875 | 
            -
                    channel[:stdout]  | 
| 876 | 
            -
                    channel[:stderr]  | 
| 875 | 
            +
                    stdout = channel[:stdout].read if channel[:stdout].available > 0
         | 
| 876 | 
            +
                    stderr = channel[:stderr].read if channel[:stderr].available > 0
         | 
| 877 877 |  | 
| 878 | 
            +
                    print stdout if stdout
         | 
| 879 | 
            +
                    print stderr if stderr
         | 
| 878 880 |  | 
| 879 | 
            -
                    if  | 
| 880 | 
            -
                       | 
| 881 | 
            -
             | 
| 882 | 
            -
                       | 
| 883 | 
            -
             | 
| 884 | 
            -
             | 
| 885 | 
            -
             | 
| 886 | 
            -
             | 
| 887 | 
            -
             | 
| 881 | 
            +
                    if channel[:stack].empty?
         | 
| 882 | 
            +
                      channel[:state] = :await_input
         | 
| 883 | 
            +
                    elsif channel[:stdout].available > 0 || channel[:stderr].available > 0
         | 
| 884 | 
            +
                      channel[:state] = :read_response
         | 
| 885 | 
            +
                    else
         | 
| 886 | 
            +
                      channel[:state] = :send_data
         | 
| 887 | 
            +
                    end
         | 
| 888 | 
            +
                  else
         | 
| 889 | 
            +
                    channel[:state] = :await_response
         | 
| 890 | 
            +
                  end
         | 
| 891 | 
            +
                  
         | 
| 892 | 
            +
                end
         | 
| 893 | 
            +
             | 
| 894 | 
            +
                def state_send_data(channel)
         | 
| 895 | 
            +
                  debug :send_data
         | 
| 896 | 
            +
                  cmd = channel[:stack].shift
         | 
| 897 | 
            +
                  debug "sending #{cmd.inspect}"
         | 
| 898 | 
            +
                  channel[:state] = :await_response
         | 
| 899 | 
            +
                  channel.send_data("#{cmd}\n") unless channel.eof?
         | 
| 900 | 
            +
                end
         | 
| 901 | 
            +
                
         | 
| 902 | 
            +
                def state_await_input(channel)
         | 
| 903 | 
            +
                  debug :await_input
         | 
| 904 | 
            +
                    if channel[:stdout].available > 0
         | 
| 905 | 
            +
                      channel[:state] = :read_response
         | 
| 906 | 
            +
                    else
         | 
| 907 | 
            +
                      ret = nil
         | 
| 908 | 
            +
                      if channel[:prompt] && (channel[:prompt] =~ /pass/i)
         | 
| 909 | 
            +
                        ret = Annoy.get_user_input("#{channel[:prompt]} ", echo='*', period=30)
         | 
| 910 | 
            +
                        channel[:prompt] = nil
         | 
| 888 911 | 
             
                      end
         | 
| 912 | 
            +
                      begin
         | 
| 913 | 
            +
                        list = self.commands.sort
         | 
| 889 914 |  | 
| 890 | 
            -
             | 
| 891 | 
            -
             | 
| 892 | 
            -
             | 
| 893 | 
            -
             | 
| 894 | 
            -
             | 
| 895 | 
            -
             | 
| 896 | 
            -
             | 
| 897 | 
            -
                         | 
| 898 | 
            -
                        ret =  | 
| 899 | 
            -
                         | 
| 900 | 
            -
                         | 
| 901 | 
            -
             | 
| 902 | 
            -
             | 
| 915 | 
            +
                        comp = proc { |s| 
         | 
| 916 | 
            +
                          # TODO: Something here for files
         | 
| 917 | 
            +
                          list.grep( /^#{Regexp.escape(s)}/ ) 
         | 
| 918 | 
            +
                        }
         | 
| 919 | 
            +
             | 
| 920 | 
            +
                        Readline.completion_append_character = " "
         | 
| 921 | 
            +
                        Readline.completion_proc = comp
         | 
| 922 | 
            +
                        
         | 
| 923 | 
            +
                        ret = Readline.readline(channel[:prompt] || '', true)
         | 
| 924 | 
            +
                        #ret = STDIN.gets
         | 
| 925 | 
            +
                        
         | 
| 926 | 
            +
                        if ret.nil?
         | 
| 927 | 
            +
                          channel[:state] = :exit
         | 
| 928 | 
            +
                        else
         | 
| 929 | 
            +
                          channel[:stack] << ret.chomp
         | 
| 930 | 
            +
                          channel[:state] = :send_data
         | 
| 931 | 
            +
                        end
         | 
| 932 | 
            +
                      rescue Interrupt => e
         | 
| 933 | 
            +
                        channel[:state] = :exit
         | 
| 903 934 | 
             
                      end
         | 
| 935 | 
            +
                      channel[:prompt] = nil
         | 
| 904 936 | 
             
                    end
         | 
| 905 | 
            -
             | 
| 937 | 
            +
                end
         | 
| 938 | 
            +
                
         | 
| 939 | 
            +
                def state_ignore_response(channel)
         | 
| 940 | 
            +
                  debug :ignore_response
         | 
| 941 | 
            +
                  @ignore_response_counter ||= 0
         | 
| 942 | 
            +
                  if channel[:stdout].available > 0
         | 
| 943 | 
            +
                    @await_response_counter = 0
         | 
| 944 | 
            +
                    channel[:stdout].read
         | 
| 945 | 
            +
                    channel[:state] = :process
         | 
| 946 | 
            +
                  elsif @ignore_response_counter > 2
         | 
| 947 | 
            +
                    @await_response_counter = 0
         | 
| 948 | 
            +
                    channel[:state] = :process
         | 
| 906 949 | 
             
                  end
         | 
| 907 | 
            -
                  
         | 
| 908 | 
            -
             | 
| 909 | 
            -
             | 
| 910 | 
            -
             | 
| 911 | 
            -
             | 
| 912 | 
            -
             | 
| 950 | 
            +
                  @ignore_response_counter += 1
         | 
| 951 | 
            +
                end
         | 
| 952 | 
            +
                
         | 
| 953 | 
            +
                def state_exit(channel)
         | 
| 954 | 
            +
                  debug :exit_state
         | 
| 955 | 
            +
                  channel[:state] = nil
         | 
| 956 | 
            +
                  if rye_shell && (!channel.eof? || !channel.closing?)
         | 
| 957 | 
            +
                    puts
         | 
| 958 | 
            +
                    channel.send_data("exit\n")
         | 
| 959 | 
            +
                  else
         | 
| 960 | 
            +
                    channel.eof!
         | 
| 913 961 | 
             
                  end
         | 
| 914 | 
            -
             | 
| 915 | 
            -
             | 
| 916 | 
            -
             | 
| 962 | 
            +
                end
         | 
| 963 | 
            +
                
         | 
| 964 | 
            +
                # TODO: implement callback in create_channel Proc
         | 
| 965 | 
            +
                ##def state_handle_error(channel)
         | 
| 966 | 
            +
                ##  debug :handle_error
         | 
| 967 | 
            +
                ##  channel[:state] = nil
         | 
| 968 | 
            +
                ##  if rye_shell && (!channel.eof? || !channel.closing?)
         | 
| 969 | 
            +
                ##    puts
         | 
| 970 | 
            +
                ##    channel.send_data("exit\n")
         | 
| 971 | 
            +
                ##  else
         | 
| 972 | 
            +
                ##    channel.eof!
         | 
| 973 | 
            +
                ##  end
         | 
| 974 | 
            +
                ##end
         | 
| 975 | 
            +
                
         | 
| 976 | 
            +
             | 
| 977 | 
            +
                def state_run_block(channel)
         | 
| 978 | 
            +
                  debug :run_block
         | 
| 979 | 
            +
                  channel[:state] = nil
         | 
| 980 | 
            +
                  blk = channel[:block]
         | 
| 981 | 
            +
                  channel[:block] = nil
         | 
| 982 | 
            +
                  begin
         | 
| 983 | 
            +
                    instance_eval &blk
         | 
| 984 | 
            +
                  rescue => ex
         | 
| 985 | 
            +
                    channel[:exception] = ex
         | 
| 917 986 | 
             
                  end
         | 
| 918 | 
            -
                  
         | 
| 919 | 
            -
             | 
| 920 | 
            -
             | 
| 921 | 
            -
             | 
| 987 | 
            +
                  channel[:state] = :exit
         | 
| 988 | 
            +
                end
         | 
| 989 | 
            +
                
         | 
| 990 | 
            +
                def create_channel()
         | 
| 991 | 
            +
                  Proc.new do |channel,success|
         | 
| 992 | 
            +
                    channel[:stdout  ] = Net::SSH::Buffer.new
         | 
| 993 | 
            +
                    channel[:stderr  ] = Net::SSH::Buffer.new
         | 
| 994 | 
            +
                    channel[:stack] ||= []
         | 
| 995 | 
            +
                    channel.on_close                  { |ch|  
         | 
| 996 | 
            +
                      channel[:handler] = ":on_close"
         | 
| 997 | 
            +
                    }
         | 
| 998 | 
            +
                    channel.on_data                   { |ch, data| 
         | 
| 999 | 
            +
                      channel[:handler] = ":on_data"
         | 
| 1000 | 
            +
                      @rye_stdout_hook.call(data) if !@rye_pty && !@rye_quiet && @rye_stdout_hook.kind_of?(Proc)
         | 
| 1001 | 
            +
                      if rye_pty && data =~ /password/i
         | 
| 1002 | 
            +
                        channel[:prompt] = data
         | 
| 1003 | 
            +
                        channel[:state] = :await_input
         | 
| 1004 | 
            +
                      else
         | 
| 1005 | 
            +
                        channel[:stdout].append(data) 
         | 
| 1006 | 
            +
                      end
         | 
| 1007 | 
            +
                    }
         | 
| 1008 | 
            +
                    channel.on_extended_data          { |ch, type, data| 
         | 
| 1009 | 
            +
                      channel[:handler] = ":on_extended_data"
         | 
| 1010 | 
            +
                      if rye_pty && data =~ /\Apassword/i
         | 
| 1011 | 
            +
                        channel[:prompt] = data
         | 
| 1012 | 
            +
                        channel[:state] = :await_input
         | 
| 1013 | 
            +
                      else
         | 
| 1014 | 
            +
                        channel[:stderr].append(data)
         | 
| 1015 | 
            +
                      end
         | 
| 1016 | 
            +
                    }
         | 
| 1017 | 
            +
                    channel.on_request("exit-status") { |ch, data| 
         | 
| 1018 | 
            +
                      channel[:handler] = ":on_request (exit-status)"
         | 
| 1019 | 
            +
                      channel[:exit_status] = data.read_long 
         | 
| 1020 | 
            +
                    }
         | 
| 1021 | 
            +
                    channel.on_request("exit-signal") do |ch, data|
         | 
| 1022 | 
            +
                      channel[:handler] = ":on_request (exit-signal)"
         | 
| 1023 | 
            +
                      # This should be the POSIX SIGNAL that ended the process
         | 
| 1024 | 
            +
                      channel[:exit_signal] = data.read_long
         | 
| 1025 | 
            +
                    end
         | 
| 1026 | 
            +
                    channel.on_process                { 
         | 
| 1027 | 
            +
                      channel[:handler] = :on_process
         | 
| 1028 | 
            +
                      print channel[:stderr].read if channel[:stderr].available > 0
         | 
| 1029 | 
            +
                      begin
         | 
| 1030 | 
            +
                        send("state_#{channel[:state]}", channel) unless channel[:state].nil?
         | 
| 1031 | 
            +
                      rescue Interrupt
         | 
| 1032 | 
            +
                        debug :on_process_interrupt
         | 
| 1033 | 
            +
                        channel[:state] = :exit
         | 
| 1034 | 
            +
                      end
         | 
| 1035 | 
            +
                    }
         | 
| 922 1036 | 
             
                  end
         | 
| 923 | 
            -
                  
         | 
| 924 | 
            -
                  ## I'm getting weird behavior with exit codes. Sometimes
         | 
| 925 | 
            -
                  ## a command which usually returns an exit code will not
         | 
| 926 | 
            -
                  ## return one the next time it's run. The following crap
         | 
| 927 | 
            -
                  ## was from the debugging.
         | 
| 928 | 
            -
                  ##Kernel.sleep 5
         | 
| 929 | 
            -
                  ###channel.close
         | 
| 930 | 
            -
                  #channel.eof!
         | 
| 931 | 
            -
                  ##p [:active, channel.active?]
         | 
| 932 | 
            -
                  ##p [:closing, channel.closing?]
         | 
| 933 | 
            -
                  ##p [:eof, channel.eof?]
         | 
| 934 | 
            -
                  
         | 
| 935 | 
            -
                  channel[:exit_code] ||= 0
         | 
| 936 | 
            -
                  channel[:exit_code] &&= channel[:exit_code].to_i
         | 
| 937 | 
            -
                  channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]
         | 
| 938 | 
            -
                  
         | 
| 939 | 
            -
                  [channel[:stdout], channel[:stderr], channel[:exit_code], channel[:exit_signal]]
         | 
| 940 1037 | 
             
                end
         | 
| 941 1038 |  | 
| 942 1039 |  | 
| 1040 | 
            +
                
         | 
| 943 1041 | 
             
                # * +direction+ is one of :upload, :download
         | 
| 944 1042 | 
             
                # * +recursive+ should be true for directories and false for files. 
         | 
| 945 1043 | 
             
                # * +files+ is an Array of file paths, the content is direction specific.
         | 
| @@ -978,6 +1076,7 @@ module Rye | |
| 978 1076 | 
             
                    end
         | 
| 979 1077 |  | 
| 980 1078 | 
             
                  elsif direction == :upload
         | 
| 1079 | 
            +
            #        p :UPLOAD, @rye_templates
         | 
| 981 1080 | 
             
                    raise "Cannot upload to a StringIO object" if target.is_a?(StringIO)
         | 
| 982 1081 | 
             
                    if files.size == 1
         | 
| 983 1082 | 
             
                      target = self.getenv['HOME'] || guess_user_home
         | 
| @@ -989,7 +1088,9 @@ module Rye | |
| 989 1088 | 
             
                    # Expand fileglobs (e.g. path/*.rb becomes [path/1.rb, path/2.rb]).
         | 
| 990 1089 | 
             
                    # This should happen after checking files.size to determine the target
         | 
| 991 1090 | 
             
                    unless @rye_safe
         | 
| 992 | 
            -
                      files.collect! { |file|  | 
| 1091 | 
            +
                      files.collect! { |file| 
         | 
| 1092 | 
            +
                        file.is_a?(StringIO) ? file : Dir.glob(File.expand_path(file)) 
         | 
| 1093 | 
            +
                      }
         | 
| 993 1094 | 
             
                      files.flatten! 
         | 
| 994 1095 | 
             
                    end
         | 
| 995 1096 | 
             
                  end
         | 
| @@ -1004,7 +1105,6 @@ module Rye | |
| 1004 1105 | 
             
                    end
         | 
| 1005 1106 | 
             
                  end
         | 
| 1006 1107 |  | 
| 1007 | 
            -
                  info "#{direction.to_s} to: #{target}"
         | 
| 1008 1108 | 
             
                  debug "FILES: " << files.join(', ')
         | 
| 1009 1109 |  | 
| 1010 1110 | 
             
                  # Make sure the target directory exists. We can do this only when
         | 
| @@ -1020,22 +1120,16 @@ module Rye | |
| 1020 1120 | 
             
                    files.each do |file|
         | 
| 1021 1121 | 
             
                      debug file.to_s
         | 
| 1022 1122 | 
             
                      prev = ""
         | 
| 1023 | 
            -
                       | 
| 1024 | 
            -
                        tmp_file = file.is_a?(StringIO) ? '<string>' : file
         | 
| 1025 | 
            -
                        tmp_target = target.is_a?(StringIO) ? '<string>' : target
         | 
| 1026 | 
            -
                        STDOUT.puts "[#{direction}] #{tmp_file} #{tmp_target}" 
         | 
| 1027 | 
            -
                      end
         | 
| 1123 | 
            +
                      line = nil
         | 
| 1028 1124 | 
             
                      transfers << scp.send(direction, file, target, :recursive => recursive)  do |ch, n, s, t|
         | 
| 1029 1125 | 
             
                        line = "%-50s %6d/%-6d bytes" % [n, s, t]
         | 
| 1030 1126 | 
             
                        spaces = (prev.size > line.size) ? ' '*(prev.size - line.size) : ''
         | 
| 1031 | 
            -
                        pinfo "%s %s  | 
| 1127 | 
            +
                        pinfo "[%s] %s %s %s" % [direction, line, spaces, s == t ? "\n" : "\r"]   # update line: "file: sent/total"
         | 
| 1032 1128 | 
             
                        @rye_info.flush if @rye_info        # make sure every line is printed
         | 
| 1033 1129 | 
             
                        prev = line
         | 
| 1034 1130 | 
             
                      end
         | 
| 1035 1131 | 
             
                    end
         | 
| 1036 1132 | 
             
                    transfers.each { |t| t.wait }   # Run file transfers in parallel
         | 
| 1037 | 
            -
                    pinfo (' '*prev.size) << "\r"
         | 
| 1038 | 
            -
                    info $/
         | 
| 1039 1133 | 
             
                  end
         | 
| 1040 1134 |  | 
| 1041 1135 | 
             
                  target.is_a?(StringIO) ? target : nil
         |