capistrano 2.0.0 → 2.15.5

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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +737 -18
  5. data/Gemfile +13 -0
  6. data/README.md +94 -0
  7. data/Rakefile +11 -0
  8. data/bin/cap +0 -0
  9. data/bin/capify +37 -22
  10. data/capistrano.gemspec +40 -0
  11. data/lib/capistrano/callback.rb +5 -1
  12. data/lib/capistrano/cli/execute.rb +10 -7
  13. data/lib/capistrano/cli/help.rb +39 -16
  14. data/lib/capistrano/cli/help.txt +44 -16
  15. data/lib/capistrano/cli/options.rb +71 -11
  16. data/lib/capistrano/cli/ui.rb +13 -1
  17. data/lib/capistrano/cli.rb +5 -5
  18. data/lib/capistrano/command.rb +215 -58
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
  20. data/lib/capistrano/configuration/actions/inspect.rb +3 -3
  21. data/lib/capistrano/configuration/actions/invocation.rb +224 -22
  22. data/lib/capistrano/configuration/alias_task.rb +26 -0
  23. data/lib/capistrano/configuration/callbacks.rb +26 -27
  24. data/lib/capistrano/configuration/connections.rb +130 -52
  25. data/lib/capistrano/configuration/execution.rb +34 -18
  26. data/lib/capistrano/configuration/loading.rb +99 -6
  27. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  28. data/lib/capistrano/configuration/namespaces.rb +45 -12
  29. data/lib/capistrano/configuration/roles.rb +28 -2
  30. data/lib/capistrano/configuration/servers.rb +51 -10
  31. data/lib/capistrano/configuration/variables.rb +3 -3
  32. data/lib/capistrano/configuration.rb +20 -4
  33. data/lib/capistrano/errors.rb +12 -8
  34. data/lib/capistrano/ext/multistage.rb +67 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +1 -1
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +112 -5
  39. data/lib/capistrano/processable.rb +55 -0
  40. data/lib/capistrano/recipes/compat.rb +2 -2
  41. data/lib/capistrano/recipes/deploy/assets.rb +201 -0
  42. data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
  43. data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
  44. data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
  45. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  46. data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
  47. data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
  48. data/lib/capistrano/recipes/deploy/scm/cvs.rb +11 -9
  49. data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
  50. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  51. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
  52. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  53. data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
  54. data/lib/capistrano/recipes/deploy/scm/subversion.rb +36 -18
  55. data/lib/capistrano/recipes/deploy/scm.rb +1 -1
  56. data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
  57. data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
  58. data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
  59. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
  60. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  61. data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
  62. data/lib/capistrano/recipes/deploy.rb +265 -123
  63. data/lib/capistrano/recipes/standard.rb +1 -1
  64. data/lib/capistrano/role.rb +102 -0
  65. data/lib/capistrano/server_definition.rb +6 -1
  66. data/lib/capistrano/shell.rb +30 -33
  67. data/lib/capistrano/ssh.rb +46 -60
  68. data/lib/capistrano/task_definition.rb +16 -8
  69. data/lib/capistrano/transfer.rb +218 -0
  70. data/lib/capistrano/version.rb +6 -17
  71. data/lib/capistrano.rb +4 -1
  72. data/test/cli/execute_test.rb +3 -3
  73. data/test/cli/help_test.rb +33 -7
  74. data/test/cli/options_test.rb +109 -6
  75. data/test/cli/ui_test.rb +2 -2
  76. data/test/cli_test.rb +3 -3
  77. data/test/command_test.rb +144 -124
  78. data/test/configuration/actions/file_transfer_test.rb +41 -20
  79. data/test/configuration/actions/inspect_test.rb +21 -7
  80. data/test/configuration/actions/invocation_test.rb +123 -30
  81. data/test/configuration/alias_task_test.rb +118 -0
  82. data/test/configuration/callbacks_test.rb +41 -46
  83. data/test/configuration/connections_test.rb +187 -36
  84. data/test/configuration/execution_test.rb +18 -2
  85. data/test/configuration/loading_test.rb +33 -4
  86. data/test/configuration/namespace_dsl_test.rb +54 -5
  87. data/test/configuration/roles_test.rb +114 -4
  88. data/test/configuration/servers_test.rb +97 -4
  89. data/test/configuration/variables_test.rb +12 -2
  90. data/test/configuration_test.rb +9 -13
  91. data/test/deploy/local_dependency_test.rb +76 -0
  92. data/test/deploy/remote_dependency_test.rb +146 -0
  93. data/test/deploy/scm/accurev_test.rb +23 -0
  94. data/test/deploy/scm/base_test.rb +1 -1
  95. data/test/deploy/scm/bzr_test.rb +51 -0
  96. data/test/deploy/scm/darcs_test.rb +37 -0
  97. data/test/deploy/scm/git_test.rb +274 -0
  98. data/test/deploy/scm/mercurial_test.rb +134 -0
  99. data/test/deploy/scm/none_test.rb +35 -0
  100. data/test/deploy/scm/perforce_test.rb +23 -0
  101. data/test/deploy/scm/subversion_test.rb +68 -0
  102. data/test/deploy/strategy/copy_test.rb +240 -26
  103. data/test/extensions_test.rb +2 -2
  104. data/test/logger_formatting_test.rb +149 -0
  105. data/test/logger_test.rb +13 -2
  106. data/test/recipes_test.rb +25 -0
  107. data/test/role_test.rb +11 -0
  108. data/test/server_definition_test.rb +15 -2
  109. data/test/shell_test.rb +33 -1
  110. data/test/ssh_test.rb +40 -24
  111. data/test/task_definition_test.rb +18 -2
  112. data/test/transfer_test.rb +168 -0
  113. data/test/utils.rb +28 -33
  114. metadata +215 -101
  115. data/MIT-LICENSE +0 -20
  116. data/README +0 -43
  117. data/examples/sample.rb +0 -14
  118. data/lib/capistrano/gateway.rb +0 -131
  119. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  120. data/lib/capistrano/recipes/upgrade.rb +0 -33
  121. data/lib/capistrano/upload.rb +0 -146
  122. data/test/gateway_test.rb +0 -167
  123. data/test/upload_test.rb +0 -131
  124. data/test/version_test.rb +0 -24
