capistrano 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/bin/cap +11 -0
  2. data/examples/sample.rb +113 -0
  3. data/lib/capistrano.rb +1 -0
  4. data/lib/capistrano/actor.rb +438 -0
  5. data/lib/capistrano/cli.rb +295 -0
  6. data/lib/capistrano/command.rb +90 -0
  7. data/lib/capistrano/configuration.rb +243 -0
  8. data/lib/capistrano/extensions.rb +38 -0
  9. data/lib/capistrano/gateway.rb +118 -0
  10. data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +25 -0
  11. data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +46 -0
  12. data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +122 -0
  13. data/lib/capistrano/generators/rails/loader.rb +20 -0
  14. data/lib/capistrano/logger.rb +59 -0
  15. data/lib/capistrano/recipes/standard.rb +242 -0
  16. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  17. data/lib/capistrano/scm/base.rb +62 -0
  18. data/lib/capistrano/scm/baz.rb +118 -0
  19. data/lib/capistrano/scm/bzr.rb +70 -0
  20. data/lib/capistrano/scm/cvs.rb +124 -0
  21. data/lib/capistrano/scm/darcs.rb +27 -0
  22. data/lib/capistrano/scm/perforce.rb +139 -0
  23. data/lib/capistrano/scm/subversion.rb +122 -0
  24. data/lib/capistrano/ssh.rb +39 -0
  25. data/lib/capistrano/transfer.rb +90 -0
  26. data/lib/capistrano/utils.rb +26 -0
  27. data/lib/capistrano/version.rb +30 -0
  28. data/test/actor_test.rb +294 -0
  29. data/test/command_test.rb +43 -0
  30. data/test/configuration_test.rb +233 -0
  31. data/test/fixtures/config.rb +5 -0
  32. data/test/fixtures/custom.rb +3 -0
  33. data/test/scm/cvs_test.rb +186 -0
  34. data/test/scm/subversion_test.rb +137 -0
  35. data/test/ssh_test.rb +104 -0
  36. data/test/utils.rb +50 -0
  37. metadata +107 -0
