Skip to content

Commit

Permalink
restore plugin extension mechanism, fix hostname problem for tunnelle…
Browse files Browse the repository at this point in the history
…d connections

git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6436 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
jamis committed Mar 16, 2007
1 parent f8d425e commit 55e194c
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 73 deletions.
2 changes: 1 addition & 1 deletion capistrano.gemspec
Expand Up @@ -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'

Expand Down
1 change: 1 addition & 0 deletions lib/capistrano.rb
@@ -1 +1,2 @@
require 'capistrano/configuration'
require 'capistrano/extensions'
1 change: 1 addition & 0 deletions lib/capistrano/cli.rb
@@ -1,3 +1,4 @@
require 'capistrano'
require 'capistrano/cli/execute'
require 'capistrano/cli/help'
require 'capistrano/cli/options'
Expand Down
2 changes: 1 addition & 1 deletion lib/capistrano/command.rb
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion lib/capistrano/configuration.rb
@@ -1,4 +1,3 @@
#require 'capistrano/extensions'
require 'capistrano/logger'

require 'capistrano/configuration/connections'
Expand Down
77 changes: 41 additions & 36 deletions 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
1 change: 1 addition & 0 deletions lib/capistrano/gateway.rb
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion lib/capistrano/ssh.rb
Expand Up @@ -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

Expand All @@ -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?
Expand Down
8 changes: 4 additions & 4 deletions test/command_test.rb
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
31 changes: 22 additions & 9 deletions test/gateway_test.rb
Expand Up @@ -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!
Expand All @@ -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|
Expand Down
48 changes: 28 additions & 20 deletions test/ssh_test.rb
Expand Up @@ -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
Expand All @@ -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

0 comments on commit 55e194c

Please sign in to comment.