@@ -1,6 +1,9 @@
1
- require 'yaml'
2
- require 'capistrano/recipes/deploy/scm'
3
- require 'capistrano/recipes/deploy/strategy'
1
+ require "benchmark"
2
+ require "set"
3
+ require "shellwords"
4
+ require "yaml"
5
+ require "capistrano/recipes/deploy/scm"
6
+ require "capistrano/recipes/deploy/strategy"
4
7
 
5
8
  def _cset(name, *args, &block)
6
9
  unless exists?(name)
@@ -21,12 +24,15 @@ _cset(:repository) { abort "Please specify the repository that houses your appl
21
24
  # are not sufficient.
22
25
  # =========================================================================
23
26
 
24
- _cset :scm, :subversion
27
+ _cset(:scm) { scm_default }
25
28
  _cset :deploy_via, :checkout
26
29
 
27
30
  _cset(:deploy_to) { "/u/apps/#{application}" }
28
31
  _cset(:revision) { source.head }
29
32
 
33
+ _cset :rails_env, "production"
34
+ _cset :rake, "rake"
35
+
30
36
  # =========================================================================
31
37
  # These variables should NOT be changed unless you are very confident in
32
38
  # what you are doing. Make sure you understand all the implications of your
@@ -34,23 +40,30 @@ _cset(:revision) { source.head }
34
40
  # =========================================================================
35
41
 
36
42
  _cset(:source) { Capistrano::Deploy::SCM.new(scm, self) }
37
- _cset(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { `#{cmd}` } } }
43
+ _cset(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } } }
38
44
 
39
45
  _cset(:strategy) { Capistrano::Deploy::Strategy.new(deploy_via, self) }
40
46
 
