public
Description: Remote multi-server automation tool. This repository is no longer being actively maintained. Please ask on the mailing list to find someone who has a well-maintained fork. Thanks!
Homepage: http://www.capify.org
Clone URL: git://github.com/jamis/capistrano.git
Ben Lavender (author)
Wed Jun 18 07:19:52 -0700 2008
jamis (committer)
Mon Jun 23 08:28:27 -0700 2008
commit  babc48a04c799d21145f843080c57b91119fbae9
tree    ffd3e82005f23960218bb6174421624aa6e76fb4
parent  a6223b3939f0ef91c928f94502c806a68a011e74
capistrano / lib / capistrano / ssh.rb
100644 80 lines (70 sloc) 3.104 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
begin
  require 'rubygems'
  gem 'net-ssh', ">= 1.99.1"
rescue LoadError, NameError
end
 
require 'net/ssh'
 
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 server
    # definition object that defines 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 Server #:nodoc:
      def self.apply_to(connection, server)
        connection.extend(Server)
        connection.xserver = server
        connection
      end
 
      attr_accessor :xserver
    end
 
    # An abstraction to make it possible to connect to the server via public key
    # without prompting for the password. If the public key authentication fails
    # this will fall back to password authentication.
    #
    # +server+ must be an instance of ServerDefinition.
    #
    # If a block is given, the new session is yielded to it, otherwise the new
    # session is returned.
    #
    # If an :ssh_options key exists in +options+, it is passed to the Net::SSH
    # constructor. Values in +options+ are then merged into it, and any
    # connection information in +server+ is added last, so that +server+ info
    # takes precedence over +options+, which takes precendence over ssh_options.
    def self.connect(server, options={})
      connection_strategy(server, options) do |host, user, connection_options|
        connection = Net::SSH.start(host, user, connection_options)
        Server.apply_to(connection, server)
      end
    end
 
    # Abstracts the logic for establishing an SSH connection (which includes
    # testing for connection failures and retrying with a password, and so forth,
    # mostly made complicated because of the fact that some of these variables
    # might be lazily evaluated and try to do something like prompt the user,
    # which should only happen when absolutely necessary.
    #
    # This will yield the hostname, username, and a hash of connection options
    # to the given block, which should return a new connection.
    def self.connection_strategy(server, options={}, &block)
      methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
      password_value = nil
 
      ssh_options = (server.options[:ssh_options] || {}).merge(options[:ssh_options] || {})
      user = server.user || options[:user] || ssh_options[:username] || ServerDefinition.default_user
      port = server.port || options[:port] || ssh_options[:port]
 
      ssh_options[:port] = port if port
      ssh_options.delete(:username)
 
      begin
        connection_options = ssh_options.merge(
          :password => password_value,
          :auth_methods => ssh_options[:auth_methods] || methods.shift
        )
 
        yield server.host, user, connection_options
      rescue Net::SSH::AuthenticationFailed
        raise if methods.empty? || ssh_options[:auth_methods]
        password_value = options[:password]
        retry
      end
    end
  end
end