drupal-deploy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ *.gemspec
3
+ pkg/*
data/LICENSE.markdown ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) <2010> Joe Hassick
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,80 @@
1
+ # Drupal Deploy (Based off of Lee Hambley's Railsless Deploy)
2
+
3
+ If you want a way to deploy your drupal code, copy `files/` directory (and back it up), and dump and
4
+ import your development database to another server (and back it up!) all by with 'cap deploy', this is for you.
5
+
6
+ ## Installation
7
+
8
+ $ gem install drupal-deploy
9
+
10
+ ## Dependencies
11
+
12
+ `capistrano-ext`, the multi-stage extension for Capistrano, will be checked for while installing this.
13
+
14
+ ## Usage
15
+
16
+ Begin your application's `Capfile` like this:
17
+
18
+ require 'rubygems'
19
+ require 'drupal-deploy'
20
+ load 'config/deploy'
21
+
22
+ Be sure to remove the original `require 'deploy'` as this is where the standard tasks are defined. You don't want to use those.
23
+
24
+ Now proceed as you normally would. I really, really recommend looking through the tasks that come with this, though.
25
+
26
+ Get a full task list with `cap -T`
27
+
28
+ ## Assumptions
29
+
30
+ This deploy strategy makes a bunch of assumptions which you may or may not like:
31
+
32
+ * You have [drush](http://drupal.org/project/drush) installed on your remote server(s)
33
+ * Your `files/` directory is in the standard location, `sites/default`
34
+ * Your site's code lives in a directory named `drupal/`
35
+
36
+ Your site's directory structure should resemble this:
37
+
38
+ my_website/
39
+ `- .git/
40
+ `- .gitignore
41
+ `- Capfile
42
+ `- config/
43
+ `- deploy.rb
44
+ `- deploy/
45
+ `- staging.rb
46
+ `- production.rb
47
+ `- drupal/
48
+ `- <drupal files>
49
+
50
+ ## What's Included?
51
+
52
+ If you want to try before you buy, here's the list of tasks included with this version of the deploy recipe:
53
+
54
+ cap deploy # Deploys your project.
55
+ cap deploy:check # Test deployment dependencies.
56
+ cap deploy:cleanup # Clean up old releases.
57
+ cap deploy:db # Dumps and imports the development database to your live site
58
+ cap deploy:db:pull # Dumps and imports the live database to the development site
59
+ cap deploy:files # Copies the files directory from development to live
60
+ cap deploy:files:pull # Pull the files directory from the live server to development
61
+ cap deploy:pending # Displays the commits since your last deploy.
62
+ cap deploy:pending:diff # Displays the `diff' since your last deploy.
63
+ cap deploy:pull # Pull the database and files directory from your primary server to your secondary server
64
+ cap deploy:rollback # Rolls back to a previous version and restarts.
65
+ cap deploy:setup # Prepares one or more servers for deployment.
66
+ cap deploy:update # Copies your project and updates the symlink.
67
+ cap deploy:update_code # Copies your project to the remote servers.
68
+ cap deploy:upload # Copy files to the currently deployed version.
69
+ cap drupal:configure:settings # Copy the appropriate settings.php file.
70
+ cap drupal:symlink:webapp # Symlink the website
71
+ cap invoke # Invoke a single command on the remote servers.
72
+ cap shell # Begin an interactive Capistrano session.
73
+
74
+ I recommend running `cap -vT` too and looking through everything. There are a bunch of internal tasks at work.
75
+
76
+ ## Bugs & Feedback
77
+
78
+ Questions? Feedback? Love it? Hate it? Want to fix it? Anything else? Let me know.
79
+
80
+ http://github.com/jh3
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "drupal-deploy"
8
+ gemspec.summary = "A Drupal focused replacement for the default Capistrano tasks"
9
+ gemspec.description = "Replacement for the rails deploy strategy which ships with Capistrano, allows you to deploy Drupal sites with ease. Based off of Lee Hambley's Railsless Deploy."
10
+ gemspec.add_dependency 'capistrano-ext'
11
+ gemspec.email = "ehassick@gmail.com"
12
+ gemspec.homepage = "http://github.com/jh3/drupal-deploy"
13
+ gemspec.authors = ["Joe Hassick"]
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,703 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
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 :scm, :git
26
+ _cset :deploy_via, :remote_cache
27
+
28
+ _cset(:deploy_to) { "/u/apps/#{application}" }
29
+ _cset(:revision) { source.head }
30
+
31
+ # =========================================================================
32
+ # These variables should NOT be changed unless you are very confident in
33
+ # what you are doing. Make sure you understand all the implications of your
34
+ # changes if you do decide to muck with these!
35
+ # =========================================================================
36
+
37
+ _cset(:source) { Capistrano::Deploy::SCM.new(scm, self) }
38
+ _cset(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } } }
39
+
40
+ _cset(:strategy) { Capistrano::Deploy::Strategy.new(deploy_via, self) }
41
+
42
+ _cset(:release_name) { set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S") }
43
+ _cset(:db_snapshot_name) { "#{release_name}-snapshot.sql" }
44
+ _cset(:file_archive_name) { "#{release_name}-files.tar" }
45
+
46
+ _cset :version_dir, "releases"
47
+ _cset :shared_dir, "shared"
48
+ _cset :shared_children, %w(dumps files files_backup)
49
+ _cset :current_dir, "current"
50
+
51
+ _cset(:releases_path) { File.join(deploy_to, version_dir) }
52
+ _cset(:shared_path) { File.join(deploy_to, shared_dir) }
53
+ _cset(:databases_path) { File.join(deploy_to, shared_dir, shared_children[0]) }
54
+ _cset(:files_path) { File.join(deploy_to, shared_dir, shared_children[1]) }
55
+ _cset(:files_backup_path) { File.join(deploy_to, shared_dir, shared_children[2]) }
56
+ _cset(:current_path) { File.join(deploy_to, current_dir) }
57
+ _cset(:release_path) { File.join(releases_path, release_name) }
58
+
59
+ _cset(:releases) { capture("ls -xt #{releases_path}").split.reverse }
60
+ _cset(:databases) { capture("ls -xt #{databases_path}").split.reverse }
61
+ _cset(:files_backup) { capture("ls -xt #{files_backup_path}").split.reverse }
62
+
63
+ _cset(:current_release) { File.join(releases_path, releases.last) }
64
+ _cset(:current_database) { File.join(databases_path, databases.last) }
65
+ _cset(:current_files) { File.join(files_backup_path, files_backup.last) }
66
+
67
+ _cset(:previous_release) { releases.length > 1 ? File.join(releases_path, releases[-2]) : nil }
68
+ _cset(:previous_database) { databases.length > 1 ? File.join(databases_path, databases[-2]) : nil }
69
+ _cset(:previous_files) { files_backup.length > 1 ? File.join(files_backup_path, files_backup[-2]) : nil }
70
+
71
+ _cset(:current_revision) { capture("cat #{current_path}/REVISION").chomp }
72
+ _cset(:latest_revision) { capture("cat #{current_release}/REVISION").chomp }
73
+ _cset(:previous_revision) { capture("cat #{previous_release}/REVISION").chomp }
74
+
75
+ _cset(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }
76
+
77
+ # some tasks, like symlink, need to always point at the latest release, but
78
+ # they can also (occassionally) be called standalone. In the standalone case,
79
+ # the timestamped release_path will be inaccurate, since the directory won't
80
+ # actually exist. This variable lets tasks like symlink work either in the
81
+ # standalone case, or during deployment.
82
+ _cset(:latest_release) { exists?(:deploy_timestamped) ? release_path : current_release }
83
+
84
+ # This assumes that drupal lives in its own directory named 'drupal'
85
+ _cset(:drupal_root) { File.join(current_release, 'drupal') }
86
+
87
+ # =========================================================================
88
+ # These are helper methods that will be available to your recipes.
89
+ # =========================================================================
90
+
91
+ # Auxiliary helper method for the `deploy:check' task. Lets you set up your
92
+ # own dependencies.
93
+ def depend(location, type, *args)
94
+ deps = fetch(:dependencies, {})
95
+ deps[location] ||= {}
96
+ deps[location][type] ||= []
97
+ deps[location][type] << args
98
+ set :dependencies, deps
99
+ end
100
+
101
+ # Temporarily sets an environment variable, yields to a block, and restores
102
+ # the value when it is done.
103
+ def with_env(name, value)
104
+ saved, ENV[name] = ENV[name], value
105
+ yield
106
+ ensure
107
+ ENV[name] = saved
108
+ end
109
+
110
+ # logs the command then executes it locally.
111
+ # returns the command output as a string
112
+ def run_locally(cmd)
113
+ logger.trace "executing locally: #{cmd.inspect}" if logger
114
+ `#{cmd}`
115
+ end
116
+
117
+ # If a command is given, this will try to execute the given command, as
118
+ # described below. Otherwise, it will return a string for use in embedding in
119
+ # another command, for executing that command as described below.
120
+ #
121
+ # If :run_method is :sudo (or :use_sudo is true), this executes the given command
122
+ # via +sudo+. Otherwise is uses +run+. If :as is given as a key, it will be
123
+ # passed as the user to sudo as, if using sudo. If the :as key is not given,
124
+ # it will default to whatever the value of the :admin_runner variable is,
125
+ # which (by default) is unset.
126
+ #
127
+ # THUS, if you want to try to run something via sudo, and what to use the
128
+ # root user, you'd just to try_sudo('something'). If you wanted to try_sudo as
129
+ # someone else, you'd just do try_sudo('something', :as => "bob"). If you
130
+ # always wanted sudo to run as a particular user, you could do
131
+ # set(:admin_runner, "bob").
132
+ def try_sudo(*args)
133
+ options = args.last.is_a?(Hash) ? args.pop : {}
134
+ command = args.shift
135
+ raise ArgumentError, "too many arguments" if args.any?
136
+
137
+ as = options.fetch(:as, fetch(:admin_runner, nil))
138
+ via = fetch(:run_method, :sudo)
139
+ if command
140
+ invoke_command(command, :via => via, :as => as)
141
+ elsif via == :sudo
142
+ sudo(:as => as)
143
+ else
144
+ ""
145
+ end
146
+ end
147
+
148
+ # Same as sudo, but tries sudo with :as set to the value of the :runner
149
+ # variable (which defaults to "app").
150
+ def try_runner(*args)
151
+ options = args.last.is_a?(Hash) ? args.pop : {}
152
+ args << options.merge(:as => fetch(:runner, "app"))
153
+ try_sudo(*args)
154
+ end
155
+
156
+ # Attempts to grab the user and host from a specific 'web' role. If the
157
+ # role has its own user, it will use that. Otherwise, the user set either
158
+ # in the Capfile or any specific stage file will be used.
159
+ #
160
+ # If the specific web role cannot be found, it will abort with an error
161
+ # message.
162
+ def web_role(rank)
163
+ server = ''
164
+ find_servers( { :roles => :web, :only => { rank => true } } ).each do |s|
165
+ server = s.user ? "#{s.user}@#{s.host}" : "#{user}@#{s.host}"
166
+ end
167
+
168
+ if server.length > 0
169
+ server
170
+ else
171
+ abort "The option '#{rank}' is not applied to any of your 'web' roles"
172
+ end
173
+ end
174
+
175
+ # =========================================================================
176
+ # These are the tasks that are available to help with deploying web apps,
177
+ # and specifically, Rails applications. You can have cap give you a summary
178
+ # of them with `cap -T'.
179
+ # =========================================================================
180
+
181
+ namespace :deploy do
182
+ desc <<-DESC
183
+ Deploys your project. Handy wrapper to hook into the beginning of the deployment. Note that \
184
+ this will generally only work for applications that have already been deployed \
185
+ once.
186
+ DESC
187
+ task :default do
188
+ update
189
+ end
190
+
191
+ desc <<-DESC
192
+ Prepares one or more servers for deployment. Before you can use any \
193
+ of the Capistrano deployment tasks with your project, you will need to \
194
+ make sure all of your servers have been prepared with `cap deploy:setup'. When \
195
+ you add a new server to your cluster, you can easily run the setup task \
196
+ on just that server by specifying the HOSTS environment variable:
197
+
198
+ $ cap HOSTS=new.server.com deploy:setup
199
+
200
+ It is safe to run this task on servers that have already been set up; it \
201
+ will not destroy any deployed revisions or data.
202
+ DESC
203
+ task :setup, :except => { :no_release => true } do
204
+ dirs = [deploy_to, releases_path, shared_path]
205
+ dirs += shared_children.map { |d| File.join(shared_path, d) }
206
+ run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}"
207
+ run "#{try_sudo} chown -R #{user}:#{group} #{deploy_to}"
208
+ end
209
+
210
+ desc <<-DESC
211
+ Copies your project and updates the symlink. It does this in a \
212
+ transaction, so that if any of the tasks fail, all changes made \
213
+ to the remote servers will be rolled back, leaving your system in \
214
+ the same state it was in before 'update' was invoked. Usually, you \
215
+ will want to call 'deploy' instead of 'update'.
216
+ DESC
217
+ task :update do
218
+ transaction do
219
+ files.default
220
+ update_code
221
+ symlink
222
+ db.default
223
+ end
224
+ end
225
+
226
+ desc <<-DESC
227
+ Copies your project to the remote servers. This is the first stage \
228
+ of any deployment; moving your updated code and assets to the deployment \
229
+ servers. You will rarely call this task directly, however; instead, you \
230
+ should call the `deploy' task (to do a complete deploy) or the `update' \
231
+ task (if you want to perform the `restart' task separately).
232
+
233
+ You will need to make sure you set the :scm variable to the source \
234
+ control software you are using (it defaults to :git), and the \
235
+ :deploy_via variable to the strategy you want to use to deploy (it \
236
+ defaults to :remote_cache).
237
+ DESC
238
+ task :update_code, :except => { :no_release => true } do
239
+ on_rollback { run "rm -rf #{release_path}; true" }
240
+ strategy.deploy!
241
+ finalize_update
242
+ end
243
+
244
+ namespace :files do
245
+ namespace :pull do
246
+ desc "[internal] Set the temporary filename for the files archive"
247
+ task :filename, :except => { :no_release => true } do
248
+ set :tmp_filename, "/tmp/#{application}-#{stage}-files.bz2"
249
+ end
250
+
251
+ desc <<-DESC
252
+ [internal] Compresses the files directory located on your primary web \
253
+ server and sends it to your secondary web server. You asked twice \
254
+ before it actually removes/overrides anything, just in case.
255
+
256
+ If this task is aborted after the 'compress' tasks executes, the \
257
+ archive is deleted.
258
+
259
+ It is possible, to call this task, but it's a little easier to just \
260
+ run 'files:pull'.
261
+ DESC
262
+ task :paranoid_execute, :except => { :no_release => true } do
263
+ filename
264
+ Capistrano::CLI.ui.say("You are about to pull the files directory from #{stage} and send it to your development server.")
265
+ answer = Capistrano::CLI.ui.ask("Are you sure you want to do this? (y[es]/n[o]): ")
266
+ answer = answer.match(/(^y(es)?$)/i)
267
+
268
+ if answer
269
+ compress
270
+ Capistrano::CLI.ui.say("The files directory has been compressed and sent from #{stage} to your secondary machine.")
271
+ Capistrano::CLI.ui.say("This next step will remove the files directory on your development machine.")
272
+ answer = Capistrano::CLI.ui.ask("Are you sure you want to continue? (y[es]/n[o]): ")
273
+ answer = answer.match(/(^y(es)?$)/i)
274
+
275
+ if answer
276
+ decompress
277
+ else
278
+ rollback
279
+ end
280
+ else
281
+ abort "Files directory pull from #{stage} aborted!"
282
+ end
283
+ end
284
+
285
+ desc <<-DESC
286
+ [internal] Creates a compressed archive of the files directory on your \
287
+ primary web server. Then it is copied to your secondary web server and \
288
+ the original file is removed. Do not call this task directly. Instead \
289
+ let 'paranoid_execute' run this.
290
+ DESC
291
+ task :compress, :roles => :web, :only => { :primary => true } do
292
+ filename
293
+ execute = []
294
+ execute << "cd #{shared_path}"
295
+ execute << "tar cjf #{tmp_filename} files/"
296
+ execute << "scp #{tmp_filename} #{web_role(:secondary)}:#{tmp_filename}"
297
+ execute << "rm #{tmp_filename}"
298
+ run execute.join(" && ")
299
+ end
300
+
301
+ desc <<-DESC
302
+ [internal] Removes the files directory on your secondary server and \
303
+ replaces it with the files archive created by the 'compress' task.
304
+ Do not call this task directly. Instead let 'paranoid_execute' run this.
305
+ DESC
306
+ task :decompress, :roles => :web, :only => { :secondary => true } do
307
+ filename
308
+ execute = []
309
+ execute << "#{try_sudo} rm -rf #{application_wd}/sites/default/files"
310
+ execute << "#{try_sudo} tar xjf #{tmp_filename} -C #{application_wd}/sites/default"
311
+ execute << "#{try_sudo} rm #{tmp_filename}"
312
+ run execute.join(" && ")
313
+ end
314
+
315
+ desc <<-DESC
316
+ [internal] Removes the files archive if the 'paranoid_execute' task is \
317
+ aborted. Do not call this task directly. Instead let 'paranoid_execute' \
318
+ handle running this if necessary.
319
+ DESC
320
+ task :rollback, :roles => :web, :only => { :secondary => true } do
321
+ filename
322
+ try_sudo "rm #{tmp_filename}"
323
+ abort "Files directory pull was aborted before decompressing the archive. The archive has been removed."
324
+ end
325
+
326
+ desc "Pull the files directory from the live server to development"
327
+ task :default, :except => { :no_release => true } do
328
+ paranoid_execute
329
+ end
330
+ end
331
+
332
+ desc "[internal] Sets several filenames before transferring the files directory"
333
+ task :set_file_paths, :except => { :no_release => true } do
334
+ tmpdir = '/tmp'
335
+
336
+ set(:files_dir) { File.join(application_wd, 'sites/default') }
337
+ set(:filename) { File.join(tmpdir, "#{file_archive_name}.bz2") }
338
+ set(:remote_filename) { File.join(tmpdir, File.basename(filename)) }
339
+ end
340
+
341
+ desc <<-DESC
342
+ [internal] Compresses the files directory on the secondary server \
343
+ and sends them to the primary server. Do not call this task directly.
344
+ DESC
345
+ task :copy, :roles => :web, :only => { :secondary => true } do
346
+ set_file_paths
347
+
348
+ execute = []
349
+ execute << "cd #{files_dir}"
350
+ execute << "tar cjf #{filename} files/"
351
+ execute << "scp #{filename} #{web_role(:primary)}:#{remote_filename}"
352
+ execute << "rm #{filename}"
353
+ run execute.join(" && ")
354
+ end
355
+
356
+ desc <<-DESC
357
+ [internal] Removes the existing files directory on the primary server and \
358
+ unpacks the compressed files archive. The compressed archive is saved \
359
+ in 'shared/files_backup'.
360
+
361
+ In case of an error, the process is rolled back and the previous files \
362
+ archive is restored. Do not call this task directly.
363
+ DESC
364
+ task :unpack, :roles => :web, :only => { :primary => true } do
365
+ on_rollback do
366
+ if previous_files
367
+ rollback
368
+ else
369
+ logger.important "no previous files archive to rollback to, rollback of files skipped"
370
+ end
371
+ end
372
+
373
+ set_file_paths
374
+
375
+ execute = []
376
+ execute << "#{try_sudo} rm -rf #{files_path}"
377
+ execute << "tar xjf #{remote_filename} -C #{shared_path}"
378
+ execute << "#{try_sudo} chown -R #{web_user}:#{web_user} #{files_path}"
379
+ execute << "#{try_sudo} chown #{user}:#{group} #{files_path}"
380
+ execute << "mv #{remote_filename} #{files_backup_path}"
381
+ run execute.join(" && ")
382
+ end
383
+
384
+ desc <<-DESC
385
+ [internal] Removes the existing files directory and restores a previous \
386
+ archive. This task should not be called directly.
387
+ DESC
388
+ task :rollback, :except => { :no_release => true } do
389
+ run "#{try_sudo} rm -rf #{files_path}"
390
+ run "#{try_sudo} tar xjf #{previous_files} -C #{shared_path}"
391
+ run "#{try_sudo} chmod 775 #{files_path}"
392
+ run "#{try_sudo} chown -R #{web_user}:#{web_user} #{files_path}"
393
+ run "#{try_sudo} chown #{user}:#{group} #{files_path}"
394
+ end
395
+
396
+ desc "Copies the files directory from development to live."
397
+ task :default, :except => { :no_release => true } do
398
+ copy
399
+ unpack
400
+ end
401
+ end
402
+
403
+ desc <<-DESC
404
+ [internal] Touches up the released code. This is called by update_code \
405
+ after the basic deploy finishes.
406
+
407
+ This task will make the release group-writable (if the :group_writable \
408
+ variable is set to true, which is the default). It will then setup the \
409
+ correct permissions for the sites/default/files directory. It is assumed \
410
+ that your 'files' directory is in the standard location. At the moment, \
411
+ it is also assumed that your web user is apache.
412
+ DESC
413
+ task :finalize_update, :except => { :no_release => true } do
414
+ run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
415
+ run "#{try_sudo} chmod 775 #{files_path}"
416
+ run "ln -s #{files_path} #{drupal_root}/sites/default/files"
417
+ end
418
+
419
+ desc <<-DESC
420
+ [internal] Updates the symlink to the most recently deployed version. \
421
+ Capistrano works by putting each new release of your application in its own \
422
+ directory. When you deploy a new version, this task's job is to update the \
423
+ 'current' symlink to point at the new version. You will rarely need to call \
424
+ this task directly; instead, use the `deploy' task.
425
+ DESC
426
+ task :symlink, :except => { :no_release => true } do
427
+ on_rollback do
428
+ if previous_release
429
+ run "rm -f #{current_path}; ln -s #{previous_release}/drupal #{current_path}; true"
430
+ else
431
+ logger.important "no previous release to rollback to, rollback of symlink skipped"
432
+ end
433
+ end
434
+
435
+ run "rm -f #{current_path} && ln -s #{latest_release}/drupal #{current_path}"
436
+ end
437
+
438
+ desc <<-DESC
439
+ Copy files to the currently deployed version. This is useful for updating \
440
+ files piecemeal, such as when you need to quickly deploy only a single \
441
+ file. Some files, such as updated templates, images, or stylesheets, \
442
+ might not require a full deploy, and especially in emergency situations \
443
+ it can be handy to just push the updates to production, quickly.
444
+
445
+ To use this task, specify the files and directories you want to copy as a \
446
+ comma-delimited list in the FILES environment variable. All directories \
447
+ will be processed recursively, with all files being pushed to the \
448
+ deployment servers.
449
+
450
+ $ cap deploy:upload FILES=templates,controller.rb
451
+
452
+ Dir globs are also supported:
453
+
454
+ $ cap deploy:upload FILES='config/apache/*.conf'
455
+ DESC
456
+ task :upload, :except => { :no_release => true } do
457
+ files = (ENV["FILES"] || "").split(",").map { |f| Dir[f.strip] }.flatten
458
+ abort "Please specify at least one file or directory to update (via the FILES environment variable)" if files.empty?
459
+
460
+ files.each { |file| top.upload(file, File.join(current_path, file)) }
461
+ end
462
+
463
+ namespace :db do
464
+ namespace :pull do
465
+ desc <<-DESC
466
+ [internal] Asks if you are sure you want to override the development \
467
+ database with the primary server's database. If anything but 'y' or 'yes' \
468
+ is entered, the pull is aborted.
469
+ DESC
470
+ task :paranoid_execute, :roles => :web, :only => { :secondary => true } do
471
+ Capistrano::CLI.ui.say("You are about to pull the database from #{stage} and import it to your development server.")
472
+ answer = Capistrano::CLI.ui.ask("Are you sure you want to do this? (y[es]/n[o]): ")
473
+ answer = answer.match(/(^y(es)?$)/i)
474
+
475
+ if answer
476
+ run "cd #{application_wd} && #{dump} | drush sql-cli"
477
+ else
478
+ abort "Database pull from #{stage} aborted!"
479
+ end
480
+ end
481
+
482
+ desc <<-DESC
483
+ [internal] The command used on the development server to pull down \
484
+ the live database and import it. This task should never be called \
485
+ by itself. If you want to execute this command, run 'db:pull' instead.
486
+ DESC
487
+ task :dump, :roles => :web, :only => { :primary => true } do
488
+ cmd = "ssh #{web_role(:primary)} 'cd #{drupal_root} && drush -q cc all && drush sql-dump'"
489
+ end
490
+
491
+ desc <<-DESC
492
+ Dumps the database from the primary server and imports it to the \
493
+ development machine.
494
+ DESC
495
+ task :default, :except => { :no_release => true } do
496
+ paranoid_execute
497
+ end
498
+ end
499
+
500
+ desc <<-DESC
501
+ [internal] Set the name of the database backup snapshot. All snapshots \
502
+ will be stored in shared/dumps and will contain that timestamp of the \
503
+ release it is associated with.
504
+ DESC
505
+ task :snapshot_name, :except => { :no_release => true } do
506
+ set :dump_file, "#{databases_path}/#{db_snapshot_name}"
507
+ end
508
+
509
+ desc <<-DESC
510
+ [internal] Clears the cache on the development site, dumps the database, \
511
+ and compresses it for storage. All of the database snapshots sit in \
512
+ shared/dumps.
513
+
514
+ It is assumed you have drush installed on your remote server.
515
+
516
+ This task depends on the 'web' role(s). The primary 'web' role is the \
517
+ server that receives the compressed snapshot. The secondary 'web' role \
518
+ is assumed to be your development machine. This task also relies on the \
519
+ 'application_wd' variable being set in the Capfile.
520
+
521
+ This task is called by the 'deploy' task.
522
+ DESC
523
+ task :dump, :except => { :no_release => true } do
524
+ snapshot_name
525
+
526
+ primary = web_role(:primary)
527
+ secondary = web_role(:secondary)
528
+
529
+ execute = []
530
+ execute << "ssh #{secondary} 'cd #{application_wd} && drush cc all'"
531
+ execute << "ssh #{secondary} 'cd #{application_wd} && drush sql-dump' | bzip2 -c > /tmp/#{db_snapshot_name}.bz2"
532
+ execute << "scp /tmp/#{db_snapshot_name}.bz2 #{primary}:#{dump_file}.bz2"
533
+ execute << "rm -r /tmp/#{db_snapshot_name}.bz2"
534
+
535
+ run_locally execute.join(" && ")
536
+ end
537
+
538
+ desc <<-DESC
539
+ [internal] Imports the current database snapshot using drush. On \
540
+ rollback, the previous database will be imported. This task is called by \
541
+ the default deploy task, but can also be executed separately.
542
+ DESC
543
+ task :import, :roles => :web, :only => { :primary => true } do
544
+ on_rollback do
545
+ if previous_database
546
+ run "cd #{previous_release}/drupal && bzcat #{previous_database} | drush sql-cli; true"
547
+ else
548
+ logger.important "no previous database to rollback to, rollback of database skipped"
549
+ end
550
+ end
551
+
552
+ snapshot_name
553
+ run "cd #{drupal_root} && bzcat #{current_database} | drush sql-cli"
554
+ end
555
+
556
+ desc "Dumps and imports the development database to the live site"
557
+ task :default, :except => { :no_release => true } do
558
+ dump
559
+ import
560
+ end
561
+ end
562
+
563
+ namespace :rollback do
564
+ desc <<-DESC
565
+ [internal] Points the current symlink at the previous revision, imports \
566
+ the previous database snapshot, and unpacks the previous files directory. \
567
+ This is called by the rollback sequence, and should rarely (if ever) need \
568
+ to be called directly.
569
+ DESC
570
+ task :revision, :except => { :no_release => true } do
571
+ if previous_release && previous_database && previous_files
572
+ run "rm #{current_path}; ln -s #{previous_release}/drupal #{current_path}"
573
+ run "cd #{previous_release}/drupal && bzcat #{previous_database} | drush sql-cli"
574
+ files.rollback
575
+ else
576
+ abort "could not rollback the site because there is no prior release"
577
+ end
578
+ end
579
+
580
+ desc <<-DESC
581
+ [internal] Removes the most recently deployed release. This is called by \
582
+ the rollback sequence, and should rarely (if ever) need to be called directly.
583
+ DESC
584
+ task :cleanup, :except => { :no_release => true } do
585
+ execute = []
586
+ execute << "rm -rf #{current_release}"
587
+ execute << "rm -f #{current_database}"
588
+ execute << "rm -f #{current_files}"
589
+ run "if [[ `readlink #{current_path}` != #{current_release}/drupal ]]; then #{execute.join(" && ")}; fi"
590
+ end
591
+
592
+ desc <<-DESC
593
+ Rolls back to a previous version. This is handy if you ever \
594
+ discover that you've deployed a lemon; `cap rollback' and you're right \
595
+ back where you were, on the previously deployed version.
596
+ DESC
597
+ task :default do
598
+ revision
599
+ cleanup
600
+ end
601
+ end
602
+
603
+ desc <<-DESC
604
+ Pull the database and files directory from your primary server to your \
605
+ secondary server. Note that this wipes the current database and files \
606
+ on your secondary server and completely replaces them with what is on \
607
+ the primary machine. Nothing that is created with these tasks is saved.
608
+ DESC
609
+ task :pull, :except => { :no_release => true } do
610
+ db.pull.default
611
+ files.pull.default
612
+ end
613
+
614
+ desc <<-DESC
615
+ Clean up old releases. By default, the last 5 releases are kept on each \
616
+ server (though you can change this with the keep_releases variable). All \
617
+ other deployed revisions are removed from the servers. By default, this \
618
+ will use sudo to clean up the old releases, but if sudo is not available \
619
+ for your environment, set the :use_sudo variable to false instead.
620
+ DESC
621
+ task :cleanup, :except => { :no_release => true } do
622
+ count = fetch(:keep_releases, 5).to_i
623
+ if count >= releases.length
624
+ logger.important "no old releases to clean up"
625
+ else
626
+ logger.info "keeping #{count} of #{releases.length} deployed releases"
627
+
628
+ directories = (releases - releases.last(count)).map { |release|
629
+ File.join(releases_path, release) }.join(" ")
630
+
631
+ db_snapshots = (databases - databases.last(count)).map { |db_snapshot|
632
+ File.join(databases_path, db_snapshot) }.join(" ")
633
+
634
+ files_archives = (files_backup - files_backup.last(count)).map { |files_archive|
635
+ File.join(files_backup_path, files_archive) }.join(" ")
636
+
637
+ try_sudo "rm -rf #{directories} #{db_snapshots} #{files_archives}"
638
+ end
639
+ end
640
+
641
+ desc <<-DESC
642
+ Test deployment dependencies. Checks things like directory permissions, \
643
+ necessary utilities, and so forth, reporting on the things that appear to \
644
+ be incorrect or missing. This is good for making sure a deploy has a \
645
+ chance of working before you actually run `cap deploy'.
646
+
647
+ You can define your own dependencies, as well, using the `depend' method:
648
+
649
+ depend :remote, :gem, "tzinfo", ">=0.3.3"
650
+ depend :local, :command, "svn"
651
+ depend :remote, :directory, "/u/depot/files"
652
+ DESC
653
+ task :check, :except => { :no_release => true } do
654
+ dependencies = strategy.check!
655
+
656
+ other = fetch(:dependencies, {})
657
+ other.each do |location, types|
658
+ types.each do |type, calls|
659
+ if type == :gem
660
+ dependencies.send(location).command(fetch(:gem_command, "gem")).or("`gem' command could not be found. Try setting :gem_command")
661
+ end
662
+
663
+ calls.each do |args|
664
+ dependencies.send(location).send(type, *args)
665
+ end
666
+ end
667
+ end
668
+
669
+ if dependencies.pass?
670
+ puts "You appear to have all necessary dependencies installed"
671
+ else
672
+ puts "The following dependencies failed. Please check them and try again:"
673
+ dependencies.reject { |d| d.pass? }.each do |d|
674
+ puts "--> #{d.message}"
675
+ end
676
+ abort
677
+ end
678
+ end
679
+
680
+ namespace :pending do
681
+ desc <<-DESC
682
+ Displays the `diff' since your last deploy. This is useful if you want \
683
+ to examine what changes are about to be deployed. Note that this might \
684
+ not be supported on all SCM's.
685
+ DESC
686
+ task :diff, :except => { :no_release => true } do
687
+ system(source.local.diff(current_revision))
688
+ end
689
+
690
+ desc <<-DESC
691
+ Displays the commits since your last deploy. This is good for a summary \
692
+ of the changes that have occurred since the last deploy. Note that this \
693
+ might not be supported on all SCM's.
694
+ DESC
695
+ task :default, :except => { :no_release => true } do
696
+ from = source.next_revision(current_revision)
697
+ system(source.local.log(from))
698
+ end
699
+ end
700
+
701
+ end
702
+
703
+ end # Capistrano::Configuration.instance(:must_exist).load do
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: drupal-deploy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Joe Hassick
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-28 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: capistrano-ext
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Replacement for the rails deploy strategy which ships with Capistrano, allows you to deploy Drupal sites with ease. Based off of Lee Hambley's Railsless Deploy.
36
+ email: ehassick@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE.markdown
43
+ - README.markdown
44
+ files:
45
+ - .gitignore
46
+ - LICENSE.markdown
47
+ - README.markdown
48
+ - Rakefile
49
+ - VERSION
50
+ - lib/drupal-deploy.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/jh3/drupal-deploy
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --charset=UTF-8
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.7
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: A Drupal focused replacement for the default Capistrano tasks
85
+ test_files: []
86
+