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