lib/rye/box.rb in rye-0.9.2 vs lib/rye/box.rb in rye-0.9.3

- old
+ new

@@ -1,5 +1,7 @@ +# vim: set sw=2 ts=2 : + require 'annoy' require 'readline' module Rye DEBUG = false unless defined?(Rye::DEBUG) @@ -48,14 +50,16 @@ def sudo?; @rye_sudo == true end # Returns the current value of the stash +@rye_stash+ def stash; @rye_stash; end def quiet; @rye_quiet; end + def via; @rye_via; end def nickname; @rye_nickname || host; end def host=(val); @rye_host = val; end def opts=(val); @rye_opts = val; end + def via=(val); @rye_via = val; end # Store a value to the stash +@rye_stash+ def stash=(val); @rye_stash = val; end def nickname=(val); @rye_nickname = val; end @@ -70,10 +74,11 @@ 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 via?; !@rye_via.nil?; 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 @@ -91,10 +96,11 @@ # The +opts+ hash excepts the following keys: # # * :safe => should Rye be safe? Default: true # * :port => remote server ssh port. Default: SSH config file or 22 # * :keys => one or more private key file paths (passwordless login) + # * :via => the Rye::Hop to access this host through # * :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) @@ -118,10 +124,11 @@ # These opts are use by Rye::Box and also passed to Net::SSH @rye_opts = { :safe => true, :port => ssh_opts[:port], :keys => Rye.keys, + :via => nil, :info => nil, :debug => nil, :error => STDERR, :getenv => true, :templates => :erb, @@ -129,10 +136,13 @@ }.merge(opts) # Close the SSH session before Ruby exits. This will do nothing # if disconnect has already been called explicitly. at_exit { self.disconnect } + + # Properly handle whether the opt :via is a +Rye::Hop+ or a +String+ + via_hop(@rye_opts.delete(:via)) # @rye_opts gets sent to Net::SSH so we need to remove the keys # that are not meant for it. @rye_safe, @rye_debug = @rye_opts.delete(:safe), @rye_opts.delete(:debug) @rye_info, @rye_error = @rye_opts.delete(:info), @rye_opts.delete(:error) @@ -147,11 +157,11 @@ 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[: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 @@ -176,10 +186,43 @@ # Parse SSH config files for use with Net::SSH def ssh_config_options(host) return Net::SSH::Config.for(host) end + # * +hops+ Rye::Hop objects will be added directly + # to the set. Hostnames will be used to create new instances of Rye::Hop + # h1 = Rye::Hop.new "host1" + # h1.via_hop "host2", :user => "service_user" + # + # OR + # + # h1 = Rye::Hop.new "host1" + # h2 = Rye::Hop.new "host2" + # h1.via_hop h2 + # + def via_hop(*args) + args = args.flatten.compact + if args.first.nil? + return @rye_via + elsif args.first.is_a?(Rye::Hop) + @rye_via = args.first + elsif args.first.is_a?(String) + hop = args.shift + if args.first.is_a?(Hash) + @rye_via = Rye::Hop.new(hop, args.first.merge( + :debug => @rye_debug, + :info => @rye_info, + :error => @rye_error) + ) + else + @rye_via = Rye::Hop.new(hop) + end + end + disconnect + self + end + # Change the current working directory (sort of). # # I haven't been able to wrangle Net::SSH to do my bidding. # "My bidding" in this case, is maintaining an open channel between commands. # I'm using Net::SSH::Connection::Session#exec for all commands @@ -600,16 +643,28 @@ # connected. def connect(reconnect=true) raise Rye::NoHost unless @rye_host return if @rye_ssh && !reconnect disconnect if @rye_ssh - debug "Opening connection to #{@rye_host} as #{@rye_user}" + if @rye_via + debug "Opening connection to #{@rye_host} as #{@rye_user}, via #{@rye_via.host}" + else + debug "Opening connection to #{@rye_host} as #{@rye_user}" + end highline = HighLine.new # Used for password prompt retried = 0 @rye_opts[:keys].compact! # A quick fix in Windows. TODO: Why is there a nil? begin - @rye_ssh = Net::SSH.start(@rye_host, @rye_user, @rye_opts || {}) + if @rye_via + # tell the +Rye::Hop+ what and where to setup, + # it returns the local port used + @rye_localport = @rye_via.fetch_port(@rye_host, @rye_opts[:port].nil? ? 22 : @rye_opts[:port] ) + debug "fetched localport #{@rye_localport}" + @rye_ssh = Net::SSH.start("localhost", @rye_user, @rye_opts.merge(:port => @rye_localport) || {}) + else + @rye_ssh = Net::SSH.start(@rye_host, @rye_user, @rye_opts || {}) + end rescue Net::SSH::HostKeyMismatch => ex STDERR.puts ex.message print "\a" if @rye_info # Ring the bell if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i) @rye_opts[:paranoid] = false @@ -651,11 +706,16 @@ @rye_ssh.loop(0.3) { @rye_ssh.busy?; } end end debug "Closing connection to #{@rye_ssh.host}" @rye_ssh.close + if @rye_via + debug "disconnecting Hop #{@rye_via.host}" + @rye_via.disconnect + end rescue SystemCallError, Timeout::Error => ex - error "Disconnect timeout" + error "Rye::Box: Disconnect timeout (#{ex.message})" + debug ex.backtrace rescue Interrupt debug "Exiting..." end end