@@ -0,0 +1,139 @@
1
+ require 'capistrano/scm/base'
2
+
3
+ module Capistrano
4
+ module SCM
5
+
6
+ # An SCM module for using perforce as your source control tool.
7
+ # This module can explicitly selected by placing the following line
8
+ # in your configuration:
9
+ #
10
+ # set :scm, :perforce
11
+ #
12
+ # Also, this module accepts a <tt>:p4</tt> configuration variable,
13
+ # which (if specified) will be used as the full path to the p4
14
+ # executable on the remote machine:
15
+ #
16
+ # set :p4, "/usr/local/bin/p4"
17
+ #
18
+ # This module accepts another <tt>:p4sync_flags</tt> configuration
19
+ # variable, which (if specified) can add extra options. This setting
20
+ # defaults to the value "-f" which forces resynchronization.
21
+ #
22
+ # set :p4sync_flags, "-f"
23
+ #
24
+ # This module accepts another <tt>:p4client_root</tt> configuration
25
+ # variable to handle mapping adjustments. Perforce doesn't have the
26
+ # ability to sync to a specific directory (e.g. the release_path); the
27
+ # location being synced to is defined by the client-spec. As such, we
28
+ # sync the client-spec (defined by <tt>p4client</tt> and then copy from
29
+ # the determined root of the client-spec mappings to the release_path.
30
+ # In the unlikely event that your client-spec mappings introduces
31
+ # directory structure above the rails structure, you can override the
32
+ # may need to specify the directory
33
+ #
34
+ # set :p4client_root, "/user/rmcmahon/project1/code"
35
+ #
36
+ # Finally, the module accepts a <tt>p4diff2_options</tt> configuration
37
+ # variable. This can be used to manipulate the output when running a
38
+ # diff between what is deployed, and another revision. This option
39
+ # defaults to "-u". Run 'p4 help diff2' for other options.
40
+ #
41
+ class Perforce < Base
42
+
43
+ def latest_revision
44
+ configuration.logger.debug "querying latest revision..." unless @latest_revision
45
+ @latest_revision = `#{local_p4} counter change`.strip
46
+ @latest_revision
47
+ end
48
+
49
+ # Return the number of the revision currently deployed.
50
+ def current_revision(actor)
51
+ latest = actor.releases.last
52
+ grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log)
53
+ result = ""
54
+ actor.run(grep, :once => true) do |ch, str, out|
55
+ result << out if str == :out
56
+ raise "could not determine current revision" if str == :err
57
+ end
58
+
59
+ date, time, user, rev, dir = result.split
60
+ raise "current revision not found in revisions.log" unless dir == latest
61
+ rev.to_i
62
+ end
63
+
64
+ # Return a string containing the diff between the two revisions. +from+
65
+ # and +to+ may be in any format that p4 recognizes as a valid revision
66
+ # identifiers. If +from+ is +nil+, it defaults to the last deployed
67
+ # revision. If +to+ is +nil+, it defaults to #head.
68
+ def diff(actor, from=nil, to=nil)
69
+ from ||= "@#{current_revision(actor)}"
70
+ to ||= "#head"
71
+ p4client = configuration[:p4client]
72
+ p4diff2_options = configuration[:p4diff2_options]||"-u -db"
73
+ `#{local_p4} diff2 #{p4diff2_options} //#{p4client}/...#{from} //#{p4client}/...#{to}`
74
+ end
75
+
76
+ # Syncronizes (on all servers associated with the current task) the head
77
+ # revision of the code. Uses the given actor instance to execute the command.
78
+ #
79
+ def checkout(actor)
80
+ p4sync_flags = configuration[:p4sync_flags] || "-f"
81
+ p4client_root = configuration[:p4client_root] || "`#{remote_p4} client -o | grep ^Root | cut -f2`"
82
+ command = "#{remote_p4} sync #{p4sync_flags} && cp -rf #{p4client_root} #{actor.release_path};"
83
+ run_checkout(actor, command, &p4_stream_handler(actor))
84
+ end
85
+
86
+ def update(actor)
87
+ raise "#{self.class} doesn't support update(actor)"
88
+ end
89
+
90
+ private
91
+
92
+ def local_p4
93
+ add_standard_p4_options('p4')
94
+ end
95
+
96
+ def remote_p4
97
+ p4_cmd = configuration[:p4] || 'p4'
98
+ add_standard_p4_options(p4_cmd)
99
+ end
100
+
101
+ def add_standard_p4_options(p4_location)
102
+ check_settings
103
+ p4_cmd = p4_location
104
+ p4_cmd = "#{p4_cmd} -p #{configuration[:p4port]}" if configuration[:p4port]
105
+ p4_cmd = "#{p4_cmd} -u #{configuration[:p4user]}" if configuration[:p4user]
106
+ p4_cmd = "#{p4_cmd} -P #{configuration[:p4passwd]}" if configuration[:p4passwd]
107
+ p4_cmd = "#{p4_cmd} -c #{configuration[:p4client]}" if configuration[:p4client]
108
+ p4_cmd
109
+ end
110
+
111
+ def check_settings
112
+ check_setting(:p4port, "Add set :p4port, <your perforce server details e.g. my.p4.server:1666> to deploy.rb")
113
+ check_setting(:p4user, "Add set :p4user, <your production build username> to deploy.rb")
114
+ check_setting(:p4passwd, "Add set :p4passwd, <your build user password> to deploy.rb")
115
+ check_setting(:p4client, "Add set :p4client, <your client-spec name> to deploy.rb")
116
+ end
117
+
118
+ def check_setting(p4setting, message)
119
+ raise "#{p4setting} is not configured. #{message}" unless configuration[p4setting]
120
+ end
121
+
122
+ def p4_stream_handler(actor)
123
+ Proc.new do |ch, stream, out|
124
+ prefix = "#{stream} :: #{ch[:host]}"
125
+ actor.logger.info out, prefix
126
+ if out =~ /\(P4PASSWD\) invalid or unset\./i
127
+ raise "p4passwd is incorrect or unset"
128
+ elsif out =~ /Can.t create a new user.*/i
129
+ raise "p4user is incorrect or unset"
130
+ elsif out =~ /Perforce client error\:/i
131
+ raise "p4port is incorrect or unset"
132
+ elsif out =~ /Client \'[\w\-\_\.]+\' unknown.*/i
133
+ raise "p4client is incorrect or unset"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,122 @@
1
+ require 'capistrano/scm/base'
2
+
3
+ module Capistrano
4
+ module SCM
5
+
6
+ # An SCM module for using subversion as your source control tool. This
7
+ # module is used by default, but you can explicitly specify it by
8
+ # placing the following line in your configuration:
9
+ #
10
+ # set :scm, :subversion
11
+ #
12
+ # Also, this module accepts a <tt>:svn</tt> configuration variable,
13
+ # which (if specified) will be used as the full path to the svn
14
+ # executable on the remote machine:
15
+ #
16
+ # set :svn, "/opt/local/bin/svn"
17
+ class Subversion < Base
18
+ # Return an integer identifying the last known revision in the svn
19
+ # repository. (This integer is currently the revision number.) If latest
20
+ # revision does not exist in the given repository, this routine will
21
+ # walk up the directory tree until it finds it.
22
+ def latest_revision
23
+ configuration.logger.debug "querying latest revision..." unless @latest_revision
24
+ repo = configuration.repository
25
+ until @latest_revision
26
+ match = svn_log(repo).scan(/r(\d+)/).first
27
+ @latest_revision = match ? match.first : nil
28
+ if @latest_revision.nil?
29
+ # if a revision number was not reported, move up a level in the path
30
+ # and try again.
31
+ repo = File.dirname(repo)
32
+ end
33
+ end
34
+ @latest_revision
35
+ end
36
+
37
+ # Return the number of the revision currently deployed.
38
+ def current_revision(actor)
39
+ latest = actor.releases.last
40
+ grep = %(grep " #{latest}$" #{configuration.deploy_to}/revisions.log)
41
+ result = ""
42
+ actor.run(grep, :once => true) do |ch, str, out|
43
+ result << out if str == :out
44
+ raise "could not determine current revision" if str == :err
45
+ end
46
+
47
+ date, time, user, rev, dir = result.split
48
+ raise "current revision not found in revisions.log" unless dir == latest
49
+
50
+ rev.to_i
51
+ end
52
+
53
+ # Return a string containing the diff between the two revisions. +from+
54
+ # and +to+ may be in any format that svn recognizes as a valid revision
55
+ # identifier. If +from+ is +nil+, it defaults to the last deployed
56
+ # revision. If +to+ is +nil+, it defaults to HEAD.
57
+ def diff(actor, from=nil, to=nil)
58
+ from ||= current_revision(actor)
59
+ to ||= "HEAD"
60
+
61
+ `svn diff #{configuration.repository}@#{from} #{configuration.repository}@#{to}`
62
+ end
63
+
64
+ # Check out (on all servers associated with the current task) the latest
65
+ # revision. Uses the given actor instance to execute the command. If
66
+ # svn asks for a password this will automatically provide it (assuming
67
+ # the requested password is the same as the password for logging into the
68
+ # remote server.)
69
+ def checkout(actor)
70
+ op = configuration[:checkout] || "co"
71
+ username = configuration[:svn_username] ? "--username #{configuration[:svn_username]}" : ""
72
+ command = "#{svn} #{op} #{username} -q -r#{configuration.revision} #{configuration.repository} #{actor.release_path} &&"
73
+ run_checkout(actor, command, &svn_stream_handler(actor))
74
+ end
75
+
76
+ # Update the current release in-place. This assumes that the original
77
+ # deployment was made using checkout, and not something like export.
78
+ def update(actor)
79
+ command = "cd #{actor.current_path} && #{svn} up -q &&"
80
+ run_update(actor, command, &svn_stream_handler(actor))
81
+ end
82
+
83
+ private
84
+
85
+ def svn
86
+ configuration[:svn] || "svn"
87
+ end
88
+
89
+ def svn_log(path)
90
+ `svn log -q -rhead #{path}`
91
+ end
92
+
93
+ def svn_password
94
+ configuration[:svn_password] || configuration[:password]
95
+ end
96
+
97
+ def svn_stream_handler(actor)
98
+ Proc.new do |ch, stream, out|
99
+ prefix = "#{stream} :: #{ch[:host]}"
100
+ actor.logger.info out, prefix
101
+ if out =~ /\bpassword.*:/i
102
+ actor.logger.info "subversion is asking for a password", prefix
103
+ ch.send_data "#{svn_password}\n"
104
+ elsif out =~ %r{\(yes/no\)}
105
+ actor.logger.info "subversion is asking whether to connect or not",
106
+ prefix
107
+ ch.send_data "yes\n"
108
+ elsif out =~ %r{passphrase}
109
+ message = "subversion needs your key's passphrase, sending empty string"
110
+ actor.logger.info message, prefix
111
+ ch.send_data "\n"
112
+ elsif out =~ %r{The entry \'(\w+)\' is no longer a directory}
113
+ message = "subversion can't update because directory '#{$1}' was replaced. Please add it to svn:ignore."
114
+ actor.logger.info message, prefix
115
+ raise message
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/ssh'
2
+
3
+ module Capistrano
4
+ unless ENV['SKIP_VERSION_CHECK']
5
+ require 'capistrano/version'
6
+ require 'net/ssh/version'
7
+ ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
8
+ required_version = [1,0,5]
9
+ if !Version.check(required_version, ssh_version)
10
+ raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{required_version.join(".")}"
11
+ end
12
+ end
13
+
14
+ # A helper class for dealing with SSH connections.
15
+ class SSH
16
+ # An abstraction to make it possible to connect to the server via public key
17
+ # without prompting for the password. If the public key authentication fails
18
+ # this will fall back to password authentication.
19
+ #
20
+ # If a block is given, the new session is yielded to it, otherwise the new
21
+ # session is returned.
22
+ def self.connect(server, config, port=22, &block)
23
+ methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
24
+ password_value = nil
25
+
26
+ begin
27
+ ssh_options = { :username => config.user,
28
+ :password => password_value,
29
+ :port => port,
30
+ :auth_methods => methods.shift }.merge(config.ssh_options)
31
+ Net::SSH.start(server,ssh_options,&block)
32
+ rescue Net::SSH::AuthenticationFailed
33
+ raise if methods.empty?
34
+ password_value = config.password
35
+ retry
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,90 @@
1
+ begin
2
+ require 'capistrano/version'
3
+ require 'net/sftp'
4
+ require 'net/sftp/version'
5
+ sftp_version = [Net::SFTP::Version::MAJOR, Net::SFTP::Version::MINOR, Net::SFTP::Version::TINY]
6
+ required_version = [1,1,0]
7
+ if !Capistrano::Version.check(required_version, sftp_version)
8
+ warn "You have Net::SFTP #{sftp_version.join(".")}, but you need at least #{required_version.join(".")}. Net::SFTP will not be used."
9
+ Capistrano::SFTP = false
10
+ else
11
+ Capistrano::SFTP = true
12
+ end
13
+ rescue LoadError
14
+ Capistrano::SFTP = false
15
+ end
16
+
17
+ module Capistrano
18
+
19
+ # This class encapsulates a single file transfer to be performed in parallel
20
+ # across multiple machines, using the SFTP protocol.
21
+ class Transfer
22
+ def initialize(servers, actor, filename, params={}) #:nodoc:
23
+ @servers = servers
24
+ @actor = actor
25
+ @filename = filename
26
+ @params = params
27
+ @completed = 0
28
+ @failed = 0
29
+ @sftps = setup_transfer
30
+ end
31
+
32
+ def logger #:nodoc:
33
+ @actor.logger
34
+ end
35
+
36
+ # Uploads to all specified servers in parallel.
37
+ def process!
38
+ logger.debug "uploading #{@filename}"
39
+
40
+ loop do
41
+ @sftps.each { |sftp| sftp.channel.connection.process(true) }
42
+ break if @completed == @servers.length
43
+ end
44
+
45
+ logger.trace "upload finished"
46
+
47
+ if @failed > 0
48
+ raise "upload of #{@filename} failed on one or more hosts"
49
+ end
50
+
51
+ self
52
+ end
53
+
54
+ private
55
+
56
+ def setup_transfer
57
+ @servers.map do |server|
58
+ sftp = @actor.sessions[server].sftp
59
+ sftp.connect unless sftp.state == :open
60
+
61
+ sftp.open(@filename, IO::WRONLY | IO::CREAT | IO::TRUNC, @params[:mode] || 0660) do |status, handle|
62
+ break unless check_status("open #{@filename}", server, status)
63
+
64
+ logger.info "uploading data to #{server}:#{@filename}"
65
+ sftp.write(handle, @params[:data] || "") do |status|
66
+ break unless check_status("write to #{server}:#{@filename}", server, status)
67
+ sftp.close_handle(handle) do
68
+ logger.debug "done uploading data to #{server}:#{@filename}"
69
+ @completed += 1
70
+ end
71
+ end
72
+ end
73
+
74
+ sftp
75
+ end
76
+ end
77
+
78
+ def check_status(action, server, status)
79
+ if status.code != Net::SFTP::Session::FX_OK
80
+ logger.error "could not #{action} on #{server} (#{status.message})"
81
+ @failed += 1
82
+ @completed += 1
83
+ return false
84
+ end
85
+
86
+ true
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,26 @@
1
+ module Capistrano
2
+ # A helper method for converting a comma-delimited string into an array of
3
+ # roles.
4
+ def self.str2roles(string)
5
+ list = string.split(/,/).map { |s| s.strip.to_sym }
6
+ list.empty? ? nil : list
7
+ end
8
+
9
+ # Used by third-party task bundles to identify the capistrano configuration
10
+ # that is loading them. It's return value is not reliable in other contexts.
11
+ # If +require_config+ is not false, an exception will be raised if the current
12
+ # configuration is not set.
13
+ def self.configuration(require_config=false)
14
+ config = Thread.current[:capistrano_configuration]
15
+ if require_config && config.nil?
16
+ raise "Please require this file from within a Capistrano recipe"
17
+ end
18
+ config
19
+ end
20
+
21
+ # Used internally by Capistrano to specify the current configuration before
22
+ # loading a third-party task bundle.
23
+ def self.configuration=(config)
24
+ Thread.current[:capistrano_configuration] = config
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ module Capistrano
2
+ module Version #:nodoc:
3
+ # A method for comparing versions of required modules. It expects two
4
+ # arrays as parameters, and returns true if the first is no more than the
5
+ # second.
6
+ def self.check(expected, actual) #:nodoc:
7
+ good = false
8
+ if actual[0] > expected[0]
9
+ good = true
10
+ elsif actual[0] == expected[0]
11
+ if actual[1] > expected[1]
12
+ good = true
13
+ elsif actual[1] == expected[1] && actual[2] >= expected[2]
14
+ good = true
15
+ end
16
+ end
17
+
18
+ good
19
+ end
20
+
21
+ MAJOR = 1
22
+ MINOR = 1
23
+ TINY = 0
24
+
25
+ STRING = [MAJOR, MINOR, TINY].join(".")
26
+
27
+ SSH_REQUIRED = [1,0,8]
28
+ SFTP_REQUIRED = [1,1,0]
29
+ end
30
+ end