capper 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rst CHANGED
@@ -11,12 +11,12 @@ Capper is a collection of opinionated Capistrano recipes.
11
11
  Rationale
12
12
  =========
13
13
 
14
- Most Ruby (on Rails) applications are deployed the same way. While capistrano
15
- itself is already quite opinionated, maintaining a multitude of applications
16
- feels like copy&paste very fast.
14
+ Most web applications are deployed the same way. While capistrano itself is
15
+ already quite opinionated, maintaining a multitude of applications feels like
16
+ copy&paste very fast.
17
17
 
18
18
  Capper provides sane defaults and many recipes for technologies typically used
19
- with Ruby (on Rails) deployments to make ``config/deploy.rb`` much more
19
+ with Ruby and Python deployments to make ``config/deploy.rb`` much more
20
20
  declarative and terse.
21
21
 
22
22
  Usage
@@ -34,8 +34,8 @@ Capper recipes should be loaded via ``Capfile`` like this::
34
34
 
35
35
  load "capper/multistage"
36
36
 
37
- Note: capper takes care of loading capistranos default deploy recipe, no need
38
- to load it again in your ``Capfile``.
37
+ Note: capper does not support capistranos default deploy recipe, instead an
38
+ enhanced copy is shipped directly with capper and enabled by default.
39
39
 
40
40
  Multistage
41
41
  ----------
@@ -102,8 +102,9 @@ The following recipes are included in capper.
102
102
  base
103
103
  ----
104
104
 
105
- The base recipe is automatically loaded and provides the basic opinions capper
106
- has about deployment:
105
+ The base is an enhanced version of capistranos default deploy recipe. It is
106
+ loaded automatically and provides the basic opinions capper has about
107
+ deployment:
107
108
 
108
109
  - The application is deployed to a dedicated user account with the same name as
109
110
  the application.
@@ -114,10 +115,12 @@ has about deployment:
114
115
 
115
116
  - Releases are cleaned up by default.
116
117
 
117
- Additionally a ``deploy:symlink`` task is provided to make symlinks declarative
118
- in ``config/deploy.rb``::
118
+ - The application is deployed via ``remote_cache`` and ``git``.
119
119
 
