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')
Methods
Public Class
Public Instance
Constants
MAX_PORT | = | 65535 |
The maximum port number that the gateway will attempt to use to forward connections from. |
|
MIN_PORT | = | 1024 |
The minimum port number that the gateway will attempt to use to forward connections from. |
Public Instance Aliases
add_key | -> | add_keys |
remove_key | -> | remove_keys |
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 = `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/) @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
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
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
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 350 def inspect %{#<%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
# File lib/rye/hop.rb, line 47 def nickname=(val); @rye_nickname = 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
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
-
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
# 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