class Rye::Hop
Rye::Hop¶ ↑
The Rye::Hop class represents a machine. This class allows boxes to by accessed via it.
rhop = Rye::Hop.new('firewall.lan') rbox = Rye::Box.new('filibuster', :via => rhop) rbox.uptime # => 20:53 up 1 day, 1:52, 4 users
Or
rbox = Rye::Box.new('filibuster', :via => 'firewall.lan')
Constants
- MAX_PORT
The maximum port number that the gateway will attempt to use to forward connections from.
- MIN_PORT
The minimum port number that the gateway will attempt to use to forward connections from.
Public Class Methods
-
host
The hostname to connect to. Default: localhost. -
user
The username to connect as. Default: SSH config file or current shell user. -
opts
a hash of optional arguments.
The opts
hash excepts the following keys:
-
: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)
-
: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.
# File lib/rye/hop.rb, line 81 def initialize(host, opts={}) ssh_opts = ssh_config_options(host) @rye_exception_hook = {} @rye_host = host if opts[:user] @rye_user = opts[:user] else @rye_user = ssh_opts[:user] || Rye.sysinfo.user end # These opts are use by Rye::Box and also passed to # Net::SSH::Gateway (and Net::SSH) @rye_opts = { :port => ssh_opts[:port], :keys => Rye.keys, :via => nil, :info => nil, :debug => nil, :error => STDERR, :getenv => true, :templates => :erb, :quiet => false }.merge(opts) @next_port = MAX_PORT # 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+ # and does nothing if nil 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) @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) # Store the state of the terminal @rye_stty_save = %x`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[: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]) # From: capistrano/lib/capistrano/cli.rb STDOUT.sync = true # so that Net::SSH prompts show up debug "ssh-agent info: #{Rye.sshagent_info.inspect}" debug @rye_opts.inspect end
Public Instance Methods
Compares itself with the other
box. If the hostnames are the
same, this will return true. Otherwise false.
# File lib/rye/hop.rb, line 361 def ==(other) @rye_host == other.host end
Add one or more private keys to the list of key paths.
-
keys
is a list of file paths to private keys
Returns the instance of Box
# File lib/rye/hop.rb, line 204 def add_keys(*keys) @rye_opts[:keys] ||= [] @rye_opts[:keys] += keys.flatten.compact @rye_opts[:keys].uniq! self # MUST RETURN self end
Open an SSH session with +@rye_host+. This called automatically when you the first comamnd is run if it's not already connected. Raises a Rye::NoHost exception if +@rye_host+ is not specified. Will attempt a password login up to 3 times if the initial authentication fails.
-
reconnect
Disconnect first if already connected. The default
is true. When set to false, connect will do nothing if already connected.
# File lib/rye/hop.rb, line 246 def connect(reconnect=true) raise Rye::NoHost unless @rye_host return if @rye_ssh && !reconnect disconnect if @rye_ssh 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 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] ) @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 debug "starting the port forward thread" port_loop 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 retry else raise ex end rescue Net::SSH::AuthenticationFailed => ex print "\a" if retried == 0 && @rye_info # Ring the bell once retried += 1 if STDIN.tty? && retried <= 3 STDERR.puts "Passwordless login failed for #{@rye_user}" @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }.strip @rye_opts[:auth_methods] ||= [] @rye_opts[:auth_methods].push *['keyboard-interactive', 'password'] retry else raise ex end end # We add :auth_methods (a Net::SSH joint) to force asking for a # password if the initial (key-based) authentication fails. We # need to delete the key from @rye_opts otherwise it lingers until # the next connection (if we switch_user is called for example). @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods) self end
# File lib/rye/hop.rb, line 54 def debug?; !@rye_debug.nil?; end
Close the SSH session with +@rye_host+. This is called automatically at exit if the connection is open.
# File lib/rye/hop.rb, line 317 def disconnect return unless @rye_ssh && !@rye_ssh.closed? begin debug "removing active forwards" remove_hops! debug "killing port_loop @rye_port_thread" @rye_port_thread.kill 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 if @rye_via debug "disconnecting Hop #{@rye_via.host}" @rye_via.disconnect end rescue SystemCallError, Timeout::Error => ex error "Rye::Hop: Disconnect timeout (#{ex.message})" debug ex.backtrace rescue Interrupt debug "Exiting..." end end
# File lib/rye/hop.rb, line 55 def error?; !@rye_error.nil?; end
A Hash. The keys are exception classes, the values are Procs to execute
# File lib/rye/hop.rb, line 59 def exception_hook=(val); @rye_exception_hook = val; end
instance method, that will setup a forward, and return the port used
# File lib/rye/hop.rb, line 181 def fetch_port(host, port = 22, localport = nil) connect unless @rye_ssh if localport.nil? port_used = next_port else port_used = localport end # i would like to check if the port and host # are already an active_locals forward, but that # info does not get returned, and trusting the localport # is not enough information, so lets just set up a new one @rye_ssh.forward.local(port_used, host, port) return port_used end
# File lib/rye/hop.rb, line 39 def host; @rye_host; end
# File lib/rye/hop.rb, line 48 def host=(val); @rye_host = val; end
Returns the host SSH keys for this box
# File lib/rye/hop.rb, line 366 def host_key raise "No host" unless @rye_host Rye.remote_host_keys(@rye_host) end
# File lib/rye/hop.rb, line 53 def info?; !@rye_info.nil?; end
# File lib/rye/hop.rb, line 350 def inspect %q{#<%s:%s name=%s cwd=%s umask=%s env=%s via=%s opts=%s keys=%s>} % [self.class.to_s, self.host, self.nickname, @rye_current_working_directory, @rye_current_umask, (@rye_current_environment_variables || '').inspect, (@rye_via || '').inspect, self.opts.inspect, self.keys.inspect] end
See Rye#keys
# File lib/rye/hop.rb, line 345 def keys; Rye.keys; end
# File lib/rye/hop.rb, line 44 def nickname; @rye_nickname || host; end
# File lib/rye/hop.rb, line 47 def nickname=(val); @rye_nickname = val; end
# File lib/rye/hop.rb, line 40 def opts; @rye_opts; end
# File lib/rye/hop.rb, line 49 def opts=(val); @rye_opts = val; end
Cancel the port forward on all active local forwards
# File lib/rye/hop.rb, line 302 def remove_hops! return unless @rye_ssh && @rye_ssh.forward.active_locals.count > 0 @rye_ssh.forward.active_locals.each {|fport, fhost| @rye_ssh.forward.cancel_local(fport, fhost) } if !@rye_ssh.channels.empty? @rye_ssh.channels.each {|channel| channel[-1].close } end return @rye_ssh.forward.active_locals.count end
Remove one or more private keys fromt he list of key paths.
-
keys
is a list of file paths to private keys
Returns the instance of Box
# File lib/rye/hop.rb, line 215 def remove_keys(*keys) @rye_opts[:keys] ||= [] @rye_opts[:keys] -= keys.flatten.compact @rye_opts[:keys].uniq! self # MUST RETURN self end
# File lib/rye/hop.rb, line 42 def root?; user.to_s == "root" end
Parse SSH config files for use with Net::SSH
# File lib/rye/hop.rb, line 197 def ssh_config_options(host) return Net::SSH::Config.for(host) end
Reconnect as another user. This is different from su= which executes subsequent commands via +su -c COMMAND USER+.
-
newuser
The username to reconnect as
NOTE: if there is an open connection, it's disconnected but not reconnected because it's possible it wasn't connected yet in the first place (if you create the instance with default settings for example)
# File lib/rye/hop.rb, line 231 def switch_user(newuser) return if newuser.to_s == self.user.to_s @rye_opts ||= {} @rye_user = newuser disconnect end
Returns +user@rye_host+
# File lib/rye/hop.rb, line 348 def to_s; '%s@rye_%s' % [user, @rye_host]; end
# File lib/rye/hop.rb, line 41 def user; @rye_user; end
# File lib/rye/hop.rb, line 45 def via; @rye_via; end
# File lib/rye/hop.rb, line 52 def via?; !@rye_via.nil?; end
-
hops
Rye::Hop objects will be added directly
to the set. Hostnames will be used to create new instances of Rye::Hop h1 = ::new “host1” h1.via_hop “host2”, :user => “service_user”
OR
h1 = ::new “host1” h2 = ::new “host2” h1.via_hop h2
# File lib/rye/hop.rb, line 161 def via_hop(*hops) hops = hops.flatten.compact if hops.first.nil? return @rye_via elsif hops.first.is_a?(Rye::Hop) @rye_via = hops.first elsif hops.first.is_a?(String) hop = hops.shift if hops.first.is_a?(Hash) @rye_via = Rye::Hop.new(hop, hops.first) else @rye_via = Rye::Hop.new(hop) end end disconnect self end
Private Instance Methods
# File lib/rye/hop.rb, line 402 def debug(msg="unknown debug msg"); @rye_debug.puts msg if @rye_debug; end
# File lib/rye/hop.rb, line 403 def error(msg="unknown error msg"); @rye_error.puts msg if @rye_error; end
# File lib/rye/hop.rb, line 405 def info(msg="unknown info msg"); @rye_info.puts msg if @rye_info; end
Grabs the next available port number and returns it.
# File lib/rye/hop.rb, line 386 def next_port port = @next_port @next_port -= 1 @next_port = MAX_PORT if @next_port < MIN_PORT # check if the port is in use, if so get the next_port begin TCPSocket.new '127.0.0.1', port rescue Errno::EADDRINUSE next_port() rescue Errno::ECONNREFUSED port else next_port() end end
# File lib/rye/hop.rb, line 404 def pinfo(msg="unknown info msg"); @rye_info.print msg if @rye_info; end
Kicks off the thread that maintains the forwards if additional +Rye::Box+es
add this Rye::Hop
as their via, it'll keep on trucking
# File lib/rye/hop.rb, line 375 def port_loop connect unless @rye_ssh @active = true @rye_port_thread = Thread.new do while @active @rye_ssh.process(0.1) end end end