120
- set :symlink, {
120
+ The ``deploy:finalize_update`` task has been enhanced to make symlinks
121
+ declarative in ``config/deploy.rb``::
122
+
123
+ set :symlinks, {
121
124
  "config/database.yml" => "config/database.yml",
122
125
  "shared/uploads" => "public/uploads"
123
126
  }
@@ -142,6 +145,7 @@ The bundler recipe is an extension of bundlers native capistrano integration:
142
145
  (``shared/bundle/#{gemset}``).
143
146
 
144
147
  - The option ``ruby_exec_prefix`` is set to ``bundle exec`` for convenience.
148
+ (see ``ruby`` recipe for details)
145
149
 
146
150
  config
147
151
  ------
@@ -169,14 +173,10 @@ automatically (re)started during deploy and can be specified via
169
173
  :worker2 => 2..10
170
174
  }
171
175
 
172
- git
173
- ---
174
-
175
- The git recipe will simply set capistrano to use the git strategy with
176
- remote_cache::
176
+ django
177
+ ------
177
178
 
178
- set(:scm, :git)
179
- set(:deploy_via, :remote_cache)
179
+ The django recipe provides setup and migrate tasks for Django.
180
180
 
181
181
  hoptoad
182
182
  -------
@@ -197,12 +197,19 @@ The monit recipe will collect all snippets declared via ``monit_config`` and
197
197
  render them into the file specified via ``monitrc`` (default:
198
198
  ``~/.monitrc.local``, this file should be included in your ``~/.monitrc``).
199
199
 
200
+ python
201
+ ------
202
+
203
+ The python recipe provides basic support for Python applications. It will
204
+ create a symlink from ``#{current_path}/#{application}`` to ``#{current_path}``
205
+ for Python namespace support.
206
+
200
207
  rails
201
208
  -----
202
209
 
203
210
  The rails recipe sets the default ``rails_env`` to production and includes
204
- tasks for deploying the asset pipeline for rails 3.1 applications. The recipe
205
- will automatically determine if your asset timestamps need to be normalized.
211
+ tasks for deploying the asset pipeline for rails 3.1 applications. It also
212
+ provdes a migrate task for Rails applications.
206
213
 
207
214
  resque
208
215
  ------
@@ -231,6 +238,12 @@ the ruby version and gemset via the projects ``.rvmrc``.
231
238
  A ``deploy:setup`` hook is provided to ensure the correct ruby and rubygems
232
239
  version is installed on all servers.
233
240
 
241
+ ruby
242
+ ----
243
+
244
+ The ruby recipe provides basic support for Ruby applications. It will setup a
245
+ gemrc file and and variables for ``ruby_exec_prefix`` (such as bundler).
246
+
234
247
  unicorn
235
248
  -------
236
249
 
@@ -238,10 +251,6 @@ The unicorn recipe provides integration with Unicorn. A script to manage the
238
251
  unicorn process is uploaded to ``#{bin_path}/unicorn``. Additionally this
239
252
  recipe also manages the unicorn configuration file (in ``config/unicorn.rb``).
240
253
 
241
- If monit integration has been enabled via ``capper/monit`` unicorn is
242
- automatically (re)started during ``deploy:restart`` using unicorns in-place,
243
- no-downtime upgrade method.
244
-
245
254
  The following configuration options are provided:
246
255
 
247
256
  ``unicorn_worker_processes``
@@ -250,6 +259,25 @@ The following configuration options are provided:
250
259
  ``unicorn_timeout``
251
260
  Timeout after which workers are killed (default: 30)
252
261
 
262
+ uwsgi
263
+ -----
264
+
265
+ The uwsgi recipe provides integration with uWSGI. A script to manage the uwsgi
266
+ process is uploaded to ``#{bin_path}/uwsgi``. Additionally this recipe also
267
+ manages the uwsgi configuration file (in ``config/uwsgi.xml``).
268
+
269
+ The following configuration options are provided:
270
+
271
+ ``uwsgi_worker_processes``
272
+ Number of uwsgi workers (default: 4)
273
+
274
+ virtualenv
275
+ ----------
276
+
277
+ The virtualenv recipe provides ``deploy:setup`` hooks for virtualenv support.
278
+ In addition required Python libraries are installed via pip into this
279
+ environment.
280
+
253
281
  whenever
254
282
  --------
255
283
 
@@ -276,5 +304,5 @@ Contributing to capper
276
304
  Copyright
277
305
  =========
278
306
 
279
- Copyright (c) 2011 Benedikt Böhm. See LICENSE.txt for
307
+ Copyright (c) 2011 Benedikt Böhm. See LICENSE for
280
308
  further details.
data/lib/capper.rb CHANGED
@@ -13,6 +13,5 @@ include Capper::Utils::Monit
13
13
  # make sure capper recipes can be found by load() too
14
14
  Capistrano::Configuration.instance(true).load do
15
15
  load_paths << File.expand_path(File.dirname(__FILE__))
16
- load 'deploy'
17
16
  load 'capper/base'
18
17
  end
data/lib/capper/base.rb CHANGED
@@ -1,4 +1,92 @@
1
- # add custom color scheme
1
+ require 'benchmark'
2
+ require 'yaml'
3
+ require 'capistrano/recipes/deploy/scm'
4
+ require 'capistrano/recipes/deploy/strategy'
5
+
6
+ def _cset(name, *args, &block)
7
+ unless exists?(name)
8
+ set(name, *args, &block)
9
+ end
10
+ end
11
+
12
+ # =========================================================================
13
+ # These variables MUST be set in the client capfiles. If they are not set,
14
+ # the deploy will fail with an error.
15
+ # =========================================================================
16
+
17
+ _cset(:application) { abort "Please specify the name of your application, set :application, 'foo'" }
18
+ _cset(:repository) { abort "Please specify the repository that houses your application's code, set :repository, 'foo'" }
19
+
20
+ # =========================================================================
21
+ # These variables may be set in the client capfile if their default values
22
+ # are not sufficient.
23
+ # =========================================================================
24
+
25
+ _cset(:user) { application }
26
+
27
+ _cset(:scm, :git)
28
+ _cset(:deploy_via, :remote_cache)
29
+
30
+ _cset(:deploy_to) { "/var/app/#{application}" }
31
+ _cset(:revision) { source.head }
32
+
33
+ _cset(:ruby_exec_prefix, "")
34
+ _cset(:rake) { "#{ruby_exec_prefix} rake" }
35
+
36
+ _cset(:maintenance_basename, "maintenance")
37
+
38
+ # =========================================================================
39
+ # These variables should NOT be changed unless you are very confident in
40
+ # what you are doing. Make sure you understand all the implications of your
41
+ # changes if you do decide to muck with these!
42
+ # =========================================================================
43
+
44
+ _cset(:source) { Capistrano::Deploy::SCM.new(scm, self) }
45
+ _cset(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } } }
46
+
47
+ _cset(:strategy) { Capistrano::Deploy::Strategy.new(deploy_via, self) }
48
+
49
+ # If overriding release name, please also select an appropriate setting for :releases below.
50
+ _cset(:release_name) { set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S") }
51
+
52
+ _cset :releases_dir, "releases"
53
+ _cset :shared_dir, "shared"
54
+ _cset :current_dir, "current"
55
+
56
+ _cset(:releases_path) { File.join(deploy_to, releases_dir) }
57
+ _cset(:shared_path) { File.join(deploy_to, shared_dir) }
58
+ _cset(:current_path) { File.join(deploy_to, current_dir) }
59
+ _cset(:release_path) { File.join(releases_path, release_name) }
60
+
61
+ _cset(:bin_path) { File.join(deploy_to, "bin") }
62
+ _cset(:pid_path) { File.join(shared_path, "pids") }
63
+ _cset(:log_path) { File.join(shared_path, "log") }
64
+ _cset(:config_path) { File.join(shared_path, "config") }
65
+
66
+ _cset(:releases) { capture("ls -x #{releases_path}", :except => { :no_release => true }).split.sort }
67
+ _cset(:current_release) { releases.length > 0 ? File.join(releases_path, releases.last) : nil }
68
+ _cset(:previous_release) { releases.length > 1 ? File.join(releases_path, releases[-2]) : nil }
69
+
70
+ _cset(:current_revision) { capture("cat #{current_path}/REVISION", :except => { :no_release => true }).chomp }
71
+ _cset(:latest_revision) { capture("cat #{current_release}/REVISION", :except => { :no_release => true }).chomp }
72
+ _cset(:previous_revision) { capture("cat #{previous_release}/REVISION", :except => { :no_release => true }).chomp if previous_release }
73
+
74
+ set(:internal_shared_children, fetch(:internal_shared_children, []) | %w(system log pids))
75
+
76
+ # some tasks, like symlink, need to always point at the latest release, but
77
+ # they can also (occassionally) be called standalone. In the standalone case,
78
+ # the timestamped release_path will be inaccurate, since the directory won't
79
+ # actually exist. This variable lets tasks like symlink work either in the
80
+ # standalone case, or during deployment.
81
+ _cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release }
82
+
83
+ # set proper unicode locale, so gemspecs with unicode chars will not crash
84
+ # bundler. see https://github.com/capistrano/capistrano/issues/70
85
+ set(:default_environment, fetch(:default_environment).merge({
86
+ 'LANG' => 'en_US.UTF-8'
87
+ }))
88
+
89
+ # add some colors and hide certain messages
2
90
  require 'capistrano_colors/configuration'
