capistrano 2.14.2 → 2.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/CHANGELOG +49 -8
- data/Gemfile +1 -1
- data/README.md +4 -4
- data/capistrano.gemspec +9 -17
- data/lib/capistrano/callback.rb +1 -1
- data/lib/capistrano/cli.rb +1 -1
- data/lib/capistrano/cli/execute.rb +3 -3
- data/lib/capistrano/cli/help.rb +3 -3
- data/lib/capistrano/cli/help.txt +23 -23
- data/lib/capistrano/cli/options.rb +12 -12
- data/lib/capistrano/command.rb +20 -7
- data/lib/capistrano/configuration/actions/invocation.rb +23 -11
- data/lib/capistrano/configuration/connections.rb +22 -15
- data/lib/capistrano/configuration/execution.rb +2 -2
- data/lib/capistrano/configuration/loading.rb +2 -2
- data/lib/capistrano/configuration/log_formatters.rb +5 -1
- data/lib/capistrano/configuration/roles.rb +1 -1
- data/lib/capistrano/configuration/servers.rb +6 -6
- data/lib/capistrano/errors.rb +4 -4
- data/lib/capistrano/ext/multistage.rb +2 -2
- data/lib/capistrano/logger.rb +14 -1
- data/lib/capistrano/recipes/deploy.rb +94 -27
- data/lib/capistrano/recipes/deploy/assets.rb +21 -18
- data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +11 -11
- data/lib/capistrano/recipes/deploy/scm.rb +1 -1
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +6 -6
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +4 -4
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +3 -3
- data/lib/capistrano/recipes/deploy/scm/git.rb +3 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +3 -3
- data/lib/capistrano/recipes/deploy/scm/none.rb +9 -5
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +5 -5
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
- data/lib/capistrano/recipes/deploy/strategy/base.rb +5 -1
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +14 -2
- data/lib/capistrano/recipes/standard.rb +1 -1
- data/lib/capistrano/server_definition.rb +1 -1
- data/lib/capistrano/shell.rb +4 -1
- data/lib/capistrano/ssh.rb +1 -1
- data/lib/capistrano/version.rb +2 -2
- data/test/cli/options_test.rb +1 -1
- data/test/cli/ui_test.rb +1 -1
- data/test/command_test.rb +11 -1
- data/test/configuration/actions/invocation_test.rb +5 -1
- data/test/configuration/callbacks_test.rb +1 -1
- data/test/configuration/connections_test.rb +19 -0
- data/test/configuration/execution_test.rb +1 -1
- data/test/configuration/namespace_dsl_test.rb +6 -6
- data/test/configuration/roles_test.rb +2 -2
- data/test/configuration/servers_test.rb +12 -12
- data/test/configuration/variables_test.rb +3 -3
- data/test/deploy/scm/bzr_test.rb +1 -1
- data/test/deploy/scm/darcs_test.rb +2 -2
- data/test/deploy/scm/git_test.rb +10 -0
- data/test/deploy/scm/mercurial_test.rb +3 -3
- data/test/deploy/scm/perforce_test.rb +1 -1
- data/test/deploy/strategy/copy_test.rb +25 -1
- data/test/extensions_test.rb +1 -1
- data/test/logger_formatting_test.rb +56 -1
- data/test/recipes_test.rb +1 -1
- data/test/server_definition_test.rb +2 -2
- data/test/shell_test.rb +12 -6
- data/test/transfer_test.rb +1 -1
- metadata +63 -31
@@ -54,7 +54,7 @@ module Capistrano
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def connect_to(server)
|
59
59
|
@options[:logger].debug "establishing connection to `#{server}' via gateway" if @options[:logger]
|
60
60
|
local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => gateway_for(server).open(server.host, server.port || 22))
|
@@ -106,7 +106,7 @@ module Capistrano
|
|
106
106
|
# establish connections to servers defined via ServerDefinition objects.
|
107
107
|
def connection_factory
|
108
108
|
@connection_factory ||= begin
|
109
|
-
if exists?(:gateway)
|
109
|
+
if exists?(:gateway) && !fetch(:gateway).nil? && !fetch(:gateway).empty?
|
110
110
|
logger.debug "establishing connection to gateway `#{fetch(:gateway).inspect}'"
|
111
111
|
GatewayConnectionFactory.new(fetch(:gateway), self)
|
112
112
|
else
|
@@ -139,44 +139,51 @@ module Capistrano
|
|
139
139
|
def teardown_connections_to(servers)
|
140
140
|
servers.each do |server|
|
141
141
|
begin
|
142
|
-
sessions.delete(server)
|
143
|
-
|
142
|
+
session = sessions.delete(server)
|
143
|
+
session.close if session
|
144
|
+
rescue IOError, Net::SSH::Disconnect
|
144
145
|
# the TCP connection is already dead
|
145
146
|
end
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
149
|
-
# Determines the set of servers within the current task's scope
|
150
|
-
|
151
|
-
# servers.
|
152
|
-
def execute_on_servers(options={})
|
153
|
-
raise ArgumentError, "expected a block" unless block_given?
|
154
|
-
|
150
|
+
# Determines the set of servers within the current task's scope
|
151
|
+
def filter_servers(options={})
|
155
152
|
if task = current_task
|
156
153
|
servers = find_servers_for_task(task, options)
|
157
154
|
|
158
155
|
if servers.empty?
|
159
156
|
if ENV['HOSTFILTER'] || task.options.merge(options)[:on_no_matching_servers] == :continue
|
160
157
|
logger.info "skipping `#{task.fully_qualified_name}' because no servers matched"
|
161
|
-
return
|
162
158
|
else
|
163
|
-
|
159
|
+
unless dry_run
|
160
|
+
raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched"
|
161
|
+
end
|
164
162
|
end
|
165
163
|
end
|
166
164
|
|
167
165
|
if task.continue_on_error?
|
168
166
|
servers.delete_if { |s| has_failed?(s) }
|
169
|
-
return if servers.empty?
|
170
167
|
end
|
171
168
|
else
|
172
169
|
servers = find_servers(options)
|
173
|
-
if servers.empty?
|
170
|
+
if servers.empty? && !dry_run
|
174
171
|
raise Capistrano::NoMatchingServersError, "no servers found to match #{options.inspect}" if options[:on_no_matching_servers] != :continue
|
175
|
-
return
|
176
172
|
end
|
177
173
|
end
|
178
174
|
|
179
175
|
servers = [servers.first] if options[:once]
|
176
|
+
[task, servers.compact]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Determines the set of servers within the current task's scope and
|
180
|
+
# establishes connections to them, and then yields that list of
|
181
|
+
# servers.
|
182
|
+
def execute_on_servers(options={})
|
183
|
+
raise ArgumentError, "expected a block" unless block_given?
|
184
|
+
|
185
|
+
task, servers = filter_servers(options)
|
186
|
+
return if servers.empty?
|
180
187
|
logger.trace "servers: #{servers.map { |s| s.host }.inspect}"
|
181
188
|
|
182
189
|
max_hosts = (options[:max_hosts] || (task && task.max_hosts) || servers.size).to_i
|
@@ -27,8 +27,8 @@ module Capistrano
|
|
27
27
|
def task_call_frames
|
28
28
|
Thread.current[:task_call_frames] ||= []
|
29
29
|
end
|
30
|
-
|
31
|
-
|
30
|
+
|
31
|
+
|
32
32
|
# The stack of tasks that have registered rollback handlers within the
|
33
33
|
# current transaction. If this is nil, then there is no transaction
|
34
34
|
# that is currently active.
|
@@ -141,9 +141,9 @@ module Capistrano
|
|
141
141
|
# look to see if this specific configuration instance has ever seen
|
142
142
|
# these arguments to require before
|
143
143
|
if @loaded_features.include?(args)
|
144
|
-
return false
|
144
|
+
return false
|
145
145
|
end
|
146
|
-
|
146
|
+
|
147
147
|
@loaded_features << args
|
148
148
|
begin
|
149
149
|
original_instance, self.class.instance = self.class.instance, self
|
@@ -68,7 +68,7 @@ module Capistrano
|
|
68
68
|
raise ArgumentError, "you must associate a server with at least one role" if roles.empty?
|
69
69
|
roles.each { |name| role(name, host, options) }
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
def role_names_for_host(host)
|
73
73
|
roles.map {|role_name, role| role_name if role.include?(host) }.compact || []
|
74
74
|
end
|
@@ -13,8 +13,8 @@ module Capistrano
|
|
13
13
|
# The options hash may include a :hosts option (which should specify
|
14
14
|
# an array of host names or ServerDefinition instances), a :roles
|
15
15
|
# option (specifying an array of roles), an :only option (specifying
|
16
|
-
# a hash of key/value pairs that any matching server must match),
|
17
|
-
# an :exception option (like :only, but the inverse), and a
|
16
|
+
# a hash of key/value pairs that any matching server must match),
|
17
|
+
# an :exception option (like :only, but the inverse), and a
|
18
18
|
# :skip_hostfilter option to ignore the HOSTFILTER environment variable
|
19
19
|
# described below.
|
20
20
|
#
|
@@ -27,7 +27,7 @@ module Capistrano
|
|
27
27
|
# will limit the result to hosts found in that (comma-separated) list.
|
28
28
|
#
|
29
29
|
# If the HOSTROLEFILTER environment variable is set, it will limit the
|
30
|
-
# result to hosts found in that (comma-separated) list of roles
|
30
|
+
# result to hosts found in that (comma-separated) list of roles
|
31
31
|
#
|
32
32
|
# Usage:
|
33
33
|
#
|
@@ -44,9 +44,9 @@ module Capistrano
|
|
44
44
|
def find_servers(options={})
|
45
45
|
return [] if options.key?(:hosts) && (options[:hosts].nil? || [] == options[:hosts])
|
46
46
|
return [] if options.key?(:roles) && (options[:roles].nil? || [] == options[:roles])
|
47
|
-
|
47
|
+
|
48
48
|
hosts = server_list_from(ENV['HOSTS'] || options[:hosts])
|
49
|
-
|
49
|
+
|
50
50
|
if hosts.any?
|
51
51
|
if options[:skip_hostfilter]
|
52
52
|
hosts.uniq
|
@@ -59,7 +59,7 @@ module Capistrano
|
|
59
59
|
|
60
60
|
only = options[:only] || {}
|
61
61
|
except = options[:except] || {}
|
62
|
-
|
62
|
+
|
63
63
|
# If we don't have a def for a role it means its bogus, skip it so higher level can handle
|
64
64
|
servers = roles.inject([]) { |list, role| list.concat(self.roles[role] || []) }
|
65
65
|
servers = servers.select { |server| only.all? { |key,value| server.options[key] == value } }
|
data/lib/capistrano/errors.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Capistrano
|
2
|
-
|
2
|
+
|
3
3
|
Error = Class.new(RuntimeError)
|
4
4
|
|
5
5
|
CaptureError = Class.new(Capistrano::Error)
|
6
6
|
NoSuchTaskError = Class.new(Capistrano::Error)
|
7
7
|
NoMatchingServersError = Class.new(Capistrano::Error)
|
8
|
-
|
8
|
+
|
9
9
|
class RemoteError < Error
|
10
10
|
attr_accessor :hosts
|
11
11
|
end
|
@@ -13,7 +13,7 @@ module Capistrano
|
|
13
13
|
ConnectionError = Class.new(Capistrano::RemoteError)
|
14
14
|
TransferError = Class.new(Capistrano::RemoteError)
|
15
15
|
CommandError = Class.new(Capistrano::RemoteError)
|
16
|
-
|
16
|
+
|
17
17
|
LocalArgumentError = Class.new(Capistrano::Error)
|
18
|
-
|
18
|
+
|
19
19
|
end
|
@@ -15,7 +15,7 @@ Capistrano::Configuration.instance.load do
|
|
15
15
|
desc "Set the target stage to `#{name}'."
|
16
16
|
task(name) do
|
17
17
|
set :stage, name.to_sym
|
18
|
-
load "#{location}/#{stage}"
|
18
|
+
load "#{location}/#{stage}" if File.exist?(File.join(location, "#{stage}.rb"))
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -39,7 +39,7 @@ Capistrano::Configuration.instance.load do
|
|
39
39
|
else
|
40
40
|
abort "No stage specified. Please specify one of: #{stages.join(', ')} (e.g. `cap #{stages.first} #{ARGV.last}')"
|
41
41
|
end
|
42
|
-
end
|
42
|
+
end
|
43
43
|
end
|
44
44
|
|
45
45
|
desc "Stub out the staging config files."
|
data/lib/capistrano/logger.rb
CHANGED
@@ -31,7 +31,7 @@ module Capistrano
|
|
31
31
|
}
|
32
32
|
|
33
33
|
# Set up default formatters
|
34
|
-
@
|
34
|
+
@default_formatters = [
|
35
35
|
# TRACE
|
36
36
|
{ :match => /command finished/, :color => :white, :style => :dim, :level => 3, :priority => -10 },
|
37
37
|
{ :match => /executing locally/, :color => :yellow, :level => 3, :priority => -20 },
|
@@ -49,8 +49,21 @@ module Capistrano
|
|
49
49
|
{ :match => /^err ::/, :color => :red, :level => 0, :priority => -10 },
|
50
50
|
{ :match => /.*/, :color => :blue, :level => 0, :priority => -20 }
|
51
51
|
]
|
52
|
+
@formatters = @default_formatters
|
52
53
|
|
53
54
|
class << self
|
55
|
+
def default_formatters
|
56
|
+
@default_formatters
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_formatters=(defaults=nil)
|
60
|
+
@default_formatters = [defaults].flatten
|
61
|
+
|
62
|
+
# reset the formatters
|
63
|
+
@formatters = @default_formatters
|
64
|
+
@sorted_formatters = nil
|
65
|
+
end
|
66
|
+
|
54
67
|
def add_formatter(options) #:nodoc:
|
55
68
|
@formatters.push(options)
|
56
69
|
@sorted_formatters = nil
|
@@ -57,13 +57,13 @@ _cset(:shared_path) { File.join(deploy_to, shared_dir) }
|
|
57
57
|
_cset(:current_path) { File.join(deploy_to, current_dir) }
|
58
58
|
_cset(:release_path) { File.join(releases_path, release_name) }
|
59
59
|
|
60
|
-
_cset(:releases) { capture("ls -x #{releases_path}", :except => { :no_release => true }).split.sort }
|
60
|
+
_cset(:releases) { capture("#{try_sudo} ls -x #{releases_path}", :except => { :no_release => true }).split.sort }
|
61
61
|
_cset(:current_release) { releases.length > 0 ? File.join(releases_path, releases.last) : nil }
|
62
62
|
_cset(:previous_release) { releases.length > 1 ? File.join(releases_path, releases[-2]) : nil }
|
63
63
|
|
64
|
-
_cset(:current_revision) { capture("cat #{current_path}/REVISION", :except => { :no_release => true }).chomp }
|
65
|
-
_cset(:latest_revision) { capture("cat #{current_release}/REVISION", :except => { :no_release => true }).chomp }
|
66
|
-
_cset(:previous_revision) { capture("cat #{previous_release}/REVISION", :except => { :no_release => true }).chomp if previous_release }
|
64
|
+
_cset(:current_revision) { capture("#{try_sudo} cat #{current_path}/REVISION", :except => { :no_release => true }).chomp }
|
65
|
+
_cset(:latest_revision) { capture("#{try_sudo} cat #{current_release}/REVISION", :except => { :no_release => true }).chomp }
|
66
|
+
_cset(:previous_revision) { capture("#{try_sudo} cat #{previous_release}/REVISION", :except => { :no_release => true }).chomp if previous_release }
|
67
67
|
|
68
68
|
_cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }
|
69
69
|
|
@@ -74,14 +74,16 @@ _cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }
|
|
74
74
|
# standalone case, or during deployment.
|
75
75
|
_cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release }
|
76
76
|
|
77
|
+
_cset :maintenance_basename, "maintenance"
|
78
|
+
_cset(:maintenance_template_path) { File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml") }
|
77
79
|
# =========================================================================
|
78
80
|
# These are helper methods that will be available to your recipes.
|
79
81
|
# =========================================================================
|
80
82
|
|
81
|
-
# Checks known version control directories to intelligently set the version
|
82
|
-
# control in-use. For example, if a .svn directory exists in the project,
|
83
|
-
# it will set the :scm variable to :subversion, if a .git directory exists
|
84
|
-
# in the project, it will set the :scm variable to :git and so on. If no
|
83
|
+
# Checks known version control directories to intelligently set the version
|
84
|
+
# control in-use. For example, if a .svn directory exists in the project,
|
85
|
+
# it will set the :scm variable to :subversion, if a .git directory exists
|
86
|
+
# in the project, it will set the :scm variable to :git and so on. If no
|
85
87
|
# directory is found, it will default to :git.
|
86
88
|
def scm_default
|
87
89
|
if File.exist? '.git'
|
@@ -127,6 +129,9 @@ end
|
|
127
129
|
# logs the command then executes it locally.
|
128
130
|
# returns the command output as a string
|
129
131
|
def run_locally(cmd)
|
132
|
+
if dry_run
|
133
|
+
return logger.debug "executing locally: #{cmd.inspect}"
|
134
|
+
end
|
130
135
|
logger.trace "executing locally: #{cmd.inspect}" if logger
|
131
136
|
output_on_stdout = nil
|
132
137
|
elapsed = Benchmark.realtime do
|
@@ -153,7 +158,7 @@ end
|
|
153
158
|
# THUS, if you want to try to run something via sudo, and what to use the
|
154
159
|
# root user, you'd just to try_sudo('something'). If you wanted to try_sudo as
|
155
160
|
# someone else, you'd just do try_sudo('something', :as => "bob"). If you
|
156
|
-
# always wanted sudo to run as a particular user, you could do
|
161
|
+
# always wanted sudo to run as a particular user, you could do
|
157
162
|
# set(:admin_runner, "bob").
|
158
163
|
def try_sudo(*args)
|
159
164
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
@@ -262,7 +267,7 @@ namespace :deploy do
|
|
262
267
|
public/stylesheets, and public/javascripts so that the times are \
|
263
268
|
consistent (so that asset timestamping works). This touch process \
|
264
269
|
is only carried out if the :normalize_asset_timestamps variable is \
|
265
|
-
set to true, which is the default The asset directories can be overridden \
|
270
|
+
set to true, which is the default. The asset directories can be overridden \
|
266
271
|
using the :public_children variable.
|
267
272
|
DESC
|
268
273
|
task :finalize_update, :except => { :no_release => true } do
|
@@ -279,7 +284,7 @@ namespace :deploy do
|
|
279
284
|
"mkdir -p -- #{escaped_release}/#{dir.slice(0..(dir.rindex('/'))).shellescape}"]
|
280
285
|
else
|
281
286
|
commands << "rm -rf -- #{escaped_release}/#{d}"
|
282
|
-
|
287
|
+
end
|
283
288
|
commands << "ln -s -- #{shared_path}/#{dir.split('/').last.shellescape} #{escaped_release}/#{d}"
|
284
289
|
end
|
285
290
|
|
@@ -287,8 +292,10 @@ namespace :deploy do
|
|
287
292
|
|
288
293
|
if fetch(:normalize_asset_timestamps, true)
|
289
294
|
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
|
290
|
-
asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).
|
291
|
-
|
295
|
+
asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).
|
296
|
+
map { |p| "#{latest_release}/public/#{p}" }.
|
297
|
+
map { |p| p.shellescape }.join(" ")
|
298
|
+
run("find #{asset_paths} -exec touch -t #{stamp} -- {} ';'; true",
|
292
299
|
:env => { "TZ" => "UTC" }) if asset_paths.any?
|
293
300
|
end
|
294
301
|
end
|
@@ -313,13 +320,13 @@ namespace :deploy do
|
|
313
320
|
task :create_symlink, :except => { :no_release => true } do
|
314
321
|
on_rollback do
|
315
322
|
if previous_release
|
316
|
-
run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true"
|
323
|
+
run "#{try_sudo} rm -f #{current_path}; #{try_sudo} ln -s #{previous_release} #{current_path}; true"
|
317
324
|
else
|
318
325
|
logger.important "no previous release to rollback to, rollback of symlink skipped"
|
319
326
|
end
|
320
327
|
end
|
321
328
|
|
322
|
-
run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
|
329
|
+
run "#{try_sudo} rm -f #{current_path} && #{try_sudo} ln -s #{latest_release} #{current_path}"
|
323
330
|
end
|
324
331
|
|
325
332
|
desc <<-DESC
|
@@ -363,7 +370,7 @@ namespace :deploy do
|
|
363
370
|
DESC
|
364
371
|
task :revision, :except => { :no_release => true } do
|
365
372
|
if previous_release
|
366
|
-
run "rm #{current_path}; ln -s #{previous_release} #{current_path}"
|
373
|
+
run "#{try_sudo} rm #{current_path}; #{try_sudo} ln -s #{previous_release} #{current_path}"
|
367
374
|
else
|
368
375
|
abort "could not rollback the code because there is no prior release"
|
369
376
|
end
|
@@ -375,7 +382,7 @@ namespace :deploy do
|
|
375
382
|
(if ever) need to be called directly.
|
376
383
|
DESC
|
377
384
|
task :cleanup, :except => { :no_release => true } do
|
378
|
-
run "if [ `readlink #{current_path}` != #{current_release} ]; then rm -rf #{current_release}; fi"
|
385
|
+
run "if [ `readlink #{current_path}` != #{current_release} ]; then #{try_sudo} rm -rf #{current_release}; fi"
|
379
386
|
end
|
380
387
|
|
381
388
|
desc <<-DESC
|
@@ -455,16 +462,7 @@ namespace :deploy do
|
|
455
462
|
DESC
|
456
463
|
task :cleanup, :except => { :no_release => true } do
|
457
464
|
count = fetch(:keep_releases, 5).to_i
|
458
|
-
|
459
|
-
if count >= local_releases.length
|
460
|
-
logger.important "no old releases to clean up"
|
461
|
-
else
|
462
|
-
logger.info "keeping #{count} of #{local_releases.length} deployed releases"
|
463
|
-
directories = (local_releases - local_releases.last(count)).map { |release|
|
464
|
-
File.join(releases_path, release) }.join(" ")
|
465
|
-
|
466
|
-
try_sudo "rm -rf #{directories}"
|
467
|
-
end
|
465
|
+
try_sudo "ls -1dt #{releases_path}/* | tail -n +#{count + 1} | xargs rm -rf"
|
468
466
|
end
|
469
467
|
|
470
468
|
desc <<-DESC
|
@@ -555,4 +553,73 @@ namespace :deploy do
|
|
555
553
|
system(source.local.log(from))
|
556
554
|
end
|
557
555
|
end
|
556
|
+
|
557
|
+
namespace :web do
|
558
|
+
desc <<-DESC
|
559
|
+
Present a maintenance page to visitors. Disables your application's web \
|
560
|
+
interface by writing a "#{maintenance_basename}.html" file to each web server. The \
|
561
|
+
servers must be configured to detect the presence of this file, and if \
|
562
|
+
it is present, always display it instead of performing the request.
|
563
|
+
|
564
|
+
By default, the maintenance page will just say the site is down for \
|
565
|
+
"maintenance", and will be back "shortly", but you can customize the \
|
566
|
+
page by specifying the REASON and UNTIL environment variables:
|
567
|
+
|
568
|
+
$ cap deploy:web:disable \\
|
569
|
+
REASON="hardware upgrade" \\
|
570
|
+
UNTIL="12pm Central Time"
|
571
|
+
|
572
|
+
You can use a different template for the maintenance page by setting the \
|
573
|
+
:maintenance_template_path variable in your deploy.rb file. The template file \
|
574
|
+
should either be a plaintext or an erb file.
|
575
|
+
|
576
|
+
Further customization will require that you write your own task.
|
577
|
+
DESC
|
578
|
+
task :disable, :roles => :web, :except => { :no_release => true } do
|
579
|
+
require 'erb'
|
580
|
+
on_rollback { run "rm -f #{shared_path}/system/#{maintenance_basename}.html" }
|
581
|
+
|
582
|
+
warn <<-EOHTACCESS
|
583
|
+
|
584
|
+
# Please add something like this to your site's Apache htaccess to redirect users to the maintenance page.
|
585
|
+
# More Info: http://www.shiftcommathree.com/articles/make-your-rails-maintenance-page-respond-with-a-503
|
586
|
+
|
587
|
+
ErrorDocument 503 /system/#{maintenance_basename}.html
|
588
|
+
RewriteEngine On
|
589
|
+
RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$
|
590
|
+
RewriteCond %{DOCUMENT_ROOT}/system/#{maintenance_basename}.html -f
|
591
|
+
RewriteCond %{SCRIPT_FILENAME} !#{maintenance_basename}.html
|
592
|
+
RewriteRule ^.*$ - [redirect=503,last]
|
593
|
+
|
594
|
+
# Or if you are using Nginx add this to your server config:
|
595
|
+
|
596
|
+
if (-f $document_root/system/maintenance.html) {
|
597
|
+
return 503;
|
598
|
+
}
|
599
|
+
error_page 503 @maintenance;
|
600
|
+
location @maintenance {
|
601
|
+
rewrite ^(.*)$ /system/maintenance.html break;
|
602
|
+
break;
|
603
|
+
}
|
604
|
+
EOHTACCESS
|
605
|
+
|
606
|
+
reason = ENV['REASON']
|
607
|
+
deadline = ENV['UNTIL']
|
608
|
+
|
609
|
+
template = File.read(maintenance_template_path)
|
610
|
+
result = ERB.new(template).result(binding)
|
611
|
+
|
612
|
+
put result, "#{shared_path}/system/#{maintenance_basename}.html", :mode => 0644
|
613
|
+
end
|
614
|
+
|
615
|
+
desc <<-DESC
|
616
|
+
Makes the application web-accessible again. Removes the \
|
617
|
+
"#{maintenance_basename}.html" page generated by deploy:web:disable, which (if your \
|
618
|
+
web servers are configured correctly) will make your application \
|
619
|
+
web-accessible again.
|
620
|
+
DESC
|
621
|
+
task :enable, :roles => :web, :except => { :no_release => true } do
|
622
|
+
run "rm -f #{shared_path}/system/#{maintenance_basename}.html"
|
623
|
+
end
|
624
|
+
end
|
558
625
|
end
|