switchtower 0.9.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/switchtower +11 -0
- data/examples/sample.rb +113 -0
- data/lib/switchtower.rb +1 -0
- data/lib/switchtower/actor.rb +350 -0
- data/lib/switchtower/cli.rb +220 -0
- data/lib/switchtower/command.rb +85 -0
- data/lib/switchtower/configuration.rb +193 -0
- data/lib/switchtower/gateway.rb +106 -0
- data/lib/switchtower/generators/rails/deployment/deployment_generator.rb +25 -0
- data/lib/switchtower/generators/rails/deployment/templates/deploy.rb +116 -0
- data/lib/switchtower/generators/rails/deployment/templates/switchtower.rake +33 -0
- data/lib/switchtower/generators/rails/loader.rb +20 -0
- data/lib/switchtower/logger.rb +56 -0
- data/lib/switchtower/recipes/standard.rb +175 -0
- data/lib/switchtower/recipes/templates/maintenance.rhtml +53 -0
- data/lib/switchtower/scm/base.rb +43 -0
- data/lib/switchtower/scm/cvs.rb +73 -0
- data/lib/switchtower/scm/darcs.rb +27 -0
- data/lib/switchtower/scm/subversion.rb +104 -0
- data/lib/switchtower/ssh.rb +30 -0
- data/lib/switchtower/version.rb +9 -0
- data/test/actor_test.rb +261 -0
- data/test/command_test.rb +43 -0
- data/test/configuration_test.rb +210 -0
- data/test/fixtures/config.rb +5 -0
- data/test/scm/cvs_test.rb +164 -0
- data/test/scm/subversion_test.rb +100 -0
- data/test/ssh_test.rb +104 -0
- data/test/utils.rb +41 -0
- metadata +88 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
require 'switchtower/ssh'
|
|
3
|
+
|
|
4
|
+
Thread.abort_on_exception = true
|
|
5
|
+
|
|
6
|
+
module SwitchTower
|
|
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 = SwitchTower::Configuration.new
|
|
17
|
+
# gateway = SwitchTower::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
|
+
def initialize(server, config) #:nodoc:
|
|
29
|
+
@config = config
|
|
30
|
+
@pending_forward_requests = {}
|
|
31
|
+
@mutex = Mutex.new
|
|
32
|
+
@next_port = 31310
|
|
33
|
+
@terminate_thread = false
|
|
34
|
+
|
|
35
|
+
waiter = ConditionVariable.new
|
|
36
|
+
|
|
37
|
+
@thread = Thread.new do
|
|
38
|
+
@config.logger.trace "starting connection to gateway #{server}"
|
|
39
|
+
SSH.connect(server, @config) do |@session|
|
|
40
|
+
@config.logger.trace "gateway connection established"
|
|
41
|
+
@mutex.synchronize { waiter.signal }
|
|
42
|
+
connection = @session.registry[:connection][:driver]
|
|
43
|
+
loop do
|
|
44
|
+
break if @terminate_thread
|
|
45
|
+
sleep 0.1 unless connection.reader_ready?
|
|
46
|
+
connection.process true
|
|
47
|
+
Thread.new { process_next_pending_connection_request }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@mutex.synchronize { waiter.wait(@mutex) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Shuts down all forwarded connections and terminates the gateway.
|
|
56
|
+
def shutdown!
|
|
57
|
+
# cancel all active forward channels
|
|
58
|
+
@session.forward.active_locals.each do |lport, host, port|
|
|
59
|
+
@session.forward.cancel_local(lport)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# terminate the gateway thread
|
|
63
|
+
@terminate_thread = true
|
|
64
|
+
|
|
65
|
+
# wait for the gateway thread to stop
|
|
66
|
+
@thread.join
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Connects to the given server by opening a forwarded port from the local
|
|
70
|
+
# host to the server, via the gateway, and then opens and returns a new
|
|
71
|
+
# Net::SSH connection via that port.
|
|
72
|
+
def connect_to(server)
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@pending_forward_requests[server] = ConditionVariable.new
|
|
75
|
+
@pending_forward_requests[server].wait(@mutex)
|
|
76
|
+
@pending_forward_requests.delete(server)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def process_next_pending_connection_request
|
|
83
|
+
@mutex.synchronize do
|
|
84
|
+
key = @pending_forward_requests.keys.detect { |k| ConditionVariable === @pending_forward_requests[k] } or return
|
|
85
|
+
var = @pending_forward_requests[key]
|
|
86
|
+
|
|
87
|
+
@config.logger.trace "establishing connection to #{key} via gateway"
|
|
88
|
+
|
|
89
|
+
port = @next_port
|
|
90
|
+
@next_port += 1
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
@session.forward.local(port, key, 22)
|
|
94
|
+
@pending_forward_requests[key] = SSH.connect('127.0.0.1', @config,
|
|
95
|
+
port)
|
|
96
|
+
@config.logger.trace "connection to #{key} via gateway established"
|
|
97
|
+
rescue Object
|
|
98
|
+
@pending_forward_requests[key] = nil
|
|
99
|
+
raise
|
|
100
|
+
ensure
|
|
101
|
+
var.signal
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
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 "switchtower.rake", File.join("lib", "tasks", "switchtower.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,116 @@
|
|
|
1
|
+
# This defines a deployment "recipe" that you can feed to switchtower
|
|
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
|
+
# TASKS
|
|
43
|
+
# =============================================================================
|
|
44
|
+
# Define tasks that run on all (or only some) of the machines. You can specify
|
|
45
|
+
# a role (or set of roles) that each task should be executed on. You can also
|
|
46
|
+
# narrow the set of servers to a subset of a role by specifying options, which
|
|
47
|
+
# must match the options given for the servers to select (like :primary => true)
|
|
48
|
+
|
|
49
|
+
desc <<DESC
|
|
50
|
+
An imaginary backup task. (Execute the 'show_tasks' task to display all
|
|
51
|
+
available tasks.)
|
|
52
|
+
DESC
|
|
53
|
+
task :backup, :roles => :db, :only => { :primary => true } do
|
|
54
|
+
# the on_rollback handler is only executed if this task is executed within
|
|
55
|
+
# a transaction (see below), AND it or a subsequent task fails.
|
|
56
|
+
on_rollback { delete "/tmp/dump.sql" }
|
|
57
|
+
|
|
58
|
+
run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out|
|
|
59
|
+
ch.send_data "thepassword\n" if out =~ /^Enter password:/
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Tasks may take advantage of several different helper methods to interact
|
|
64
|
+
# with the remote server(s). These are:
|
|
65
|
+
#
|
|
66
|
+
# * run(command, options={}, &block): execute the given command on all servers
|
|
67
|
+
# associated with the current task, in parallel. The block, if given, should
|
|
68
|
+
# accept three parameters: the communication channel, a symbol identifying the
|
|
69
|
+
# type of stream (:err or :out), and the data. The block is invoked for all
|
|
70
|
+
# output from the command, allowing you to inspect output and act
|
|
71
|
+
# accordingly.
|
|
72
|
+
# * sudo(command, options={}, &block): same as run, but it executes the command
|
|
73
|
+
# via sudo.
|
|
74
|
+
# * delete(path, options={}): deletes the given file or directory from all
|
|
75
|
+
# associated servers. If :recursive => true is given in the options, the
|
|
76
|
+
# delete uses "rm -rf" instead of "rm -f".
|
|
77
|
+
# * put(buffer, path, options={}): creates or overwrites a file at "path" on
|
|
78
|
+
# all associated servers, populating it with the contents of "buffer". You
|
|
79
|
+
# can specify :mode as an integer value, which will be used to set the mode
|
|
80
|
+
# on the file.
|
|
81
|
+
# * render(template, options={}) or render(options={}): renders the given
|
|
82
|
+
# template and returns a string. Alternatively, if the :template key is given,
|
|
83
|
+
# it will be treated as the contents of the template to render. Any other keys
|
|
84
|
+
# are treated as local variables, which are made available to the (ERb)
|
|
85
|
+
# template.
|
|
86
|
+
|
|
87
|
+
desc "Demonstrates the various helper methods available to recipes."
|
|
88
|
+
task :helper_demo do
|
|
89
|
+
# "setup" is a standard task which sets up the directory structure on the
|
|
90
|
+
# remote servers. It is a good idea to run the "setup" task at least once
|
|
91
|
+
# at the beginning of your app's lifetime (it is non-destructive).
|
|
92
|
+
setup
|
|
93
|
+
|
|
94
|
+
buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL'])
|
|
95
|
+
put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644
|
|
96
|
+
sudo "killall -USR1 dispatch.fcgi"
|
|
97
|
+
run "#{release_path}/script/spin"
|
|
98
|
+
delete "#{shared_path}/system/maintenance.html"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# You can use "transaction" to indicate that if any of the tasks within it fail,
|
|
102
|
+
# all should be rolled back (for each task that specifies an on_rollback
|
|
103
|
+
# handler).
|
|
104
|
+
|
|
105
|
+
desc "A task demonstrating the use of transactions."
|
|
106
|
+
task :long_deploy do
|
|
107
|
+
transaction do
|
|
108
|
+
update_code
|
|
109
|
+
disable_web
|
|
110
|
+
symlink
|
|
111
|
+
migrate
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
restart
|
|
115
|
+
enable_web
|
|
116
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# A set of rake tasks for invoking the SwitchTower automation utility.
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
desc "Push the latest revision into production using the release manager"
|
|
6
|
+
task :deploy do
|
|
7
|
+
system "switchtower -vvvv -r config/<%= recipe_file %> -a deploy"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "Rollback to the release before the current release in production"
|
|
11
|
+
task :rollback do
|
|
12
|
+
system "switchtower -vvvv -r config/<%= recipe_file %> -a rollback"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "Describe the differences between HEAD and the last production release"
|
|
16
|
+
task :diff_from_last_deploy do
|
|
17
|
+
system "switchtower -vvvv -r config/<%= recipe_file %> -a diff_from_last_deploy"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Enumerate all available deployment tasks"
|
|
21
|
+
task :show_deploy_tasks do
|
|
22
|
+
system "switchtower -r config/<%= recipe_file %> -a show_tasks"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Execute a specific action using the release manager"
|
|
26
|
+
task :remote_exec do
|
|
27
|
+
unless ENV['ACTION']
|
|
28
|
+
raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
actions = ENV['ACTION'].split(",").map { |a| "-a #{a}" }.join(" ")
|
|
32
|
+
system "switchtower -vvvv -r config/<%= recipe_file %> #{actions}"
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SwitchTower
|
|
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
|
+
:switchtower, 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,56 @@
|
|
|
1
|
+
module SwitchTower
|
|
2
|
+
class Logger #:nodoc:
|
|
3
|
+
attr_accessor :level
|
|
4
|
+
|
|
5
|
+
IMPORTANT = 0
|
|
6
|
+
INFO = 1
|
|
7
|
+
DEBUG = 2
|
|
8
|
+
TRACE = 3
|
|
9
|
+
|
|
10
|
+
def initialize(options={})
|
|
11
|
+
output = options[:output] || STDERR
|
|
12
|
+
case
|
|
13
|
+
when output.respond_to?(:puts)
|
|
14
|
+
@device = output
|
|
15
|
+
else
|
|
16
|
+
@device = File.open(output.to_str, "a")
|
|
17
|
+
@needs_close = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@options = options
|
|
21
|
+
@level = 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def close
|
|
25
|
+
@device.close if @needs_close
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def log(level, message, line_prefix=nil)
|
|
29
|
+
if level <= self.level
|
|
30
|
+
if line_prefix
|
|
31
|
+
message.split(/\r?\n/).each do |line|
|
|
32
|
+
@device.print "[#{line_prefix}] #{line.strip}\n"
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
@device.puts message.strip
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def important(message, line_prefix=nil)
|
|
41
|
+
log(IMPORTANT, message, line_prefix)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def info(message, line_prefix=nil)
|
|
45
|
+
log(INFO, message, line_prefix)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def debug(message, line_prefix=nil)
|
|
49
|
+
log(DEBUG, message, line_prefix)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def trace(message, line_prefix=nil)
|
|
53
|
+
log(TRACE, message, line_prefix)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
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 Rails spinner and reaper scripts are being used to manage the FCGI
|
|
8
|
+
# processes.
|
|
9
|
+
# * There is a script in script/ called "reap" that restarts the FCGI processes
|
|
10
|
+
|
|
11
|
+
set :rake, "rake"
|
|
12
|
+
|
|
13
|
+
desc "Enumerate and describe every available task."
|
|
14
|
+
task :show_tasks do
|
|
15
|
+
keys = tasks.keys.sort_by { |a| a.to_s }
|
|
16
|
+
longest = keys.inject(0) { |len,key| key.to_s.length > len ? key.to_s.length : len } + 2
|
|
17
|
+
|
|
18
|
+
puts "Available tasks"
|
|
19
|
+
puts "---------------"
|
|
20
|
+
tasks.keys.sort_by { |a| a.to_s }.each do |key|
|
|
21
|
+
desc = (tasks[key].options[:desc] || "").strip.split(/\r?\n/)
|
|
22
|
+
puts "%-#{longest}s %s" % [key, desc.shift]
|
|
23
|
+
puts "%#{longest}s %s" % ["", desc.shift] until desc.empty?
|
|
24
|
+
puts
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Set up the expected application directory structure on all boxes"
|
|
29
|
+
task :setup, :roles => [:app, :db, :web] do
|
|
30
|
+
run <<-CMD
|
|
31
|
+
mkdir -p -m 775 #{releases_path} #{shared_path}/system &&
|
|
32
|
+
mkdir -p -m 777 #{shared_path}/log
|
|
33
|
+
CMD
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc <<-DESC
|
|
37
|
+
Disable the web server by writing a "maintenance.html" file to the web
|
|
38
|
+
servers. The servers must be configured to detect the presence of this file,
|
|
39
|
+
and if it is present, always display it instead of performing the request.
|
|
40
|
+
DESC
|
|
41
|
+
task :disable_web, :roles => :web do
|
|
42
|
+
on_rollback { delete "#{shared_path}/system/maintenance.html" }
|
|
43
|
+
|
|
44
|
+
maintenance = render("maintenance", :deadline => ENV['UNTIL'],
|
|
45
|
+
:reason => ENV['REASON'])
|
|
46
|
+
put maintenance, "#{shared_path}/system/maintenance.html", :mode => 0644
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc %(Re-enable the web server by deleting any "maintenance.html" file.)
|
|
50
|
+
task :enable_web, :roles => :web do
|
|
51
|
+
delete "#{shared_path}/system/maintenance.html"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc <<-DESC
|
|
55
|
+
Update all servers with the latest release of the source code. All this does
|
|
56
|
+
is do a checkout (as defined by the selected scm module).
|
|
57
|
+
DESC
|
|
58
|
+
task :update_code, :roles => [:app, :db, :web] do
|
|
59
|
+
on_rollback { delete release_path, :recursive => true }
|
|
60
|
+
|
|
61
|
+
source.checkout(self)
|
|
62
|
+
|
|
63
|
+
run <<-CMD
|
|
64
|
+
rm -rf #{release_path}/log #{release_path}/public/system &&
|
|
65
|
+
ln -nfs #{shared_path}/log #{release_path}/log &&
|
|
66
|
+
ln -nfs #{shared_path}/system #{release_path}/public/system
|
|
67
|
+
CMD
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc <<-DESC
|
|
71
|
+
Rollback the latest checked-out version to the previous one by fixing the
|
|
72
|
+
symlinks and deleting the current release from all servers.
|
|
73
|
+
DESC
|
|
74
|
+
task :rollback_code, :roles => [:app, :db, :web] do
|
|
75
|
+
if releases.length < 2
|
|
76
|
+
raise "could not rollback the code because there is no prior release"
|
|
77
|
+
else
|
|
78
|
+
run <<-CMD
|
|
79
|
+
ln -nfs #{previous_release} #{current_path} &&
|
|
80
|
+
rm -rf #{current_release}
|
|
81
|
+
CMD
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc <<-DESC
|
|
86
|
+
Update the 'current' symlink to point to the latest version of
|
|
87
|
+
the application's code.
|
|
88
|
+
DESC
|
|
89
|
+
task :symlink, :roles => [:app, :db, :web] do
|
|
90
|
+
on_rollback { run "ln -nfs #{previous_release} #{current_path}" }
|
|
91
|
+
run "ln -nfs #{current_release} #{current_path}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
desc "Restart the FCGI processes on the app server."
|
|
95
|
+
task :restart, :roles => :app do
|
|
96
|
+
sudo "#{current_path}/script/process/reaper"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
set :migrate_target, :current
|
|
100
|
+
set :migrate_env, ""
|
|
101
|
+
|
|
102
|
+
desc <<-DESC
|
|
103
|
+
Run the migrate rake task. By default, it runs this in the version of the app
|
|
104
|
+
indicated by the 'current' symlink. (This means you should not invoke this task
|
|
105
|
+
until the symlink has been updated to the most recent version.) However, you
|
|
106
|
+
can specify a different release via the migrate_target variable, which must be
|
|
107
|
+
one of "current" (for the default behavior), or "latest" (for the latest release
|
|
108
|
+
to be deployed with the update_code task). You can also specify additional
|
|
109
|
+
environment variables to pass to rake via the migrate_env variable. Finally, you
|
|
110
|
+
can specify the full path to the rake executable by setting the rake variable.
|
|
111
|
+
DESC
|
|
112
|
+
task :migrate, :roles => :db, :only => { :primary => true } do
|
|
113
|
+
directory = case migrate_target.to_sym
|
|
114
|
+
when :current then current_path
|
|
115
|
+
when :latest then current_release
|
|
116
|
+
else
|
|
117
|
+
raise ArgumentError,
|
|
118
|
+
"you must specify one of current or latest for migrate_target"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
run "cd #{directory} && " +
|
|
122
|
+
"#{rake} RAILS_ENV=production #{migrate_env} migrate"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
desc <<-DESC
|
|
126
|
+
A macro-task that updates the code, fixes the symlink, and restarts the
|
|
127
|
+
application servers.
|
|
128
|
+
DESC
|
|
129
|
+
task :deploy do
|
|
130
|
+
transaction do
|
|
131
|
+
update_code
|
|
132
|
+
symlink
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
restart
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
desc <<-DESC
|
|
139
|
+
Similar to deploy, but it runs the migrate task on the new release before
|
|
140
|
+
updating the symlink. (Note that the update in this case is not atomic,
|
|
141
|
+
and transactions are not used, because migrations are not guaranteed to be
|
|
142
|
+
reversible.)
|
|
143
|
+
DESC
|
|
144
|
+
task :deploy_with_migrations do
|
|
145
|
+
update_code
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
old_migrate_target = migrate_target
|
|
149
|
+
set :migrate_target, :latest
|
|
150
|
+
migrate
|
|
151
|
+
ensure
|
|
152
|
+
set :migrate_target, old_migrate_target
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
symlink
|
|
156
|
+
|
|
157
|
+
restart
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
desc "A macro-task that rolls back the code and restarts the application servers."
|
|
161
|
+
task :rollback do
|
|
162
|
+
rollback_code
|
|
163
|
+
restart
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
desc <<-DESC
|
|
167
|
+
Displays the diff between HEAD and what was last deployed. (Not available
|
|
168
|
+
with all SCM's.)
|
|
169
|
+
DESC
|
|
170
|
+
task :diff_from_last_deploy do
|
|
171
|
+
diff = source.diff(self)
|
|
172
|
+
puts
|
|
173
|
+
puts diff
|
|
174
|
+
puts
|
|
175
|
+
end
|