capistrano 2.14.2 → 2.15.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.
- 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
|