Class: Rye::Hop
- Inherits:
-
Object
- Object
- Rye::Hop
- Defined in:
- lib/rye/hop.rb
Overview
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')
–
-
When anything confusing happens, enable debug in initialize
by passing :debug => STDERR. This will output Rye debug info as well as Net::SSH info. This is VERY helpful for figuring out why some command is hanging or otherwise acting weird.
-
If a remote command is hanging, it’s probably because a
Net::SSH channel is waiting on_extended_data (a prompt). ++
Constant Summary collapse
- MAX_PORT =
The maximum port number that the gateway will attempt to use to forward connections from.
65535- MIN_PORT =
The minimum port number that the gateway will attempt to use to forward connections from.
1024
Instance Method Summary collapse
-
#==(other) ⇒ Object
Compares itself with the
otherbox. -
#add_keys(*keys) ⇒ Object
(also: #add_key)
Add one or more private keys to the list of key paths.
-
#connect(reconnect = true) ⇒ Object
Open an SSH session with @rye_host.
- #debug? ⇒ Boolean
-
#disconnect ⇒ Object
Close the SSH session with @rye_host.
- #error? ⇒ Boolean
-
#exception_hook=(val) ⇒ Object
A Hash.
-
#fetch_port(host, port = 22, localport = nil) ⇒ Object
instance method, that will setup a forward, and return the port used.
- #host ⇒ Object
- #host=(val) ⇒ Object
-
#host_key ⇒ Object
Returns the host SSH keys for this box.
- #info? ⇒ Boolean
-
#initialize(host, opts = {}) ⇒ Hop
constructor
-
hostThe hostname to connect to.
-
- #inspect ⇒ Object
-
#keys ⇒ Object
See Rye.keys.
- #nickname ⇒ Object
- #nickname=(val) ⇒ Object
- #opts ⇒ Object
- #opts=(val) ⇒ Object
-
#remove_hops! ⇒ Object
Cancel the port forward on all active local forwards.
-
#remove_keys(*keys) ⇒ Object
(also: #remove_key)
Remove one or more private keys fromt he list of key paths.
- #root? ⇒ Boolean
-
#ssh_config_options(host) ⇒ Object
Parse SSH config files for use with Net::SSH.
-
#switch_user(newuser) ⇒ Object
Reconnect as another user.
-
#to_s ⇒ Object
Returns user@rye_host.
- #user ⇒ Object
- #via ⇒ Object
- #via? ⇒ Boolean
-
#via_hop(*hops) ⇒ Object
-
hopsRye::Hop objects will be added directly to the set.
-
Constructor Details
#initialize(host, opts = {}) ⇒ Hop
-
hostThe hostname to connect to. Default: localhost. -
userThe username to connect as. Default: SSH config file or current shell user. -
optsa 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
hostenvironment 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.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/rye/hop.rb', line 81 def initialize(host, opts={}) ssh_opts = (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 |
Instance Method Details
#==(other) ⇒ Object
Compares itself with the other box. If the hostnames are the same, this will return true. Otherwise false.
361 362 363 |
# File 'lib/rye/hop.rb', line 361 def ==(other) @rye_host == other.host end |
#add_keys(*keys) ⇒ Object Also known as: add_key
Add one or more private keys to the list of key paths.
-
keysis a list of file paths to private keys
Returns the instance of Box
204 205 206 207 208 209 |
# 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 |
#connect(reconnect = true) ⇒ Object
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.
-
reconnectDisconnect first if already connected. The default
is true. When set to false, connect will do nothing if already connected.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# 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. 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 |
#debug? ⇒ Boolean
54 |
# File 'lib/rye/hop.rb', line 54 def debug?; !@rye_debug.nil?; end |
#disconnect ⇒ Object
Close the SSH session with @rye_host. This is called automatically at exit if the connection is open.
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# 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.})" debug ex.backtrace rescue Interrupt debug "Exiting..." end end |
#error? ⇒ Boolean
55 |
# File 'lib/rye/hop.rb', line 55 def error?; !@rye_error.nil?; end |
#exception_hook=(val) ⇒ Object
A Hash. The keys are exception classes, the values are Procs to execute
59 |
# File 'lib/rye/hop.rb', line 59 def exception_hook=(val); @rye_exception_hook = val; end |
#fetch_port(host, port = 22, localport = nil) ⇒ Object
instance method, that will setup a forward, and return the port used
181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# 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 |
#host ⇒ Object
39 |
# File 'lib/rye/hop.rb', line 39 def host; @rye_host; end |
#host=(val) ⇒ Object
48 |
# File 'lib/rye/hop.rb', line 48 def host=(val); @rye_host = val; end |
#host_key ⇒ Object
Returns the host SSH keys for this box
366 367 368 369 |
# File 'lib/rye/hop.rb', line 366 def host_key raise "No host" unless @rye_host Rye.remote_host_keys(@rye_host) end |
#info? ⇒ Boolean
53 |
# File 'lib/rye/hop.rb', line 53 def info?; !@rye_info.nil?; end |
#inspect ⇒ Object
350 351 352 353 354 355 356 357 |
# 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 |
#nickname ⇒ Object
44 |
# File 'lib/rye/hop.rb', line 44 def nickname; @rye_nickname || host; end |
#nickname=(val) ⇒ Object
47 |
# File 'lib/rye/hop.rb', line 47 def nickname=(val); @rye_nickname = val; end |
#opts ⇒ Object
40 |
# File 'lib/rye/hop.rb', line 40 def opts; @rye_opts; end |
#opts=(val) ⇒ Object
49 |
# File 'lib/rye/hop.rb', line 49 def opts=(val); @rye_opts = val; end |
#remove_hops! ⇒ Object
Cancel the port forward on all active local forwards
302 303 304 305 306 307 308 309 310 311 312 313 |
# 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_keys(*keys) ⇒ Object Also known as: remove_key
Remove one or more private keys fromt he list of key paths.
-
keysis a list of file paths to private keys
Returns the instance of Box
215 216 217 218 219 220 |
# 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 |
#root? ⇒ Boolean
42 |
# File 'lib/rye/hop.rb', line 42 def root?; user.to_s == "root" end |
#ssh_config_options(host) ⇒ Object
Parse SSH config files for use with Net::SSH
197 198 199 |
# File 'lib/rye/hop.rb', line 197 def (host) return Net::SSH::Config.for(host) end |
#switch_user(newuser) ⇒ Object
Reconnect as another user. This is different from su= which executes subsequent commands via su -c COMMAND USER.
-
newuserThe 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)
231 232 233 234 235 236 |
# 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 |
#to_s ⇒ Object
Returns user@rye_host
348 |
# File 'lib/rye/hop.rb', line 348 def to_s; '%s@rye_%s' % [user, @rye_host]; end |
#user ⇒ Object
41 |
# File 'lib/rye/hop.rb', line 41 def user; @rye_user; end |
#via ⇒ Object
45 |
# File 'lib/rye/hop.rb', line 45 def via; @rye_via; end |
#via? ⇒ Boolean
52 |
# File 'lib/rye/hop.rb', line 52 def via?; !@rye_via.nil?; end |
#via_hop(*hops) ⇒ Object
-
hopsRye::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
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# 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 |