47
+ # If overriding release name, please also select an appropriate setting for :releases below.
41
48
  _cset(:release_name) { set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S") }
42
- _cset(:releases_path) { File.join(deploy_to, "releases") }
43
- _cset(:shared_path) { File.join(deploy_to, "shared") }
44
- _cset(:current_path) { File.join(deploy_to, "current") }
49
+
50
+ _cset :version_dir, "releases"
51
+ _cset :shared_dir, "shared"
52
+ _cset :shared_children, %w(public/system log tmp/pids)
53
+ _cset :current_dir, "current"
54
+
55
+ _cset(:releases_path) { File.join(deploy_to, version_dir) }
56
+ _cset(:shared_path) { File.join(deploy_to, shared_dir) }
57
+ _cset(:current_path) { File.join(deploy_to, current_dir) }
45
58
  _cset(:release_path) { File.join(releases_path, release_name) }
46
59
 
47
- _cset(:releases) { capture("ls -x #{releases_path}").split.sort }
48
- _cset(:current_release) { File.join(releases_path, releases.last) }
49
- _cset(:previous_release) { File.join(releases_path, releases[-2]) }
60
+ _cset(:releases) { capture("#{try_sudo} ls -x #{releases_path}", :except => { :no_release => true }).split.sort }
61
+ _cset(:current_release) { releases.length > 0 ? File.join(releases_path, releases.last) : nil }
62
+ _cset(:previous_release) { releases.length > 1 ? File.join(releases_path, releases[-2]) : nil }
50
63
 
51
- _cset(:current_revision) { capture("cat #{current_path}/REVISION").chomp }
52
- _cset(:latest_revision) { capture("cat #{current_release}/REVISION").chomp }
53
- _cset(:previous_revision) { capture("cat #{previous_release}/REVISION").chomp }
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 }
54
67
 
55
68
  _cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }
56
69
 
@@ -61,10 +74,39 @@ _cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }
61
74
  # standalone case, or during deployment.
62
75
  _cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release }
63
76
 
