lib/capistrano/gateway.rb in capistrano-1.4.2 vs lib/capistrano/gateway.rb in capistrano-2.0.0

- old
+ new

@@ -1,99 +1,124 @@ +if RUBY_VERSION == "1.8.6" + begin + require 'fastthread' + rescue LoadError + warn "You are running Ruby 1.8.6, which has a bug in its threading implementation." + warn "You are liable to encounter deadlocks running Capistrano, unless you install" + warn "the fastthread library, which is available as a gem:" + warn " gem install fastthread" + end +end + require 'thread' +require 'capistrano/errors' require 'capistrano/ssh' +require 'capistrano/server_definition' Thread.abort_on_exception = true module Capistrano # Black magic. It uses threads and Net::SSH to set up a connection to a # gateway server, through which connections to other servers may be # tunnelled. # - # It is used internally by Actor, but may be useful on its own, as well. + # It is used internally by Capistrano, but may be useful on its own, as well. # # Usage: # - # config = Capistrano::Configuration.new - # gateway = Capistrano::Gateway.new('gateway.example.com', config) + # gateway = Capistrano::Gateway.new(Capistrano::ServerDefinition.new('gateway.example.com')) # - # sess1 = gateway.connect_to('hidden.example.com') - # sess2 = gateway.connect_to('other.example.com') + # sess1 = gateway.connect_to(Capistrano::ServerDefinition.new('hidden.example.com')) + # sess2 = gateway.connect_to(Capistrano::ServerDefinition.new('other.example.com')) class Gateway - # The thread inside which the gateway connection itself is running. + # The Thread instance driving the gateway connection. attr_reader :thread # The Net::SSH session representing the gateway connection. attr_reader :session MAX_PORT = 65535 MIN_PORT = 1024 - def initialize(server, config) #:nodoc: - @config = config + def initialize(server, options={}) #:nodoc: + @options = options @next_port = MAX_PORT @terminate_thread = false @port_guard = Mutex.new mutex = Mutex.new waiter = ConditionVariable.new - @thread = Thread.new do - @config.logger.trace "starting connection to gateway #{server}" - SSH.connect(server, @config) do |@session| - @config.logger.trace "gateway connection established" - mutex.synchronize { waiter.signal } - @session.loop { !@terminate_thread } + mutex.synchronize do + @thread = Thread.new do + logger.trace "starting connection to gateway `#{server}'" if logger + SSH.connect(server, @options) do |@session| + logger.trace "gateway connection established" if logger + mutex.synchronize { waiter.signal } + @session.loop do + !@terminate_thread + end + end end - end - mutex.synchronize { waiter.wait(mutex) } + waiter.wait(mutex) + end end # Shuts down all forwarded connections and terminates the gateway. def shutdown! # cancel all active forward channels - @session.forward.active_locals.each do |lport, host, port| - @session.forward.cancel_local(lport) + session.forward.active_locals.each do |lport, host, port| + session.forward.cancel_local(lport) end # terminate the gateway thread @terminate_thread = true # wait for the gateway thread to stop - @thread.join + thread.join end # Connects to the given server by opening a forwarded port from the local # host to the server, via the gateway, and then opens and returns a new # Net::SSH connection via that port. def connect_to(server) connection = nil - @config.logger.trace "establishing connection to #{server} via gateway" + logger.debug "establishing connection to `#{server}' via gateway" if logger local_port = next_port thread = Thread.new do begin - user, server_stripped, port = SSH.parse_server(server) - @config.ssh_options[:username] = user if user - remote_port = port || 22 - @session.forward.local(local_port, server_stripped, remote_port) - connection = SSH.connect('127.0.0.1', @config, local_port) - @config.logger.trace "connection to #{server} via gateway established" + local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => local_port) + session.forward.local(local_port, server.host, server.port || 22) + connection = SSH.connect(local_host, @options) + connection.xserver = server + logger.trace "connected: `#{server}' (via gateway)" if logger rescue Errno::EADDRINUSE local_port = next_port retry rescue Exception => e - puts e.class.name - puts e.backtrace.join("\n") + warn "#{e.class}: #{e.message}" + warn e.backtrace.join("\n") end end thread.join - connection or raise "Could not establish connection to #{server}" + if connection.nil? + error = ConnectionError.new("could not establish connection to `#{server}'") + error.hosts = [server] + raise error + end + + connection end private + + def logger + @options[:logger] + end def next_port @port_guard.synchronize do port = @next_port @next_port -= 1