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,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