77
+ _cset :maintenance_basename, "maintenance"
78
+ _cset(:maintenance_template_path) { File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml") }
64
79
  # =========================================================================
65
80
  # These are helper methods that will be available to your recipes.
66
81
  # =========================================================================
67
82
 
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
87
+ # directory is found, it will default to :git.
88
+ def scm_default
89
+ if File.exist? '.git'
90
+ :git
91
+ elsif File.exist? '.accurev'
92
+ :accurev
93
+ elsif File.exist? '.bzr'
94
+ :bzr
95
+ elsif File.exist? '.cvs'
96
+ :cvs
97
+ elsif File.exist? '_darcs'
98
+ :darcs
99
+ elsif File.exist? '.hg'
100
+ :mercurial
101
+ elsif File.exist? '.perforce'
102
+ :perforce
103
+ elsif File.exist? '.svn'
104
+ :subversion
105
+ else
106
+ :none
107
+ end
108
+ end
109
+
68
110
  # Auxiliary helper method for the `deploy:check' task. Lets you set up your
69
111
  # own dependencies.
70
112
  def depend(location, type, *args)
@@ -84,6 +126,64 @@ ensure
84
126
  ENV[name] = saved
85
127
  end
86
128
 
129
+ # logs the command then executes it locally.
130
+ # returns the command output as a string
131
+ def run_locally(cmd)
132
+ if dry_run
133
+ return logger.debug "executing locally: #{cmd.inspect}"
134
+ end
135
+ logger.trace "executing locally: #{cmd.inspect}" if logger
136
+ output_on_stdout = nil
137
+ elapsed = Benchmark.realtime do
138
+ output_on_stdout = `#{cmd}`
139
+ end
140
+ if $?.to_i > 0 # $? is command exit code (posix style)
141
+ raise Capistrano::LocalArgumentError, "Command #{cmd} returned status code #{$?}"
142
+ end
143
+ logger.trace "command finished in #{(elapsed * 1000).round}ms" if logger
144
+ output_on_stdout
145
+ end
146
+
147
+
148
+ # If a command is given, this will try to execute the given command, as
149
+ # described below. Otherwise, it will return a string for use in embedding in
150
+ # another command, for executing that command as described below.
151
+ #
152
+ # If :run_method is :sudo (or :use_sudo is true), this executes the given command
153
+ # via +sudo+. Otherwise is uses +run+. If :as is given as a key, it will be
154
+ # passed as the user to sudo as, if using sudo. If the :as key is not given,
155
+ # it will default to whatever the value of the :admin_runner variable is,
156
+ # which (by default) is unset.
157
+ #
158
+ # THUS, if you want to try to run something via sudo, and what to use the
159
+ # root user, you'd just to try_sudo('something'). If you wanted to try_sudo as
160
+ # someone else, you'd just do try_sudo('something', :as => "bob"). If you
161
+ # always wanted sudo to run as a particular user, you could do
162
+ # set(:admin_runner, "bob").
163
+ def try_sudo(*args)
164
+ options = args.last.is_a?(Hash) ? args.pop : {}
165
+ command = args.shift
166
+ raise ArgumentError, "too many arguments" if args.any?
167
+
168
+ as = options.fetch(:as, fetch(:admin_runner, nil))
169
+ via = fetch(:run_method, :sudo)
170
+ if command
171
+ invoke_command(command, :via => via, :as => as)
172
+ elsif via == :sudo
173
+ sudo(:as => as)
174
+ else
175
+ ""
176
+ end
177
+ end
178
+
179
+ # Same as sudo, but tries sudo with :as set to the value of the :runner
180
+ # variable (which defaults to "app").
181
+ def try_runner(*args)
182
+ options = args.last.is_a?(Hash) ? args.pop : {}
183
+ args << options.merge(:as => fetch(:runner, "app"))
184
+ try_sudo(*args)
185
+ end
186
+
87
187
  # =========================================================================
88
188
  # These are the tasks that are available to help with deploying web apps,
89
189
  # and specifically, Rails applications. You can have cap give you a summary
@@ -105,19 +205,20 @@ namespace :deploy do
105
205
  desc <<-DESC
106
206
  Prepares one or more servers for deployment. Before you can use any \
107
207
  of the Capistrano deployment tasks with your project, you will need to \
108
- make sure all of your servers have been prepared with `cap setup'. When \
208
+ make sure all of your servers have been prepared with `cap deploy:setup'. When \
109
209
  you add a new server to your cluster, you can easily run the setup task \
110
210
  on just that server by specifying the HOSTS environment variable:
111
211
 
112
- $ cap HOSTS=new.server.com setup
212
+ $ cap HOSTS=new.server.com deploy:setup
113
213
 
114
214
  It is safe to run this task on servers that have already been set up; it \
115
215
  will not destroy any deployed revisions or data.
116
216
  DESC
117
217
  task :setup, :except => { :no_release => true } do
118
218
  dirs = [deploy_to, releases_path, shared_path]
119
- dirs += %w(system log pids).map { |d| File.join(shared_path, d) }
120
- run "umask 02 && mkdir -p #{dirs.join(' ')}"
219
+ dirs += shared_children.map { |d| File.join(shared_path, d.split('/').last) }
220
+ run "#{try_sudo} mkdir -p #{dirs.join(' ')}"
221
+ run "#{try_sudo} chmod g+w #{dirs.join(' ')}" if fetch(:group_writable, true)
121
222
  end
122
223
 
123
224
  desc <<-DESC
@@ -131,7 +232,7 @@ namespace :deploy do
131
232
  task :update do
132
233
  transaction do
133
234
  update_code
134
- symlink
235
+ create_symlink
135
236
  end
136
237
  end
137
238
 
@@ -164,25 +265,47 @@ namespace :deploy do
164
265
  symlinks to the shared directory for the log, system, and tmp/pids \
165
266
  directories, and will lastly touch all assets in public/images, \
166
267
  public/stylesheets, and public/javascripts so that the times are \
167
- consistent (so that asset timestamping works).
268
+ consistent (so that asset timestamping works). This touch process \
269
+ is only carried out if the :normalize_asset_timestamps variable is \
270
+ set to true, which is the default. The asset directories can be overridden \
271
+ using the :public_children variable.
168
272
  DESC
169
273
  task :finalize_update, :except => { :no_release => true } do
170
- run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
274
+ escaped_release = latest_release.to_s.shellescape
275
+ commands = []
276
+ commands << "chmod -R -- g+w #{escaped_release}" if fetch(:group_writable, true)
171
277
 
172
278
  # mkdir -p is making sure that the directories are there for some SCM's that don't
173
279
  # save empty folders
174
- run <<-CMD
175
- rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &&
176
- mkdir -p #{latest_release}/public &&
177
- mkdir -p #{latest_release}/tmp &&
178
- ln -s #{shared_path}/log #{latest_release}/log &&
179
- ln -s #{shared_path}/system #{latest_release}/public/system &&
180
- ln -s #{shared_path}/pids #{latest_release}/tmp/pids
181
- CMD
182
-
183
- stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
184
- asset_paths = %w(images stylesheets javascripts).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
185
- run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
280
+ shared_children.map do |dir|
281
+ d = dir.shellescape
282
+ if (dir.rindex('/')) then
283
+ commands += ["rm -rf -- #{escaped_release}/#{d}",
284
+ "mkdir -p -- #{escaped_release}/#{dir.slice(0..(dir.rindex('/'))).shellescape}"]
285
+ else
286
+ commands << "rm -rf -- #{escaped_release}/#{d}"
287
+ end
288
+ commands << "ln -s -- #{shared_path}/#{dir.split('/').last.shellescape} #{escaped_release}/#{d}"
289
+ end
290
+
291
+ run commands.join(' && ') if commands.any?
292
+
293
+ if fetch(:normalize_asset_timestamps, true)
294
+ stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
295
+ asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).
296
+ map { |p| "#{latest_release}/public/#{p}" }.
297
+ map { |p| p.shellescape }
298
+ run("find #{asset_paths.join(" ")} -exec touch -t #{stamp} -- {} ';'; true",
299
+ :env => { "TZ" => "UTC" }) if asset_paths.any?
300
+ end
301
+ end
302
+
303
+ desc <<-DESC
304
+ Deprecated API. This has become deploy:create_symlink, please update your recipes
305
+ DESC
306
+ task :symlink, :except => { :no_release => true } do
307
+ Kernel.warn "[Deprecation Warning] This API has changed, please hook `deploy:create_symlink` instead of `deploy:symlink`."
308
+ create_symlink
186
309
  end