3
91
  require 'capistrano_colors/logger'
4
92
 
@@ -21,36 +109,53 @@ colorize([
21
109
  # do not trace by default
22
110
  logger.level = Capistrano::Logger::DEBUG
23
111
 
24
- # default app layout
25
- _cset(:user) { application }
26
-
27
- _cset(:base_path) { "/var/app" }
28
- set(:deploy_to) { "#{base_path}/#{application}" }
112
+ # =========================================================================
113
+ # These are helper methods that will be available to your recipes.
114
+ # =========================================================================
29
115
 
30
- _cset(:bin_path) { File.join(deploy_to, "bin") }
31
- _cset(:pid_path) { File.join(shared_path, "pids") }
32
- _cset(:config_path) { File.join(shared_path, "config") }
33
-
34
- # apps should not require root access
35
- set(:use_sudo, false)
36
- set(:group_writable, false)
37
-
38
- # set proper unicode locale, so gemspecs with unicode chars will not crash
39
- # bundler. see https://github.com/capistrano/capistrano/issues/70
40
- _cset(:default_environment, { 'LANG' => 'en_US.UTF-8' })
116
+ # Temporarily sets an environment variable, yields to a block, and restores
117
+ # the value when it is done.
118
+ def with_env(name, value)
119
+ saved, ENV[name] = ENV[name], value
120
+ yield
121
+ ensure
122
+ ENV[name] = saved
123
+ end
41
124
 
42
- # set a global (empty) prefix for ruby applications so that
43
- # other recipes (bundler) can override it (with bundle exec)
44
- _cset(:ruby_exec_prefix, "")
45
- set(:rake) { "#{ruby_exec_prefix} rake" }
125
+ # logs the command then executes it locally.
126
+ # returns the command output as a string
127
+ def run_locally(cmd)
128
+ logger.trace "executing locally: #{cmd.inspect}" if logger
129
+ output_on_stdout = nil
130
+ elapsed = Benchmark.realtime do
131
+ output_on_stdout = `#{cmd}`
132
+ end
133
+ if $?.to_i > 0 # $? is command exit code (posix style)
134
+ raise Capistrano::LocalArgumentError, "Command #{cmd} returned status code #{$?}"
135
+ end
136
+ logger.trace "command finished in #{(elapsed * 1000).round}ms" if logger
137
+ output_on_stdout
138
+ end
46
139
 
47
- # cleanup by default
48
- after "deploy:update", "deploy:cleanup"
49
- after "deploy:update_code", "deploy:symlink_shared"
140
+ # =========================================================================
141
+ # These are the tasks that are available to help with deploying web apps,
142
+ # and specifically, Rails applications. You can have cap give you a summary
143
+ # of them with `cap -T'.
144
+ # =========================================================================
50
145
 
51
- # overwrite deploy:setup to get rid of the annoying chmod g+w which makes ssh
52
- # logins impossible
53
146
  namespace :deploy do
147
+ desc <<-DESC
148
+ Deploys your project. This calls `update', `cleanup' and `restart'. Note that \
149
+ this will generally only work for applications that have already been deployed \
150
+ once. For a "cold" deploy, you'll want to take a look at the `deploy:cold' \
151
+ task, which handles the cold start specifically.
152
+ DESC
153
+ task :default do
154
+ update
155
+ cleanup
156
+ restart
157
+ end
158
+
54
159
  desc <<-DESC
55
160
  Prepares one or more servers for deployment. Before you can use any \
56
161
  of the Capistrano deployment tasks with your project, you will need to \
@@ -64,16 +169,277 @@ namespace :deploy do
64
169
  will not destroy any deployed revisions or data.
65
170
  DESC
66
171
  task :setup, :except => { :no_release => true } do
67
- shared = %w(system log pids) | shared_children
68
- dirs = [releases_path, shared_path]
172
+ shared = fetch(:internal_shared_children, []) | fetch(:shared_children, [])
173
+ dirs = [deploy_to, releases_path, shared_path]
69
174
  dirs += shared.map { |d| File.join(shared_path, d) }
70
175
  run "mkdir -p #{dirs.join(' ')}"
71
176
  end
72
177
 
73
- desc "Create symlinks from shared to current"
74
- task :symlink_shared, :roles => :app, :except => { :no_release => true } do
75
- fetch(:symlinks, {}).each do |source, dest|
76
- run "rm -rf #{release_path}/#{dest} && ln -nfs #{shared_path}/#{source} #{release_path}/#{dest}"
178
+ desc <<-DESC
179
+ Copies your project and updates the symlink. It does this in a \
180
+ transaction, so that if either `update_code' or `symlink' fail, all \
181
+ changes made to the remote servers will be rolled back, leaving your \
182
+ system in the same state it was in before `update' was invoked. Usually, \
183
+ you will want to call `deploy' instead of `update', but `update' can be \
184
+ handy if you want to deploy, but not immediately restart your application.
185
+ DESC
186
+ task :update do
187
+ transaction do
188
+ update_code
189
+ symlink
190
+ end
191
+ end
192
+
193
+ desc <<-DESC
194
+ Copies your project to the remote servers. This is the first stage \
195
+ of any deployment; moving your updated code and assets to the deployment \
196
+ servers. You will rarely call this task directly, however; instead, you \
197
+ should call the `deploy' task (to do a complete deploy) or the `update' \
198
+ task (if you want to perform the `restart' task separately).
199
+
200
+ You will need to make sure you set the :scm variable to the source \
201
+ control software you are using (it defaults to :git), and the \
202
+ :deploy_via variable to the strategy you want to use to deploy (it \
203
+ defaults to :remote_cache).
204
+ DESC
205
+ task :update_code, :except => { :no_release => true } do
206
+ on_rollback { run "rm -rf #{release_path}; true" }
207
+ strategy.deploy!
208
+ finalize_update
209
+ end
210
+
211
+ desc <<-DESC
212
+ [internal] Touches up the released code. This is called by update_code \
213
+ after the basic deploy finishes.
214
+
215
+ This task will set up symlinks to the shared directory for the log, system, \
216
+ and tmp/pids directories as well as mappings specified in :symlinks.
217
+ DESC
218
+ task :finalize_update, :except => { :no_release => true } do
219
+ run(fetch(:internal_symlinks, {}).merge(fetch(:symlinks, {})).map do |source, dest|
220
+ "rm -rf #{latest_release}/#{dest} && " +
221
+ "mkdir -p #{File.dirname(File.join(latest_release, dest))} && " +
222
+ "ln -s #{shared_path}/#{source} #{latest_release}/#{dest}"
223
+ end.join(" && "))
224
+ end
225
+
226
+ desc <<-DESC
227
+ Updates the symlink to the most recently deployed version. Capistrano works \
228
+ by putting each new release of your application in its own directory. When \
229
+ you deploy a new version, this task's job is to update the `current' symlink \
230
+ to point at the new version. You will rarely need to call this task \
231
+ directly; instead, use the `deploy' task (which performs a complete \
232
+ deploy, including `restart') or the 'update' task (which does everything \
233
+ except `restart').
234
+ DESC
235
+ task :symlink, :except => { :no_release => true } do
236
+ on_rollback do
237
+ if previous_release
238
+ run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true"
239
+ else
240
+ logger.important "no previous release to rollback to, rollback of symlink skipped"
241
+ end
242
+ end
243
+
244
+ run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
245
+ end
246
+
247
+ desc <<-DESC
248
+ Blank task exists as a hook into which to install your own environment \
249
+ specific behaviour.
250
+ DESC
251
+ task :start, :roles => :app do
252
+ # Empty Task to overload with your platform specifics
253
+ end
254
+
255
+ desc <<-DESC
256
+ Blank task exists as a hook into which to install your own environment \
257
+ specific behaviour.
258
+ DESC
259
+ task :stop, :roles => :app do
260
+ # Empty Task to overload with your platform specifics
261
+ end
262
+
263
+ desc <<-DESC
264
+ Blank task exists as a hook into which to install your own environment \
265
+ specific behaviour.
266
+ DESC
267
+ task :restart, :roles => :app, :except => { :no_release => true } do
268
+ # Empty Task to overload with your platform specifics
269
+ end
270
+
271
+ desc <<-DESC
272
+ Blank task exists as a hook into which to install your own environment \
273
+ specific behaviour.
274
+ DESC
275
+ task :migrate, :roles => :db, :only => { :primary => true } do
276
+ # Empty Task to overload with your platform specifics
277
+ end
278
+
279
+ desc <<-DESC
280
+ Deploy and run pending migrations. This will work similarly to the \
281
+ `deploy' task, but will also run any pending migrations (via the \
282
+ `deploy:migrate' task) prior to updating the symlink. Note that the \
283
+ update in this case is not atomic, and transactions are not used, \
284
+ because migrations are not guaranteed to be reversible.
285
+ DESC
286
+ task :migrations do
287
+ update_code
288
+ migrate
289
+ symlink
290
+ restart
291
+ end
292
+
293
+ desc <<-DESC
294
+ Deploys and starts a `cold' application. This is useful if you have not \
295
+ deployed your application before, or if your application is (for some \
296
+ other reason) not currently running. It will deploy the code, run any \
297
+ pending migrations, and then instead of invoking `deploy:restart', it will \
298
+ invoke `deploy:start' to fire up the application servers.
299
+ DESC
300
+ task :cold do
301
+ update
302
+ migrate
303
+ start
304
+ end
305
+
306
+ namespace :rollback do
307
+ desc <<-DESC
308
+ [internal] Points the current symlink at the previous revision.
309
+ This is called by the rollback sequence, and should rarely (if
310
+ ever) need to be called directly.
311
+ DESC
312
+ task :revision, :except => { :no_release => true } do
313
+ if previous_release
314
+ run "rm #{current_path}; ln -s #{previous_release} #{current_path}"
315
+ else
316
+ abort "could not rollback the code because there is no prior release"
317
+ end
318
+ end
319
+
320
+ desc <<-DESC
321
+ [internal] Removes the most recently deployed release.
322
+ This is called by the rollback sequence, and should rarely
323
+ (if ever) need to be called directly.
324
+ DESC
325
+ task :cleanup, :except => { :no_release => true } do
326
+ run "if [ `readlink #{current_path}` != #{current_release} ]; then rm -rf #{current_release}; fi"
327
+ end
328
+
329
+ desc <<-DESC
330
+ Rolls back to the previously deployed version. The `current' symlink will \
331
+ be updated to point at the previously deployed version, and then the \
332
+ current release will be removed from the servers. You'll generally want \
333
+ to call `rollback' instead, as it performs a `restart' as well.
334
+ DESC
335
+ task :code, :except => { :no_release => true } do
336
+ revision
337
+ cleanup
338
+ end
339
+
340
+ desc <<-DESC
341
+ Rolls back to a previous version and restarts. This is handy if you ever \
342
+ discover that you've deployed a lemon; `cap rollback' and you're right \
343
+ back where you were, on the previously deployed version.
344
+ DESC
345
+ task :default do
346
+ revision
347
+ restart
348
+ cleanup
349
+ end
350
+ end
351
+
352
+ desc <<-DESC
353
+ Clean up old releases. By default, the last 5 releases are kept on each \
354
+ server (though you can change this with the keep_releases variable). All \
355
+ other deployed revisions are removed from the servers.
356
+ DESC
357
+ task :cleanup, :except => { :no_release => true } do
358
+ count = fetch(:keep_releases, 5).to_i
359
+ local_releases = capture("ls -xt #{releases_path}").split.reverse
360
+ if count >= local_releases.length
361
+ logger.important "no old releases to clean up"
362
+ else
363
+ logger.info "keeping #{count} of #{local_releases.length} deployed releases"
364
+ directories = (local_releases - local_releases.last(count)).map { |release|
365
+ File.join(releases_path, release) }.join(" ")
366
+
367
+ run "rm -rf #{directories}"
368
+ end
369
+ end
370
+
371
+ namespace :pending do
372
+ desc <<-DESC
373
+ Displays the `diff' since your last deploy. This is useful if you want \
374
+ to examine what changes are about to be deployed. Note that this might \
375
+ not be supported on all SCM's.
376
+ DESC
377
+ task :diff, :except => { :no_release => true } do
378
+ system(source.local.diff(current_revision))
379
+ end
380
+
381
+ desc <<-DESC
382
+ Displays the commits since your last deploy. This is good for a summary \
383
+ of the changes that have occurred since the last deploy. Note that this \
384
+ might not be supported on all SCM's.
385
+ DESC
386
+ task :default, :except => { :no_release => true } do
387
+ from = source.next_revision(current_revision)
388
+ system(source.local.log(from))
389
+ end
390
+ end
391
+
392
+ namespace :web do
393
+ desc <<-DESC
394
+ Present a maintenance page to visitors. Disables your application's web \
395
+ interface by writing a "#{maintenance_basename}.html" file to each web server. The \
396
+ servers must be configured to detect the presence of this file, and if \
397
+ it is present, always display it instead of performing the request.
398
+
399
+ By default, the maintenance page will just say the site is down for \
400
+ "maintenance", and will be back "shortly", but you can customize the \
401
+ page by specifying the REASON and UNTIL environment variables:
402
+
403
+ $ cap deploy:web:disable \\
404
+ REASON="hardware upgrade" \\
405
+ UNTIL="12pm Central Time"
406
+
407
+ Further customization will require that you write your own task.
408
+ DESC
409
+ task :disable, :roles => :web, :except => { :no_release => true } do
410
+ require 'erb'
411
+ on_rollback { run "rm #{shared_path}/system/#{maintenance_basename}.html" }
412
+
413
+ warn <<-EOHTACCESS
414
+
415
+ # Please add something like this to your site's htaccess to redirect users to the maintenance page.
416
+ # More Info: http://www.shiftcommathree.com/articles/make-your-rails-maintenance-page-respond-with-a-503
417
+
418
+ ErrorDocument 503 /system/#{maintenance_basename}.html
419
+ RewriteEngine On
420
+ RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$
421
+ RewriteCond %{DOCUMENT_ROOT}/system/#{maintenance_basename}.html -f
422
+ RewriteCond %{SCRIPT_FILENAME} !#{maintenance_basename}.html
423
+ RewriteRule ^.*$ - [redirect=503,last]
424
+ EOHTACCESS
425
+
426
+ reason = ENV['REASON']
427
+ deadline = ENV['UNTIL']
428
+
429
+ template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml"))
430
+ result = ERB.new(template).result(binding)
431
+
432
+ put result, "#{shared_path}/system/#{maintenance_basename}.html", :mode => 0644
433
+ end
434
+
435
+ desc <<-DESC
436
+ Makes the application web-accessible again. Removes the \
437
+ "#{maintenance_basename}.html" page generated by deploy:web:disable, which (if your \
438
+ web servers are configured correctly) will make your application \
439
+ web-accessible again.
440
+ DESC
441
+ task :enable, :roles => :web, :except => { :no_release => true } do
442
+ run "rm #{shared_path}/system/#{maintenance_basename}.html"
77
443
  end
78
444
  end
79
445
  end
@@ -1,3 +1,5 @@
1
+ load 'capper/ruby'
2
+
1
3
  require 'bundler/capistrano'
2
4
 
3
5
  # use bundle exec for all ruby programs
@@ -0,0 +1,34 @@
1
+ load "capper/python"
2
+
3
+ after 'deploy:update_code', 'django:setup'
4
+
5
+ before 'deploy:migrate', 'django:migrate'
6
+
7
+ namespace :django do
8
+ desc "Generate rails configuration and helpers"
9
+ task :setup, :roles => :app, :except => { :no_release => true } do
10
+ upload_template_file("manage.py",
11
+ File.join(bin_path, "manage.py"),
12
+ :mode => "0755")
13
+ end
14
+
15
+ desc <<-DESC
16
+ Run the syncdb and migratedb task. By default, it runs this in most recently \
17
+ deployed version of the app. However, you can specify a different release \
18
+ via the migrate_target variable, which must be one of :latest (for the \
19
+ default behavior), or :current (for the release indicated by the \
20
+ `current' symlink). Strings will work for those values instead of symbols, \
21
+ too.
22
+ DESC
23
+ task :migrate, :roles => :db, :only => { :primary => true } do
24
+ migrate_target = fetch(:migrate_target, :latest)
25
+
26
+ directory = case migrate_target.to_sym
27
+ when :current then current_path
28
+ when :latest then latest_release
29
+ else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
30
+ end
31
+
32
+ run "cd #{directory} && #{python} manage.py syncdb --migrate --noinput"
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ _cset(:python, "python")
2
+
3
+ after "deploy:finalize_update" do
4
+ run("ln -nfs #{latest_release} #{latest_release}/#{application}")
5
+ end
data/lib/capper/rails.rb CHANGED
@@ -1,20 +1,23 @@
1
+ load "capper/ruby"
2
+
1
3
  _cset(:rails_env, "production")
2
4
 
3
5
  _cset(:asset_pipeline, true)
4
6
  _cset(:asset_env, "RAILS_GROUPS=assets")
5
- _cset(:assets_prefix, "assets")
6
7
 
7
- set(:normalize_asset_timestamps) do
8
- if asset_pipeline
9
- false
10
- else
11
- true
12
- end
13
- end
8
+ set(:internal_shared_children, fetch(:internal_shared_children, []) | %w(assets))
9
+
10
+ set(:internal_symlinks, fetch(:internal_symlinks, {}).merge({
11
+ "assets" => "public/assets",
12
+ "log" => "log",
13
+ "pids" => "tmp/pids",
14
+ "system" => "public/system",
15
+ }))
14
16
 
15
- before 'deploy:finalize_update', 'rails:assets:symlink'
16
- after 'deploy:update_code', 'rails:assets:precompile'
17
17
  after 'deploy:update_code', 'rails:setup'
18
+ after 'deploy:update_code', 'rails:assets:precompile'
19
+
20
+ before 'deploy:migrate', 'rails:migrate'
18
21
 
19
22
  namespace :rails do
20
23
  desc "Generate rails configuration and helpers"
@@ -24,26 +27,35 @@ namespace :rails do
24
27
  :mode => "0755")
25
28
  end
26
29
 
27
- namespace :assets do
28
- desc <<-DESC
29
- [internal] This task will set up a symlink to the shared directory \
30
- for the assets directory. Assets are shared across deploys to avoid \
31
- mid-deploy mismatches between old application html asking for assets \
32
- and getting a 404 file not found error. The assets cache is shared \
33
- for efficiency. If you cutomize the assets path prefix, override the \
34
- :assets_prefix variable to match.
35
- DESC
36
- task :symlink, :roles => [:web, :asset], :except => { :no_release => true } do
37
- if asset_pipeline
38
- run <<-CMD
39
- rm -rf #{latest_release}/public/#{assets_prefix} &&
40
- mkdir -p #{latest_release}/public &&
41
- mkdir -p #{shared_path}/assets &&
42
- ln -s #{shared_path}/assets #{latest_release}/public/#{assets_prefix}
43
- CMD
30
+ desc <<-DESC
31
+ Run the migrate rake task. By default, it runs this in most recently \
32
+ deployed version of the app. However, you can specify a different release \
33
+ via the migrate_target variable, which must be one of :latest (for the \
34
+ default behavior), or :current (for the release indicated by the \
35
+ `current' symlink). Strings will work for those values instead of symbols, \
36
+ too. You can also specify additional environment variables to pass to rake \
37
+ via the migrate_env variable. Finally, you can specify the full path to the \
38
+ rake executable by setting the rake variable. The defaults are:
39
+
40
+ set :rake, "rake"
41
+ set :rails_env, "production"
42
+ set :migrate_env, ""
43
+ set :migrate_target, :latest
44
+ DESC
45
+ task :migrate, :roles => :db, :only => { :primary => true } do
46
+ migrate_env = fetch(:migrate_env, "")
47
+ migrate_target = fetch(:migrate_target, :latest)
48
+
49
+ directory = case migrate_target.to_sym
50
+ when :current then current_path
51
+ when :latest then latest_release
52
+ else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
44
53
  end
45
- end
46
54
 
55
+ run "cd #{directory} && #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
56
+ end
57
+
58
+ namespace :assets do
47
59
  desc <<-DESC
48
60
  Run the asset precompilation rake task. You can specify the full path \
49
61
  to the rake executable by setting the rake variable. You can also \
@@ -1,3 +1,6 @@
1
+ _cset(:ruby_exec_prefix, "")
2
+ _cset(:rake) { "#{ruby_exec_prefix} rake" }
3
+
1
4
  before "deploy:setup", "gemrc:setup"
2
5
 
3
6
  namespace :gemrc do
data/lib/capper/rvm.rb CHANGED
@@ -1,4 +1,4 @@
1
- load "capper/gem"
1
+ load "capper/ruby"
2
2
 
3
3
  $:.unshift(File.expand_path('./lib', ENV['rvm_path']))
4
4
  require 'rvm/capistrano'
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+ export HOME=<%= deploy_to %>
3
+
4
+ cd <%= current_path %> >/dev/null
5
+
6
+ exec <%= python %> manage.py "$@"
@@ -13,14 +13,14 @@ worker_processes <%= unicorn_worker_processes %>
13
13
  working_directory "<%= current_path %>"
14
14
 
15
15
  # listen on Unix domain socket and let nginx proxy
16
- listen "<%= shared_path %>/pids/unicorn.sock"
16
+ listen "<%= pid_path %>/unicorn.sock"
17
17
 
18
18
  # nuke workers after 30 seconds instead of 60 seconds (the default)
19
19
  timeout <%= unicorn_timeout %>
20
20
 
21
21
  # Location of the pidfile. Should not be changed unless you
22
22
  # know what you are doing.
23
- pid "<%= shared_path %>/pids/unicorn.pid"
23
+ pid "<%= unicorn_pidfile %>"
24
24
 
25
25
  # use syslogger if available
26
26
  begin
@@ -30,8 +30,8 @@ rescue
30
30
  end
31
31
 
32
32
  # these do not seem to support syslog
33
- stderr_path "<%= shared_path %>/log/unicorn.stderr.log"
34
- stdout_path "<%= shared_path %>/log/unicorn.stdout.log"
33
+ stderr_path "<%= log_path %>/unicorn.stderr.log"
34
+ stdout_path "<%= log_path %>/unicorn.stdout.log"
35
35
 
36
36
  # combine REE with "preload_app true" for memory savings
37
37
  # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
@@ -66,7 +66,7 @@ before_fork do |server, worker|
66
66
  # thundering herd (especially in the "preload_app false" case)
67
67
  # when doing a transparent upgrade. The last worker spawned
68
68
  # will then kill off the old master process with a SIGQUIT.
69
- old_pid = "<%= shared_path %>/pids/unicorn.pid.oldbin"
69
+ old_pid = "<%= unicorn_pidfile %>.oldbin"
70
70
  if old_pid != server.pid
71
71
  begin
72
72
  sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
@@ -6,8 +6,8 @@ if [[ -e "${HOME}"/.rvm/scripts/rvm ]]; then
6
6
  source "${HOME}"/.rvm/scripts/rvm
7
7
  fi
8
8
 
9
- PIDFILE=<%= shared_path %>/pids/unicorn.pid
10
- CONFIG=<%= shared_path %>/config/unicorn.rb
9
+ PIDFILE=<%= unicorn_pidfile %>
10
+ CONFIG=<%= unicorn_config %>
11
11
  CMD="<%= ruby_exec_prefix %> unicorn -c $CONFIG -E $RAILS_ENV -D config.ru"
12
12
 
13
13
  cd <%= current_path %> >/dev/null
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ export HOME=<%= deploy_to %>
3
+
4
+ PIDFILE=<%= uwsgi_pidfile %>
5
+ CONFIG=<%= uwsgi_config %>
6
+ LOGFILE=<%= log_path %>/uwsgi.log
7
+ CMD="uwsgi -x $CONFIG -d $LOGFILE"
8
+
9
+ cd <%= current_path %> >/dev/null
10
+
11
+ sig () {
12
+ test -s "$PIDFILE" && kill -$1 $(<$PIDFILE)
13
+ }
14
+
15
+ case $1 in
16
+ start)
17
+ sig 0 && echo >&2 "Already running" && exit 0
18
+ $CMD
19
+ ;;
20
+ stop)
21
+ sig QUIT && exit 0
22
+ echo >&2 "Not running"
23
+ ;;
24
+ reload)
25
+ sig HUP && echo reloaded OK && exit 0
26
+ echo >&2 "Couldn't reload, starting '$CMD' instead"
27
+ $CMD
28
+ ;;
29
+ *)
30
+ echo >&2 "Usage: $0 <start|stop|reload>"
31
+ exit 1
32
+ ;;
33
+ esac
@@ -0,0 +1,10 @@
1
+ <uwsgi>
2
+ <master/>
3
+ <processes><%= uwsgi_worker_processes %></processes>
4
+ <home><%= deploy_to %></home>
5
+ <socket><%= pid_path %>/uwsgi.sock</socket>
6
+ <pidfile><%= uwsgi_pidfile %></pidfile>
7
+ <pythonpath><%= current_path %></pythonpath>
8
+ <env>DJANGO_SETTINGS_MODULE=<%= application %>.settings</env>
9
+ <module>django.core.handlers.wsgi:WSGIHandler()</module>
10
+ </uwsgi>
@@ -0,0 +1,44 @@
1
+ # uwsgi configuration variables
2
+ _cset(:uwsgi_worker_processes, 4)
3
+
4
+ # these cannot be overriden
5
+ set(:uwsgi_script) { File.join(bin_path, "uwsgi") }
6
+ set(:uwsgi_config) { File.join(config_path, "uwsgi.xml") }
7
+ set(:uwsgi_pidfile) { File.join(pid_path, "uwsgi.pid") }
8
+
9
+ after "deploy:update_code", "uwsgi:setup"
10
+ after "deploy:restart", "uwsgi:restart"
11
+
12
+ monit_config "uwsgi", <<EOF, :roles => :app
13
+ check process uwsgi
14
+ with pidfile "<%= uwsgi_pidfile %>"
15
+ start program = "<%= uwsgi_script %> start" with timeout 60 seconds
16
+ stop program = "<%= uwsgi_script %> stop"
17
+ EOF
18
+
19
+ namespace :uwsgi do
20
+ desc "Generate uwsgi configuration files"
21
+ task :setup, :roles => :app, :except => { :no_release => true } do
22
+ upload_template_file("uwsgi.xml",
23
+ uwsgi_config,
24
+ :mode => "0644")
25
+ upload_template_file("uwsgi.sh",
26
+ uwsgi_script,
27
+ :mode => "0755")
28
+ end
29
+
30
+ desc "Start uwsgi"
31
+ task :start, :roles => :app, :except => { :no_release => true } do
32
+ run "#{uwsgi_script} start"
33
+ end
34
+
35
+ desc "Stop uwsgi"
36
+ task :stop, :roles => :app, :except => { :no_release => true } do
37
+ run "#{uwsgi_script} stop"
38
+ end
39
+
40
+ desc "Reload uwsgi"
41
+ task :restart, :roles => :app, :except => { :no_release => true } do
42
+ run "#{uwsgi_script} reload"
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Capper
2
- VERSION = "0.8.3"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ load "capper/python"
2
+
3
+ set(:python) { "#{bin_path}/python" }
4
+
5
+ before "deploy:setup", "virtualenv:setup"
6
+
7
+ after "deploy:finalize_update", "pip:install"
8
+
9
+ namespace :virtualenv do
10
+ desc "Create virtualenv for Python packages"
11
+ task :setup, :except => {:no_release => true} do
12
+ run("if [ ! -e #{bin_path}/python ]; then " +
13
+ "virtualenv -q --no-site-packages #{deploy_to}; fi")
14
+ end
15
+ end
16
+
17
+ namespace :pip do
18
+ task :install do
19
+ run("#{bin_path}/pip install -q -E #{deploy_to} -r #{latest_release}/requirements.txt")
20
+ end
21
+ end
@@ -1,6 +1,8 @@
1
+ load "capper/ruby"
2
+
1
3
  set(:whenever_command) { "#{ruby_exec_prefix} whenever" }
2
4
  set(:whenever_identifier) { application }
3
- set(:whenever_environment) { fetch(:rails_env, "production") }
5
+ set(:whenever_environment) { rails_env }
4
6
  set(:whenever_update_flags) { "--update-crontab #{whenever_identifier} --set environment=#{whenever_environment}" }
5
7
  set(:whenever_clear_flags) { "--clear-crontab #{whenever_identifier}" }
6
8
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: capper
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.8.3
5
+ version: 0.9.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - "Benedikt B\xC3\xB6hm"
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-10-28 00:00:00 +02:00
13
+ date: 2011-11-14 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -70,24 +70,30 @@ files:
70
70
  - lib/capper/bundler.rb
71
71
  - lib/capper/config.rb
72
72
  - lib/capper/delayed_job.rb
73
- - lib/capper/gem.rb
74
- - lib/capper/git.rb
73
+ - lib/capper/django.rb
75
74
  - lib/capper/hoptoad.rb
76
75
  - lib/capper/monit.rb
77
76
  - lib/capper/multistage.rb
77
+ - lib/capper/python.rb
78
78
  - lib/capper/rails.rb
79
79
  - lib/capper/resque.rb
80
+ - lib/capper/ruby.rb
80
81
  - lib/capper/rvm.rb
81
82
  - lib/capper/templates/delayed_job.sh.erb
83
+ - lib/capper/templates/manage.py.erb
82
84
  - lib/capper/templates/rails.console.sh.erb
83
85
  - lib/capper/templates/resque.sh.erb
84
86
  - lib/capper/templates/unicorn.rb.erb
85
87
  - lib/capper/templates/unicorn.sh.erb
88
+ - lib/capper/templates/uwsgi.sh.erb
89
+ - lib/capper/templates/uwsgi.xml.erb
86
90
  - lib/capper/unicorn.rb
87
91
  - lib/capper/utils/monit.rb
88
92
  - lib/capper/utils/multistage.rb
89
93
  - lib/capper/utils/templates.rb
94
+ - lib/capper/uwsgi.rb
90
95
  - lib/capper/version.rb
96
+ - lib/capper/virtualenv.rb
91
97
  - lib/capper/whenever.rb
92
98
  has_rdoc: true
93
99
  homepage: http://github.com/zenops/capper
data/lib/capper/git.rb DELETED
@@ -1,2 +0,0 @@
1
- set(:scm, :git)
2
- set(:deploy_via, :remote_cache)