capistrano 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cap +11 -0
- data/examples/sample.rb +113 -0
- data/lib/capistrano.rb +1 -0
- data/lib/capistrano/actor.rb +438 -0
- data/lib/capistrano/cli.rb +295 -0
- data/lib/capistrano/command.rb +90 -0
- data/lib/capistrano/configuration.rb +243 -0
- data/lib/capistrano/extensions.rb +38 -0
- data/lib/capistrano/gateway.rb +118 -0
- data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +25 -0
- data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +46 -0
- data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +122 -0
- data/lib/capistrano/generators/rails/loader.rb +20 -0
- data/lib/capistrano/logger.rb +59 -0
- data/lib/capistrano/recipes/standard.rb +242 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/scm/base.rb +62 -0
- data/lib/capistrano/scm/baz.rb +118 -0
- data/lib/capistrano/scm/bzr.rb +70 -0
- data/lib/capistrano/scm/cvs.rb +124 -0
- data/lib/capistrano/scm/darcs.rb +27 -0
- data/lib/capistrano/scm/perforce.rb +139 -0
- data/lib/capistrano/scm/subversion.rb +122 -0
- data/lib/capistrano/ssh.rb +39 -0
- data/lib/capistrano/transfer.rb +90 -0
- data/lib/capistrano/utils.rb +26 -0
- data/lib/capistrano/version.rb +30 -0
- data/test/actor_test.rb +294 -0
- data/test/command_test.rb +43 -0
- data/test/configuration_test.rb +233 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/scm/cvs_test.rb +186 -0
- data/test/scm/subversion_test.rb +137 -0
- data/test/ssh_test.rb +104 -0
- data/test/utils.rb +50 -0
- 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
|