187
310
 
188
311
  desc <<-DESC
@@ -194,9 +317,16 @@ namespace :deploy do
194
317
  deploy, including `restart') or the 'update' task (which does everything \
195
318
  except `restart').
196
319
  DESC
197
- task :symlink, :except => { :no_release => true } do
198
- on_rollback { run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true" }
199
- run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
320
+ task :create_symlink, :except => { :no_release => true } do
321
+ on_rollback do
322
+ if previous_release
323
+ run "#{try_sudo} rm -f #{current_path}; #{try_sudo} ln -s #{previous_release} #{current_path}; true"
324
+ else
325
+ logger.important "no previous release to rollback to, rollback of symlink skipped"
326
+ end
327
+ end
328
+
329
+ run "#{try_sudo} rm -f #{current_path} && #{try_sudo} ln -s #{latest_release} #{current_path}"
200
330
  end
201
331
 
202
332
  desc <<-DESC
@@ -209,60 +339,73 @@ namespace :deploy do
209
339
  To use this task, specify the files and directories you want to copy as a \
210
340
  comma-delimited list in the FILES environment variable. All directories \
211
341
  will be processed recursively, with all files being pushed to the \
212
- deployment servers. Any file or directory starting with a '.' character \
213
- will be ignored.
342
+ deployment servers.
214
343
 
215
344
  $ cap deploy:upload FILES=templates,controller.rb
345
+
346
+ Dir globs are also supported:
347
+
348
+ $ cap deploy:upload FILES='config/apache/*.conf'
216
349
  DESC
217
350
  task :upload, :except => { :no_release => true } do
218
- files = (ENV["FILES"] || "").
219
- split(",").
220
- map { |f| f.strip!; File.directory?(f) ? Dir["#{f}/**/*"] : f }.
221
- flatten.
222
- reject { |f| File.directory?(f) || File.basename(f)[0] == ?. }
223
-
224
- abort "Please specify at least one file to update (via the FILES environment variable)" if files.empty?
351
+ files = (ENV["FILES"] || "").split(",").map { |f| Dir[f.strip] }.flatten
352
+ abort "Please specify at least one file or directory to update (via the FILES environment variable)" if files.empty?
225
353
 
226
- files.each do |file|
227
- put File.read(file), File.join(current_path, file)
228
- end
354
+ files.each { |file| top.upload(file, File.join(current_path, file)) }
229
355
  end
230
356
 
231
357
  desc <<-DESC
232
- Restarts your application. This works by calling the script/process/reaper \
233
- script under the current path. By default, this will be invoked via sudo, \
234
- but if you are in an environment where sudo is not an option, or is not \
235
- allowed, you can indicate that restarts should use `run' instead by \
236
- setting the `use_sudo' variable to false:
237
-
238
- set :use_sudo, false
358
+ Blank task exists as a hook into which to install your own environment \
359
+ specific behaviour.
239
360
  DESC
240
361
  task :restart, :roles => :app, :except => { :no_release => true } do
241
- invoke_command "#{current_path}/script/process/reaper", :via => run_method
362
+ # Empty Task to overload with your platform specifics
242
363
  end
243
364
 
244
- desc <<-DESC
245
- Rolls back to the previously deployed version. The `current' symlink will \
246
- be updated to point at the previously deployed version, and then the \
247
- current release will be removed from the servers. You'll generally want \
248
- to call `rollback' instead, as it performs a `restart' as well.
249
- DESC
250
- task :rollback_code, :except => { :no_release => true } do
251
- if releases.length < 2
252
- abort "could not rollback the code because there is no prior release"
253
- else
254
- run "rm #{current_path}; ln -s #{previous_release} #{current_path} && rm -rf #{current_release}"
365
+ namespace :rollback do
366
+ desc <<-DESC
367
+ [internal] Points the current symlink at the previous revision.
368
+ This is called by the rollback sequence, and should rarely (if
369
+ ever) need to be called directly.
370
+ DESC
371
+ task :revision, :except => { :no_release => true } do
372
+ if previous_release
373
+ run "#{try_sudo} rm #{current_path}; #{try_sudo} ln -s #{previous_release} #{current_path}"
374
+ else
375
+ abort "could not rollback the code because there is no prior release"
376
+ end
255
377
  end
256
- end
257
378
 
258
- desc <<-DESC
259
- Rolls back to a previous version and restarts. This is handy if you ever \
260
- discover that you've deployed a lemon; `cap rollback' and you're right \
261
- back where you were, on the previously deployed version.
262
- DESC
263
- task :rollback do
264
- rollback_code
265
- restart
379
+ desc <<-DESC
380
+ [internal] Removes the most recently deployed release.
381
+ This is called by the rollback sequence, and should rarely
382
+ (if ever) need to be called directly.
383
+ DESC
384
+ task :cleanup, :except => { :no_release => true } do
385
+ run "if [ `readlink #{current_path}` != #{current_release} ]; then #{try_sudo} rm -rf #{current_release}; fi"
386
+ end
387
+
388
+ desc <<-DESC
389
+ Rolls back to the previously deployed version. The `current' symlink will \
390
+ be updated to point at the previously deployed version, and then the \
391
+ current release will be removed from the servers. You'll generally want \
392
+ to call `rollback' instead, as it performs a `restart' as well.
393
+ DESC
394
+ task :code, :except => { :no_release => true } do
395
+ revision
396
+ cleanup
397
+ end
398
+
399
+ desc <<-DESC
400
+ Rolls back to a previous version and restarts. This is handy if you ever \
401
+ discover that you've deployed a lemon; `cap rollback' and you're right \
402
+ back where you were, on the previously deployed version.
403
+ DESC
404
+ task :default do
405
+ revision
406
+ restart
407
+ cleanup
408
+ end
266
409
  end
267
410
 
268
411
  desc <<-DESC
@@ -288,11 +431,11 @@ namespace :deploy do
288
431
 
289
432
  directory = case migrate_target.to_sym
290
433
  when :current then current_path
291
- when :latest then current_release
434
+ when :latest then latest_release
292
435
  else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
293
436
  end
294
437
 
295
- run "cd #{directory}; #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
438
+ run "cd #{directory} && #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
296
439
  end
297
440
 
298
441
  desc <<-DESC
@@ -306,7 +449,7 @@ namespace :deploy do
306
449
  set :migrate_target, :latest
307
450
  update_code
308
451
  migrate
309
- symlink
452
+ create_symlink
310
453
  restart
311
454
  end
312
455
 
@@ -319,16 +462,7 @@ namespace :deploy do
319
462
  DESC
320
463
  task :cleanup, :except => { :no_release => true } do
321
464
  count = fetch(:keep_releases, 5).to_i
322
- if count >= releases.length
323
- logger.important "no old releases to clean up"
324
- else
325
- logger.info "keeping #{count} of #{releases.length} deployed releases"
326
-
327
- directories = (releases - releases.last(count)).map { |release|
328
- File.join(releases_path, release) }.join(" ")
329
-
330
- invoke_command "rm -rf #{directories}", :via => run_method
331
- end
465
+ try_sudo "ls -1dt #{releases_path}/* | tail -n +#{count + 1} | #{try_sudo} xargs rm -rf"
332
466
  end
333
467
 
334
468
  desc <<-DESC
@@ -384,40 +518,19 @@ namespace :deploy do
384
518
  end
385
519
 
386
520
  desc <<-DESC
387
- Start the application servers. This will attempt to invoke a script \
388
- in your application called `script/spin', which must know how to start \
389
- your application listeners. For Rails applications, you might just have \
390
- that script invoke `script/process/spawner' with the appropriate \
391
- arguments.
392
-
393
- By default, the script will be executed via sudo as the `app' user. If \
394
- you wish to run it as a different user, set the :runner variable to \
395
- that user. If you are in an environment where you can't use sudo, set \
396
- the :use_sudo variable to false.
521
+ Blank task exists as a hook into which to install your own environment \
522
+ specific behaviour.
397
523
  DESC
398
524
  task :start, :roles => :app do
399
- as = fetch(:runner, "app")
400
- via = fetch(:run_method, :sudo)
401
- invoke_command "sh -c 'cd #{current_path} && nohup script/spin'", :via => via, :as => as
525
+ # Empty Task to overload with your platform specifics
402
526
  end
403
527
 
404
528
  desc <<-DESC
405
- Stop the application servers. This will call script/process/reaper for \
406
- both the spawner process, and all of the application processes it has \
407
- spawned. As such, it is fairly Rails specific and may need to be \
408
- overridden for other systems.
409
-
410
- By default, the script will be executed via sudo as the `app' user. If \
411
- you wish to run it as a different user, set the :runner variable to \
412
- that user. If you are in an environment where you can't use sudo, set \
413
- the :use_sudo variable to false.
529
+ Blank task exists as a hook into which to install your own environment \
530
+ specific behaviour.
414
531
  DESC
415
532
  task :stop, :roles => :app do
416
- as = fetch(:runner, "app")
417
- via = fetch(:run_method, :sudo)
418
-
419
- invoke_command "#{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid", :via => via, :as => as
420
- invoke_command "#{current_path}/script/process/reaper -a kill", :via => via, :as => as
533
+ # Empty Task to overload with your platform specifics
421
534
  end
422
535
 
423
536
  namespace :pending do
@@ -436,14 +549,15 @@ namespace :deploy do
436
549
  might not be supported on all SCM's.
437
550
  DESC
438
551
  task :default, :except => { :no_release => true } do
439
- system(source.local.log(current_revision))
552
+ from = source.next_revision(current_revision)
553
+ system(source.local.log(from))
440
554
  end
441
555
  end
442
556
 
443
557
  namespace :web do
444
558
  desc <<-DESC
445
559
  Present a maintenance page to visitors. Disables your application's web \
446
- interface by writing a "maintenance.html" file to each web server. The \
560
+ interface by writing a "#{maintenance_basename}.html" file to each web server. The \
447
561
  servers must be configured to detect the presence of this file, and if \
448
562
  it is present, always display it instead of performing the request.
449
563
 
@@ -455,29 +569,57 @@ namespace :deploy do
455
569
  REASON="hardware upgrade" \\
456
570
  UNTIL="12pm Central Time"
457
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
+
458
576
  Further customization will require that you write your own task.
459
577
  DESC
460
578
  task :disable, :roles => :web, :except => { :no_release => true } do
461
579
  require 'erb'
462
- on_rollback { run "rm #{shared_path}/system/maintenance.html" }
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
463
605
 
464
606
  reason = ENV['REASON']
465
607
  deadline = ENV['UNTIL']
466
608
 
467
- template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml"))
609
+ template = File.read(maintenance_template_path)
468
610
  result = ERB.new(template).result(binding)
469
611
 
470
- put result, "#{shared_path}/system/maintenance.html", :mode => 0644
612
+ put result, "#{shared_path}/system/#{maintenance_basename}.html", :mode => 0644
471
613
  end
472
614
 
473
615
  desc <<-DESC
474
616
  Makes the application web-accessible again. Removes the \
475
- "maintenance.html" page generated by deploy:web:disable, which (if your \
617
+ "#{maintenance_basename}.html" page generated by deploy:web:disable, which (if your \
476
618
  web servers are configured correctly) will make your application \
477
619
  web-accessible again.
478
620
  DESC
479
621
  task :enable, :roles => :web, :except => { :no_release => true } do
480
- run "rm #{shared_path}/system/maintenance.html"
622
+ run "rm -f #{shared_path}/system/#{maintenance_basename}.html"
481
623
  end
482
624
  end
483
625
  end
@@ -34,4 +34,4 @@ DESC
34
34
  task :shell do
35
35
  require 'capistrano/shell'
36
36
  Capistrano::Shell.run(self)
37
- end
37
+ end