From 55e194c3f058615680602fa14df25710806c120c Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 16 Mar 2007 21:22:10 +0000 Subject: [PATCH] restore plugin extension mechanism, fix hostname problem for tunnelled connections git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6436 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- capistrano.gemspec | 2 +- lib/capistrano.rb | 1 + lib/capistrano/cli.rb | 1 + lib/capistrano/command.rb | 2 +- lib/capistrano/configuration.rb | 1 - lib/capistrano/extensions.rb | 77 ++++++++++++++++++--------------- lib/capistrano/gateway.rb | 1 + lib/capistrano/ssh.rb | 17 +++++++- test/command_test.rb | 8 ++-- test/gateway_test.rb | 31 +++++++++---- test/ssh_test.rb | 48 +++++++++++--------- 11 files changed, 116 insertions(+), 73 deletions(-) diff --git a/capistrano.gemspec b/capistrano.gemspec index 9d41e79a0..3cf0976f1 100644 --- a/capistrano.gemspec +++ b/capistrano.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| on multiple remote machines, via SSH. DESC - s.files = Dir.glob("{bin,lib,examples,test}/**/*") + %w(README MIT-LICENSE CHANGELOG THANKS) + s.files = Dir.glob("{bin,lib,examples,test}/**/*") + %w(README MIT-LICENSE CHANGELOG) s.require_path = 'lib' s.autorequire = 'capistrano' diff --git a/lib/capistrano.rb b/lib/capistrano.rb index ee3d500f8..5bc32e870 100644 --- a/lib/capistrano.rb +++ b/lib/capistrano.rb @@ -1 +1,2 @@ require 'capistrano/configuration' +require 'capistrano/extensions' \ No newline at end of file diff --git a/lib/capistrano/cli.rb b/lib/capistrano/cli.rb index d1fc64ab8..b664ce13f 100644 --- a/lib/capistrano/cli.rb +++ b/lib/capistrano/cli.rb @@ -1,3 +1,4 @@ +require 'capistrano' require 'capistrano/cli/execute' require 'capistrano/cli/help' require 'capistrano/cli/options' diff --git a/lib/capistrano/command.rb b/lib/capistrano/command.rb index aceb4a11a..928c21b1f 100644 --- a/lib/capistrano/command.rb +++ b/lib/capistrano/command.rb @@ -75,7 +75,7 @@ def logger def open_channels sessions.map do |session| session.open_channel do |channel| - channel[:host] = session.host + channel[:host] = session.real_host channel[:options] = options channel.request_pty :want_reply => true diff --git a/lib/capistrano/configuration.rb b/lib/capistrano/configuration.rb index 63a3580bf..ca373a6c4 100644 --- a/lib/capistrano/configuration.rb +++ b/lib/capistrano/configuration.rb @@ -1,4 +1,3 @@ -#require 'capistrano/extensions' require 'capistrano/logger' require 'capistrano/configuration/connections' diff --git a/lib/capistrano/extensions.rb b/lib/capistrano/extensions.rb index 5d0dbeaf5..6efb7d358 100644 --- a/lib/capistrano/extensions.rb +++ b/lib/capistrano/extensions.rb @@ -1,36 +1,41 @@ -# module Capistrano -# class ExtensionProxy -# def initialize(actor, mod) -# @actor = actor -# extend(mod) -# end -# -# def method_missing(sym, *args, &block) -# @actor.send(sym, *args, &block) -# end -# end -# -# EXTENSIONS = {} -# -# def self.plugin(name, mod) -# return false if EXTENSIONS.has_key?(name) -# -# Capistrano::Actor.class_eval <<-STR, __FILE__, __LINE__+1 -# def #{name} -# @__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}]) -# end -# STR -# -# EXTENSIONS[name] = mod -# return true -# end -# -# def self.remove_plugin(name) -# if EXTENSIONS.delete(name) -# Capistrano::Actor.send(:remove_method, name) -# return true -# end -# -# return false -# end -# end +module Capistrano + class ExtensionProxy + def initialize(config, mod) + @config = config + extend(mod) + end + + def method_missing(sym, *args, &block) + @config.send(sym, *args, &block) + end + end + + EXTENSIONS = {} + + def self.plugin(name, mod) + return false if EXTENSIONS.has_key?(name) + + Capistrano::Configuration.class_eval <<-STR, __FILE__, __LINE__+1 + def #{name} + @__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}]) + end + STR + + EXTENSIONS[name] = mod + return true + end + + def self.remove_plugin(name) + if EXTENSIONS.delete(name) + Capistrano::Configuration.send(:remove_method, name) + return true + end + + return false + end + + def self.configuration(*args) + warn "[DEPRECATION] Capistrano.configuration is deprecated. Use Capistrano::Configuration.instance instead" + Capistrano::Configuration.instance(*args) + end +end diff --git a/lib/capistrano/gateway.rb b/lib/capistrano/gateway.rb index 9825d823d..180fd3a7a 100644 --- a/lib/capistrano/gateway.rb +++ b/lib/capistrano/gateway.rb @@ -81,6 +81,7 @@ def connect_to(server) 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.real_host = server.host logger.trace "connected: `#{server.host}' (via gateway)" if logger rescue Errno::EADDRINUSE local_port = next_port diff --git a/lib/capistrano/ssh.rb b/lib/capistrano/ssh.rb index 74f0d7656..2c8e80176 100644 --- a/lib/capistrano/ssh.rb +++ b/lib/capistrano/ssh.rb @@ -12,6 +12,20 @@ module Capistrano # A helper class for dealing with SSH connections. class SSH + # Patch an accessor onto an SSH connection so that we can record the "real" + # host behind the connection. This is useful because the gateway returns + # connections whose "host" is 127.0.0.1, instead of the host on the other + # side of the tunnel. + module RealHost #:nodoc: + def self.apply_to(connection, host) + connection.extend(RealHost) + connection.real_host = host + connection + end + + attr_accessor :real_host + end + # The default port for SSH. DEFAULT_PORT = 22 @@ -34,7 +48,8 @@ def self.connect(server, options={}, &block) :auth_methods => methods.shift } ssh_options.update(options[:ssh_options]) if options[:ssh_options] - Net::SSH.start(server.host, ssh_options, &block) + connection = Net::SSH.start(server.host, ssh_options, &block) + RealHost.apply_to(connection, server.host) rescue Net::SSH::AuthenticationFailed raise if methods.empty? diff --git a/test/command_test.rb b/test/command_test.rb index b48218c26..690193ddb 100644 --- a/test/command_test.rb +++ b/test/command_test.rb @@ -53,7 +53,7 @@ def test_env_with_multiple_keys_should_chain_the_entries_together end def test_open_channel_should_set_host_key_on_channel - session = mock(:host => "capistrano") + session = mock(:real_host => "capistrano") channel = stub_everything session.expects(:open_channel).yields(channel) @@ -63,7 +63,7 @@ def test_open_channel_should_set_host_key_on_channel end def test_open_channel_should_set_options_key_on_channel - session = mock(:host => "capistrano") + session = mock(:real_host => "capistrano") channel = stub_everything session.expects(:open_channel).yields(channel) @@ -73,7 +73,7 @@ def test_open_channel_should_set_options_key_on_channel end def test_open_channel_should_request_pty - session = mock(:host => "capistrano") + session = mock(:real_host => "capistrano") channel = stub_everything session.expects(:open_channel).yields(channel) @@ -240,7 +240,7 @@ def new_channel(closed, status=nil) end def setup_for_extracting_channel_action(action, *args) - session = mock(:host => "capistrano") + session = mock(:real_host => "capistrano") channel = stub_everything session.expects(:open_channel).yields(channel) diff --git a/test/gateway_test.rb b/test/gateway_test.rb index 3faf7a131..1d1424833 100644 --- a/test/gateway_test.rb +++ b/test/gateway_test.rb @@ -25,39 +25,46 @@ def test_shutdown_without_any_open_connections_should_terminate_session def test_connect_to_should_start_local_ports_at_65535 gateway = new_gateway - expect_connect_to(:host => "127.0.0.1", :port => 65535).returns :app1 + expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_real_host("app1")) newsess = gateway.connect_to(server("app1")) - assert_equal :app1, newsess + assert_equal result, newsess assert_equal [65535, "app1", 22], gateway.session.forward.active_locals[65535] end def test_connect_to_should_decrement_port_and_retry_if_ports_are_in_use gateway = new_gateway(:reserved => lambda { |n| n > 65000 }) - expect_connect_to(:host => "127.0.0.1", :port => 65000).returns :app1 + expect_connect_to(:host => "127.0.0.1", :port => 65000).returns(result = sess_with_real_host("app1")) newsess = gateway.connect_to(server("app1")) - assert_equal :app1, newsess + assert_equal result, newsess assert_equal [65000, "app1", 22], gateway.session.forward.active_locals[65000] end def test_connect_to_should_honor_user_specification_in_server_definition gateway = new_gateway - expect_connect_to(:host => "127.0.0.1", :user => "jamis", :port => 65535).returns :app1 + expect_connect_to(:host => "127.0.0.1", :user => "jamis", :port => 65535).returns(result = sess_with_real_host("app1")) newsess = gateway.connect_to(server("jamis@app1")) - assert_equal :app1, newsess + assert_equal result, newsess assert_equal [65535, "app1", 22], gateway.session.forward.active_locals[65535] end def test_connect_to_should_honor_port_specification_in_server_definition gateway = new_gateway - expect_connect_to(:host => "127.0.0.1", :port => 65535).returns :app1 + expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_real_host("app1")) newsess = gateway.connect_to(server("app1:1234")) - assert_equal :app1, newsess + assert_equal result, newsess assert_equal [65535, "app1", 1234], gateway.session.forward.active_locals[65535] end + def test_connect_to_should_set_real_host_to_tunnel_target + gateway = new_gateway + expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(result = sess_with_real_host("app1")) + newsess = gateway.connect_to(server("app1:1234")) + assert_equal result, newsess + end + def test_shutdown_should_cancel_active_forwarded_ports gateway = new_gateway - expect_connect_to(:host => "127.0.0.1", :port => 65535).returns :app1 + expect_connect_to(:host => "127.0.0.1", :port => 65535).returns(sess_with_real_host("app1")) gateway.connect_to(server("app1")) assert !gateway.session.forward.active_locals.empty? gateway.shutdown! @@ -73,6 +80,12 @@ def test_error_while_connecting_should_cause_connection_to_fail private + def sess_with_real_host(host) + sess = mock("session") + sess.expects(:real_host=).with(host) + sess + end + def expect_connect_to(options={}) Capistrano::SSH.expects(:connect).with do |server,config| options.all? do |key, value| diff --git a/test/ssh_test.rb b/test/ssh_test.rb index 182e64e95..6c6e133f3 100644 --- a/test/ssh_test.rb +++ b/test/ssh_test.rb @@ -11,14 +11,14 @@ def setup end def test_connect_with_bare_server_without_options_or_config_with_public_key_succeeding_should_only_loop_once - Net::SSH.expects(:start).with(@server.host, @options).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server) + Net::SSH.expects(:start).with(@server.host, @options).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server) end def test_connect_with_bare_server_without_options_with_public_key_failing_should_try_password Net::SSH.expects(:start).with(@server.host, @options).raises(Net::SSH::AuthenticationFailed) - Net::SSH.expects(:start).with(@server.host, @options.merge(:password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server, :password => "f4b13n") + Net::SSH.expects(:start).with(@server.host, @options.merge(:password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server, :password => "f4b13n") end def test_connect_with_bare_server_without_options_public_key_and_password_failing_should_raise_error @@ -30,49 +30,57 @@ def test_connect_with_bare_server_without_options_public_key_and_password_failin end def test_connect_with_bare_server_and_user_via_public_key_should_pass_user_to_net_ssh - Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "jamis")).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server, :user => "jamis") + Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "jamis")).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server, :user => "jamis") end def test_connect_with_bare_server_and_user_via_password_should_pass_user_to_net_ssh Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "jamis")).raises(Net::SSH::AuthenticationFailed) - Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "jamis", :password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server, :user => "jamis", :password => "f4b13n") + Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "jamis", :password => "f4b13n", :auth_methods => %w(password keyboard-interactive))).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server, :user => "jamis", :password => "f4b13n") end def test_connect_with_bare_server_with_explicit_port_should_pass_port_to_net_ssh - Net::SSH.expects(:start).with(@server.host, @options.merge(:port => 1234)).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server, :port => 1234) + Net::SSH.expects(:start).with(@server.host, @options.merge(:port => 1234)).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server, :port => 1234) end def test_connect_with_server_with_user_should_pass_user_to_net_ssh server = server("jamis@capistrano") - Net::SSH.expects(:start).with(server.host, @options.merge(:username => "jamis")).returns(:success) - assert_equal :success, Capistrano::SSH.connect(server) + Net::SSH.expects(:start).with(server.host, @options.merge(:username => "jamis")).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_server_with_port_should_pass_port_to_net_ssh server = server("capistrano:1235") - Net::SSH.expects(:start).with(server.host, @options.merge(:port => 1235)).returns(:success) - assert_equal :success, Capistrano::SSH.connect(server) + Net::SSH.expects(:start).with(server.host, @options.merge(:port => 1235)).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_server_with_user_and_port_should_pass_user_and_port_to_net_ssh server = server("jamis@capistrano:1235") - Net::SSH.expects(:start).with(server.host, @options.merge(:username => "jamis", :port => 1235)).returns(:success) - assert_equal :success, Capistrano::SSH.connect(server) + Net::SSH.expects(:start).with(server.host, @options.merge(:username => "jamis", :port => 1235)).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(server) end def test_connect_with_ssh_options_should_override_options ssh_options = { :username => "JamisMan", :port => 8125 } - Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "JamisMan", :port => 8125)).returns(:success) - assert_equal :success, Capistrano::SSH.connect(@server, {:ssh_options => ssh_options, :user => "jamis", :port => 1235}) + Net::SSH.expects(:start).with(@server.host, @options.merge(:username => "JamisMan", :port => 8125)).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server, {:ssh_options => ssh_options, :user => "jamis", :port => 1235}) end def test_connect_with_ssh_options_should_override_server_options ssh_options = { :username => "JamisMan", :port => 8125 } server = server("jamis@capistrano:1235") - Net::SSH.expects(:start).with(server.host, @options.merge(:username => "JamisMan", :port => 8125)).returns(:success) - assert_equal :success, Capistrano::SSH.connect(server, {:ssh_options => ssh_options}) + Net::SSH.expects(:start).with(server.host, @options.merge(:username => "JamisMan", :port => 8125)).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(server, {:ssh_options => ssh_options}) + end + + def test_connect_should_add_real_host_accessor_to_connection + Net::SSH.expects(:start).with(@server.host, @options).returns(success = Object.new) + assert_equal success, Capistrano::SSH.connect(@server) + assert success.respond_to?(:real_host) + assert success.respond_to?(:real_host=) + assert_equal success.real_host, @server.host end end