capistrano 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,38 @@
1
+ require 'capistrano/actor'
2
+
3
+ module Capistrano
4
+ class ExtensionProxy
5
+ def initialize(actor, mod)
6
+ @actor = actor
7
+ extend(mod)
8
+ end
9
+
10
+ def method_missing(sym, *args, &block)
11
+ @actor.send(sym, *args, &block)
12
+ end
13
+ end
14
+
15
+ EXTENSIONS = {}
16
+
17
+ def self.plugin(name, mod)
18
+ return false if EXTENSIONS.has_key?(name)
19
+
20
+ Capistrano::Actor.class_eval <<-STR, __FILE__, __LINE__+1
21
+ def #{name}
22
+ @__#{name}_proxy ||= Capistrano::ExtensionProxy.new(self, Capistrano::EXTENSIONS[#{name.inspect}])
23
+ end
24
+ STR
25
+
26
+ EXTENSIONS[name] = mod
27
+ return true
28
+ end
29
+
30
+ def self.remove_plugin(name)
31
+ if EXTENSIONS.delete(name)
32
+ Capistrano::Actor.send(:remove_method, name)
33
+ return true
34
+ end
35
+
36
+ return false
37
+ end
38
+ end
@@ -0,0 +1,118 @@
1
+ require 'thread'
2
+ require 'capistrano/ssh'
3
+
4
+ Thread.abort_on_exception = true
5
+
6
+ module Capistrano
7
+
8
+ # Black magic. It uses threads and Net::SSH to set up a connection to a
9
+ # gateway server, through which connections to other servers may be
10
+ # tunnelled.
11
+ #
12
+ # It is used internally by Actor, but may be useful on its own, as well.
13
+ #
14
+ # Usage:
15
+ #
16
+ # config = Capistrano::Configuration.new
17
+ # gateway = Capistrano::Gateway.new('gateway.example.com', config)
18
+ #
19
+ # sess1 = gateway.connect_to('hidden.example.com')
20
+ # sess2 = gateway.connect_to('other.example.com')
21
+ class Gateway
22
+ # The thread inside which the gateway connection itself is running.
23
+ attr_reader :thread
24
+
25
+ # The Net::SSH session representing the gateway connection.
26
+ attr_reader :session
27
+
28
+ MAX_PORT = 65535
29
+ MIN_PORT = 1024
30
+
31
+ def initialize(server, config) #:nodoc:
32
+ @config = config
33
+ @pending_forward_requests = {}
34
+ @mutex = Mutex.new
35
+ @next_port = MAX_PORT
36
+ @terminate_thread = false
37
+
38
+ waiter = ConditionVariable.new
39
+
40
+ @thread = Thread.new do
41
+ @config.logger.trace "starting connection to gateway #{server}"
42
+ SSH.connect(server, @config) do |@session|
43
+ @config.logger.trace "gateway connection established"
44
+ @mutex.synchronize { waiter.signal }
45
+ connection = @session.registry[:connection][:driver]
46
+ loop do
47
+ break if @terminate_thread
48
+ sleep 0.1 unless connection.reader_ready?
49
+ connection.process true
50
+ Thread.new { process_next_pending_connection_request }
51
+ end
52
+ end
53
+ end
54
+
55
+ @mutex.synchronize { waiter.wait(@mutex) }
56
+ end
57
+
58
+ # Shuts down all forwarded connections and terminates the gateway.
59
+ def shutdown!
60
+ # cancel all active forward channels
61
+ @session.forward.active_locals.each do |lport, host, port|
62
+ @session.forward.cancel_local(lport)
63
+ end
64
+
65
+ # terminate the gateway thread
66
+ @terminate_thread = true
67
+
68
+ # wait for the gateway thread to stop
69
+ @thread.join
70
+ end
71
+
72
+ # Connects to the given server by opening a forwarded port from the local
73
+ # host to the server, via the gateway, and then opens and returns a new
74
+ # Net::SSH connection via that port.
75
+ def connect_to(server)
76
+ @mutex.synchronize do
77
+ @pending_forward_requests[server] = ConditionVariable.new
78
+ @pending_forward_requests[server].wait(@mutex)
79
+ @pending_forward_requests.delete(server)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def next_port
86
+ port = @next_port
87
+ @next_port -= 1
88
+ @next_port = MAX_PORT if @next_port < MIN_PORT
89
+ port
90
+ end
91
+
92
+ def process_next_pending_connection_request
93
+ @mutex.synchronize do
94
+ key = @pending_forward_requests.keys.detect { |k| ConditionVariable === @pending_forward_requests[k] } or return
95
+ var = @pending_forward_requests[key]
96
+
97
+ @config.logger.trace "establishing connection to #{key} via gateway"
98
+
99
+ port = next_port
100
+
101
+ begin
102
+ @session.forward.local(port, key, 22)
103
+ @pending_forward_requests[key] = SSH.connect('127.0.0.1', @config,
104
+ port)
105
+ @config.logger.trace "connection to #{key} via gateway established"
106
+ rescue Errno::EADDRINUSE
107
+ port = next_port
108
+ retry
109
+ rescue Object
110
+ @pending_forward_requests[key] = nil
111
+ raise
112
+ ensure
113
+ var.signal
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,25 @@
1
+ class DeploymentGenerator < Rails::Generator::NamedBase
2
+ attr_reader :recipe_file
3
+
4
+ def initialize(runtime_args, runtime_options = {})
5
+ super
6
+ @recipe_file = @args.shift || "deploy"
7
+ end
8
+
9
+ def manifest
10
+ record do |m|
11
+ m.directory "config"
12
+ m.template "deploy.rb", File.join("config", "#{recipe_file}.rb")
13
+ m.directory "lib/tasks"
14
+ m.template "capistrano.rake", File.join("lib", "tasks", "capistrano.rake")
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ # Override with your own usage banner.
21
+ def banner
22
+ "Usage: #{$0} deployment ApplicationName [recipe-name]\n" +
23
+ " (recipe-name defaults to \"deploy\")"
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # =============================================================================
2
+ # A set of rake tasks for invoking the Capistrano automation utility.
3
+ # =============================================================================
4
+
5
+ # Invoke the given actions via Capistrano
6
+ def cap(*parameters)
7
+ begin
8
+ require 'rubygems'
9
+ rescue LoadError
10
+ # no rubygems to load, so we fail silently
11
+ end
12
+
13
+ require 'capistrano/cli'
14
+
15
+ Capistrano::CLI.new(parameters.map { |param| param.to_s }).execute!
16
+ end
17
+
18
+ namespace :remote do
19
+ <%- config = Capistrano::Configuration.new
20
+ config.load "standard"
21
+ options = { :show_tasks => ", '-q'" }
22
+ config.actor.each_task do |info| -%>
23
+ <%- unless info[:desc].empty? -%>
24
+ desc "<%= info[:desc].scan(/.*?(?:\. |$)/).first.strip.gsub(/"/, "\\\"") %>"
25
+ <%- end -%>
26
+ task(<%= info[:task].inspect %>) { cap <%= info[:task].inspect %><%= options[info[:task]] %> }
27
+
28
+ <%- end -%>
29
+ desc "Execute a specific action using capistrano"
30
+ task :exec do
31
+ unless ENV['ACTION']
32
+ raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable"
33
+ end
34
+
35
+ actions = ENV['ACTION'].split(",")
36
+ actions.concat(ENV['PARAMS'].split(" ")) if ENV['PARAMS']
37
+
38
+ cap(*actions)
39
+ end
40
+ end
41
+
42
+ desc "Push the latest revision into production (delegates to remote:deploy)"
43
+ task :deploy => "remote:deploy"
44
+
45
+ desc "Rollback to the release before the current release in production (delegates to remote:rollback)"
46
+ task :rollback => "remote:rollback"
@@ -0,0 +1,122 @@
1
+ # This defines a deployment "recipe" that you can feed to capistrano
2
+ # (http://manuals.rubyonrails.com/read/book/17). It allows you to automate
3
+ # (among other things) the deployment of your application.
4
+
5
+ # =============================================================================
6
+ # REQUIRED VARIABLES
7
+ # =============================================================================
8
+ # You must always specify the application and repository for every recipe. The
9
+ # repository must be the URL of the repository you want this recipe to
10
+ # correspond to. The deploy_to path must be the path on each machine that will
11
+ # form the root of the application path.
12
+
13
+ set :application, "<%= singular_name %>"
14
+ set :repository, "http://svn.yourhost.com/#{application}/trunk"
15
+
16
+ # =============================================================================
17
+ # ROLES
18
+ # =============================================================================
19
+ # You can define any number of roles, each of which contains any number of
20
+ # machines. Roles might include such things as :web, or :app, or :db, defining
21
+ # what the purpose of each machine is. You can also specify options that can
22
+ # be used to single out a specific subset of boxes in a particular role, like
23
+ # :primary => true.
24
+
25
+ role :web, "www01.example.com", "www02.example.com"
26
+ role :app, "app01.example.com", "app02.example.com", "app03.example.com"
27
+ role :db, "db01.example.com", :primary => true
28
+ role :db, "db02.example.com", "db03.example.com"
29
+
30
+ # =============================================================================
31
+ # OPTIONAL VARIABLES
32
+ # =============================================================================
33
+ # set :deploy_to, "/path/to/app" # defaults to "/u/apps/#{application}"
34
+ # set :user, "flippy" # defaults to the currently logged in user
35
+ # set :scm, :darcs # defaults to :subversion
36
+ # set :svn, "/path/to/svn" # defaults to searching the PATH
37
+ # set :darcs, "/path/to/darcs" # defaults to searching the PATH
38
+ # set :cvs, "/path/to/cvs" # defaults to searching the PATH
39
+ # set :gateway, "gate.host.com" # default to no gateway
40
+
41
+ # =============================================================================
42
+ # SSH OPTIONS
43
+ # =============================================================================
44
+ # ssh_options[:keys] = %w(/path/to/my/key /path/to/another/key)
45
+ # ssh_options[:port] = 25
46
+
47
+ # =============================================================================
48
+ # TASKS
49
+ # =============================================================================
50
+ # Define tasks that run on all (or only some) of the machines. You can specify
51
+ # a role (or set of roles) that each task should be executed on. You can also
52
+ # narrow the set of servers to a subset of a role by specifying options, which
53
+ # must match the options given for the servers to select (like :primary => true)
54
+
55
+ desc <<DESC
56
+ An imaginary backup task. (Execute the 'show_tasks' task to display all
57
+ available tasks.)
58
+ DESC
59
+ task :backup, :roles => :db, :only => { :primary => true } do
60
+ # the on_rollback handler is only executed if this task is executed within
61
+ # a transaction (see below), AND it or a subsequent task fails.
62
+ on_rollback { delete "/tmp/dump.sql" }
63
+
64
+ run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out|
65
+ ch.send_data "thepassword\n" if out =~ /^Enter password:/
66
+ end
67
+ end
68
+
69
+ # Tasks may take advantage of several different helper methods to interact
70
+ # with the remote server(s). These are:
71
+ #
72
+ # * run(command, options={}, &block): execute the given command on all servers
73
+ # associated with the current task, in parallel. The block, if given, should
74
+ # accept three parameters: the communication channel, a symbol identifying the
75
+ # type of stream (:err or :out), and the data. The block is invoked for all
76
+ # output from the command, allowing you to inspect output and act
77
+ # accordingly.
78
+ # * sudo(command, options={}, &block): same as run, but it executes the command
79
+ # via sudo.
80
+ # * delete(path, options={}): deletes the given file or directory from all
81
+ # associated servers. If :recursive => true is given in the options, the
82
+ # delete uses "rm -rf" instead of "rm -f".
83
+ # * put(buffer, path, options={}): creates or overwrites a file at "path" on
84
+ # all associated servers, populating it with the contents of "buffer". You
85
+ # can specify :mode as an integer value, which will be used to set the mode
86
+ # on the file.
87
+ # * render(template, options={}) or render(options={}): renders the given
88
+ # template and returns a string. Alternatively, if the :template key is given,
89
+ # it will be treated as the contents of the template to render. Any other keys
90
+ # are treated as local variables, which are made available to the (ERb)
91
+ # template.
92
+
93
+ desc "Demonstrates the various helper methods available to recipes."
94
+ task :helper_demo do
95
+ # "setup" is a standard task which sets up the directory structure on the
96
+ # remote servers. It is a good idea to run the "setup" task at least once
97
+ # at the beginning of your app's lifetime (it is non-destructive).
98
+ setup
99
+
100
+ buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL'])
101
+ put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644
102
+ sudo "killall -USR1 dispatch.fcgi"
103
+ run "#{release_path}/script/spin"
104
+ delete "#{shared_path}/system/maintenance.html"
105
+ end
106
+
107
+ # You can use "transaction" to indicate that if any of the tasks within it fail,
108
+ # all should be rolled back (for each task that specifies an on_rollback
109
+ # handler).
110
+
111
+ desc "A task demonstrating the use of transactions."
112
+ task :long_deploy do
113
+ transaction do
114
+ update_code
115
+ disable_web
116
+ symlink
117
+ migrate
118
+ end
119
+
120
+ restart
121
+ enable_web
122
+ end
@@ -0,0 +1,20 @@
1
+ module Capistrano
2
+ module Generators
3
+ class RailsLoader
4
+ def self.load!(options)
5
+ require "#{options[:apply_to]}/config/environment"
6
+ require "rails_generator"
7
+ require "rails_generator/scripts/generate"
8
+
9
+ Rails::Generator::Base.sources << Rails::Generator::PathSource.new(
10
+ :capistrano, File.dirname(__FILE__))
11
+
12
+ args = ["deployment"]
13
+ args << (options[:application] || "Application")
14
+ args << (options[:recipe_file] || "deploy")
15
+
16
+ Rails::Generator::Scripts::Generate.new.run(args)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,59 @@
1
+ module Capistrano
2
+ class Logger #:nodoc:
3
+ attr_accessor :level
4
+
5
+ IMPORTANT = 0
6
+ INFO = 1
7
+ DEBUG = 2
8
+ TRACE = 3
9
+
10
+ MAX_LEVEL = 3
11
+
12
+ def initialize(options={})
13
+ output = options[:output] || STDERR
14
+ case
15
+ when output.respond_to?(:puts)
16
+ @device = output
17
+ else
18
+ @device = File.open(output.to_str, "a")
19
+ @needs_close = true
20
+ end
21
+
22
+ @options = options
23
+ @level = 0
24
+ end
25
+
26
+ def close
27
+ @device.close if @needs_close
28
+ end
29
+
30
+ def log(level, message, line_prefix=nil)
31
+ if level <= self.level
32
+ indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
33
+ message.split(/\r?\n/).each do |line|
34
+ if line_prefix
35
+ @device.print "#{indent} [#{line_prefix}] #{line.strip}\n"
36
+ else
37
+ @device.puts "#{indent} #{line.strip}\n"
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def important(message, line_prefix=nil)
44
+ log(IMPORTANT, message, line_prefix)
45
+ end
46
+
47
+ def info(message, line_prefix=nil)
48
+ log(INFO, message, line_prefix)
49
+ end
50
+
51
+ def debug(message, line_prefix=nil)
52
+ log(DEBUG, message, line_prefix)
53
+ end
54
+
55
+ def trace(message, line_prefix=nil)
56
+ log(TRACE, message, line_prefix)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,242 @@
1
+ # Standard tasks that are useful for most recipes. It makes a few assumptions:
2
+ #
3
+ # * The :app role has been defined as the set of machines consisting of the
4
+ # application servers.
5
+ # * The :web role has been defined as the set of machines consisting of the
6
+ # web servers.
7
+ # * The :db role has been defined as the set of machines consisting of the
8
+ # databases, with exactly one set up as the :primary DB server.
9
+ # * The Rails spawner and reaper scripts are being used to manage the FCGI
10
+ # processes.
11
+
12
+ set :rake, "rake"
13
+
14
+ set :rails_env, :production
15
+
16
+ set :migrate_target, :current
17
+ set :migrate_env, ""
18
+
19
+ set :use_sudo, true
20
+ set(:run_method) { use_sudo ? :sudo : :run }
21
+
22
+ set :spinner_user, :app
23
+
24
+ desc "Enumerate and describe every available task."
25
+ task :show_tasks do
26
+ puts "Available tasks"
27
+ puts "---------------"
28
+ each_task do |info|
29
+ wrap_length = 80 - info[:longest]
30
+ lines = info[:desc].gsub(/(.{1,#{wrap_length}})(?:\s|\Z)+/, "\\1\n").split(/\n/)
31
+ puts "%-#{info[:longest]}s %s" % [info[:task], lines.shift]
32
+ puts "%#{info[:longest]}s %s" % ["", lines.shift] until lines.empty?
33
+ puts
34
+ end
35
+ end
36
+
37
+ desc "Set up the expected application directory structure on all boxes"
38
+ task :setup, :roles => [:app, :db, :web] do
39
+ run <<-CMD
40
+ mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
41
+ mkdir -p -m 777 #{shared_path}/log
42
+ CMD
43
+ end
44
+
45
+ desc <<-DESC
46
+ Disable the web server by writing a "maintenance.html" file to the web
47
+ servers. The servers must be configured to detect the presence of this file,
48
+ and if it is present, always display it instead of performing the request.
49
+ DESC
50
+ task :disable_web, :roles => :web do
51
+ on_rollback { delete "#{shared_path}/system/maintenance.html" }
52
+
53
+ maintenance = render("maintenance", :deadline => ENV['UNTIL'],
54
+ :reason => ENV['REASON'])
55
+ put maintenance, "#{shared_path}/system/maintenance.html", :mode => 0644
56
+ end
57
+
58
+ desc %(Re-enable the web server by deleting any "maintenance.html" file.)
59
+ task :enable_web, :roles => :web do
60
+ delete "#{shared_path}/system/maintenance.html"
61
+ end
62
+
63
+ desc <<-DESC
64
+ Update all servers with the latest release of the source code. All this does
65
+ is do a checkout (as defined by the selected scm module).
66
+ DESC
67
+ task :update_code, :roles => [:app, :db, :web] do
68
+ on_rollback { delete release_path, :recursive => true }
69
+
70
+ source.checkout(self)
71
+
72
+ run <<-CMD
73
+ rm -rf #{release_path}/log #{release_path}/public/system &&
74
+ ln -nfs #{shared_path}/log #{release_path}/log &&
75
+ ln -nfs #{shared_path}/system #{release_path}/public/system
76
+ CMD
77
+ end
78
+
79
+ desc <<-DESC
80
+ Rollback the latest checked-out version to the previous one by fixing the
81
+ symlinks and deleting the current release from all servers.
82
+ DESC
83
+ task :rollback_code, :roles => [:app, :db, :web] do
84
+ if releases.length < 2
85
+ raise "could not rollback the code because there is no prior release"
86
+ else
87
+ run <<-CMD
88
+ ln -nfs #{previous_release} #{current_path} &&
89
+ rm -rf #{current_release}
90
+ CMD
91
+ end
92
+ end
93
+
94
+ desc <<-DESC
95
+ Update the 'current' symlink to point to the latest version of
96
+ the application's code.
97
+ DESC
98
+ task :symlink, :roles => [:app, :db, :web] do
99
+ on_rollback { run "ln -nfs #{previous_release} #{current_path}" }
100
+ run "ln -nfs #{current_release} #{current_path}"
101
+ end
102
+
103
+ desc <<-DESC
104
+ Restart the FCGI processes on the app server. This uses the :use_sudo
105
+ variable to determine whether to use sudo or not. By default, :use_sudo is
106
+ set to true, but you can set it to false if you are in a shared environment.
107
+ DESC
108
+ task :restart, :roles => :app do
109
+ send(run_method, "#{current_path}/script/process/reaper")
110
+ end
111
+
112
+ desc <<-DESC
113
+ Run the migrate rake task. By default, it runs this in the version of the app
114
+ indicated by the 'current' symlink. (This means you should not invoke this task
115
+ until the symlink has been updated to the most recent version.) However, you
116
+ can specify a different release via the migrate_target variable, which must be
117
+ one of "current" (for the default behavior), or "latest" (for the latest release
118
+ to be deployed with the update_code task). You can also specify additional
119
+ environment variables to pass to rake via the migrate_env variable. Finally, you
120
+ can specify the full path to the rake executable by setting the rake variable.
121
+ DESC
122
+ task :migrate, :roles => :db, :only => { :primary => true } do
123
+ directory = case migrate_target.to_sym
124
+ when :current then current_path
125
+ when :latest then current_release
126
+ else
127
+ raise ArgumentError,
128
+ "you must specify one of current or latest for migrate_target"
129
+ end
130
+
131
+ run "cd #{directory} && " +
132
+ "#{rake} RAILS_ENV=#{rails_env} #{migrate_env} migrate"
133
+ end
134
+
135
+ desc <<-DESC
136
+ A macro-task that updates the code, fixes the symlink, and restarts the
137
+ application servers.
138
+ DESC
139
+ task :deploy do
140
+ transaction do
141
+ update_code
142
+ symlink
143
+ end
144
+
145
+ restart
146
+ end
147
+
148
+ desc <<-DESC
149
+ Similar to deploy, but it runs the migrate task on the new release before
150
+ updating the symlink. (Note that the update in this case it is not atomic,
151
+ and transactions are not used, because migrations are not guaranteed to be
152
+ reversible.)
153
+ DESC
154
+ task :deploy_with_migrations do
155
+ update_code
156
+
157
+ begin
158
+ old_migrate_target = migrate_target
159
+ set :migrate_target, :latest
160
+ migrate
161
+ ensure
162
+ set :migrate_target, old_migrate_target
163
+ end
164
+
165
+ symlink
166
+
167
+ restart
168
+ end
169
+
170
+ desc "A macro-task that rolls back the code and restarts the application servers."
171
+ task :rollback do
172
+ rollback_code
173
+ restart
174
+ end
175
+
176
+ desc <<-DESC
177
+ Displays the diff between HEAD and what was last deployed. (Not available
178
+ with all SCM's.)
179
+ DESC
180
+ task :diff_from_last_deploy do
181
+ diff = source.diff(self)
182
+ puts
183
+ puts diff
184
+ puts
185
+ end
186
+
187
+ desc "Update the currently released version of the software directly via an SCM update operation"
188
+ task :update_current do
189
+ source.update(self)
190
+ end
191
+
192
+ desc <<-DESC
193
+ Removes unused releases from the releases directory. By default, the last 5
194
+ releases are retained, but this can be configured with the 'keep_releases'
195
+ variable. This will use sudo to do the delete by default, but you can specify
196
+ that run should be used by setting the :use_sudo variable to false.
197
+ DESC
198
+ task :cleanup do
199
+ count = (self[:keep_releases] || 5).to_i
200
+ if count >= releases.length
201
+ logger.important "no old releases to clean up"
202
+ else
203
+ logger.info "keeping #{count} of #{releases.length} deployed releases"
204
+ directories = (releases - releases.last(count)).map { |release|
205
+ File.join(releases_path, release) }.join(" ")
206
+
207
+ send(run_method, "rm -rf #{directories}")
208
+ end
209
+ end
210
+
211
+ desc <<-DESC
212
+ Start the spinner daemon for the application (requires script/spin). This will
213
+ use sudo to start the spinner by default, unless :use_sudo is false. If using
214
+ sudo, you can specify the user that the spinner ought to run as by setting the
215
+ :spinner_user variable (defaults to :app).
216
+ DESC
217
+ task :spinner, :roles => :app do
218
+ user = (use_sudo && spinner_user) ? "-u #{spinner_user} " : ""
219
+ send(run_method, "#{user}#{current_path}/script/spin")
220
+ end
221
+
222
+ desc <<-DESC
223
+ Used only for deploying when the spinner isn't running. It invokes deploy,
224
+ and when it finishes it then invokes the spinner task (to start the spinner).
225
+ DESC
226
+ task :cold_deploy do
227
+ deploy
228
+ spinner
229
+ end
230
+
231
+ desc <<-DESC
232
+ A simple task for performing one-off commands that may not require a full task
233
+ to be written for them. Simply specify the command to execute via the COMMAND
234
+ environment variable. To execute the command only on certain roles, specify
235
+ the ROLES environment variable as a comma-delimited list of role names. Lastly,
236
+ if you want to execute the command via sudo, specify a non-empty value for the
237
+ SUDO environment variable.
238
+ DESC
239
+ task :invoke, :roles => Capistrano.str2roles(ENV["ROLES"] || "") do
240
+ method = ENV["SUDO"] ? :sudo : :run
241
+ send(method, ENV["COMMAND"])
242
+ end