mr-drupal: simpler Drupal code updating & deployment
What?
This article proposes a novel, simpler way of managing Drupal sites (where “managing” in this case is solely code updating & deployment).
It relies on only one command-line tool: mr
(“a Multiple Repository management tool”, http://joeyh.name/code/mr/), plus a Drupal plug-in for that tool: mr-drupal
, https://github.com/wimleers/mr-drupal.
Why?
If you run Drupal sites, you need some way to manage them; some way to keep them up-to-date. As of Drupal 7, there’s a built-in update manager, but it doesn’t use a VCS.
Most likely, you want use a VCS to manage your Drupal site. You may be downloading tarballs and checking their contents into your VCS, or maybe you’re using git submodules or even git-subtree
. For all of these, there’s a whole lot of process, a lot of steps, a lot to learn, and a lot of tricky things you have to think about each time you want to update something. Too much that can go wrong.
That is fine for big, commercial, team-backed sites. But it’s a pain to do for smaller sites, that maybe only are updated once every few months. When you update rarely, it becomes very easy to forget about one of those tricky things. And then, for fear of doing X or Y wrong, which might break your site, it causes you to update your site even less often.
I probably evaluated everything out there1. All of it was too complex for my needs. I want my code deployment system to be simple and preferably Drupal-agnostic.
It’s that niche that this approach to Drupal site management attempts to simplify.
How?
mr
is a small, stable shell script (written in Perl, unfortunately) to help manage multiple (VCS) repositories simultaneously. By using the Drupal plug-in provided by this project, you can leverage a subset of mr
’s functionality to simplify Drupal site management.
The Drupal plugin offers you a simple, declarative syntax to automatically clone and update git repositories for each Drupal module you use from git.drupal.org
.2
There’s almost nothing to learn. Hence, there’s almost nothing to forget. There is a single configuration file that is (mostly) declarative, so if you look at it again in 2, 6 or 12 months, it will still make sense.
Essentially, you only need to know two things:
- the desired state is declared in the
.mrconfig
file - get to the desired state by running
mr update
Note that the tool/approach described does not cover running database updates etc. (though it is possible to integrate that as well). I prefer to do this manually on smallish sites.
Getting started
Steps to be repeated on each machine:
- Install
mr
. (Package in Debian, unofficial RPM and on OS X viabrew
.) - Install the Drupal plug-in for
mr
: copy thedrupal
file in themr-drupal
repository into/usr/share/mr/
.
Creating a config file for your site
Steps to be repeated for each Drupal site:
-
Create a
.mrconfig
file (don’t forget the leading period!), it should look like this:# Use the Drupal plug-in for `mr`.[DEFAULT]include = cat /usr/share/mr/drupal# Drupal itself.[www]project = drupalversion = 7.19# Set the proper file system permissions on all Drupal files. The owner is# set to the current user, the group is set to www-data.# See http://drupal.org/node/244924.fixups = drupal_set_permissions `whoami` 'www-data'# A branch of a Drupal module.[www/sites/all/modules/cdn]project = cdnversion = 7.x-2.x# A tag of a Drupal module.[www/sites/all/modules/comment_notify]project = comment_notifyversion = 7.x-1.1# Custom themes live in a non-git.drupal.org repository. `mr` is smart enough# to automatically know this is a git repository, and knows how to update it.[www/sites/wimleers.com/themes]checkout = git clone git://github.com/wimleers/wimleers.com-themes.git themes
As you can see, it’s easy to mix in custom modules/repositories.
- Run
mr update
. Your Drupal site will be built based on your.mrconfig
file, relative to where the.mrconfig
file lives.3 - Optionally check in the
.mrconfig
file into a VCS, so you can roll back to previous versions. Highly preferable, but not essential.
In the above example, we leveraged one “advanced” feature of mr
: the ability to do “fixups” (after each checkout/update). In this case, to guarantee correct permissions using the drupal_set_permissions()
function. mr-drupal
also includes a git_apply_patch()
function to git apply
patches (it also automatically detects whether the patch has already been applied).
Note that there is nothing Drupal-specific about any of this! You can check out Drupal modules at www/sites/all/modules/
, a static “status” site at www/status
, HTML5 presentations at www/talks/
, and so on. Things won’t break because Drupal or Drush are updated.
To update your site
-
Modify the
.mrconfig
file: change version numbers, add modules. Remove a module by adding a line like this:deleted = true
. -
Run
mr update
. That’s it!4
Advanced: environment-specific (Dev vs. Prod) actions
If the above doesn’t look like it would cut it for you because you need to set up things differently for your local development setup and your production server, then you’re right (i.e. DTAP).
To easily deal with that too, this drupal
extension for mr
provides three functions:
whoami()
— a simple wrapper around thewhoami
command.hostname()
— a simple wrapper around thehostname
command.on()
— uses the two functions above to be able to write something like
on 'wim@wimleers.local'
, which evaluates to true if those are indeed the
current user and host.
You can then upgrade the above example of
[DEFAULT]include = cat /usr/share/mr/drupal[www]project = drupalversion = 7.19fixups = drupal_set_permissions `whoami` 'www-data'
to something like this:
[DEFAULT]include = cat /usr/share/mr/drupallib = get_dtap() { if on 'wim@wimleers.local'; then echo 'development' else echo 'production' fi } drupal_fs_group() { if [ "$(get_dtap)" = 'development' ]; then echo 'staff' # Mac OS X else echo 'www-data' # Linux fi }[www]project = drupalversion = 7.19fixups = drupal_set_permissions `whoami` `drupal_fs_group`
In lib
, you can define functions you want to use. In this case, we defined the get_dtap()
and drupal_fs_group()
functions. The latter uses the former.
Now the group used for enforcing the correct permissions is set based on the environment.
But really, you can do whatever you want: anything that can be used in a shell script can also be used here. This is the part of mr-drupal
where you can make things as complex as you’d like. The complex parts are opt-in.
Conclusion
One file (.mrconfig
) and one command (mr update
) together cover 90% of the smallish Drupal site management needs.
Easy to understand, very few dependencies, extremely customizable.
Simple for simple deployments, complex for complex deployments. Instead of always complex.
P.S.: I’ve been using mr-drupal
to keep my sites updated for the past several months without problems. Rather than spending >1 hour (to update locally, then look up drush
commands and triple-check everything before calling drush rsync
), it now takes me just a few minutes to update a site!
-
I evaluated the following:
- Drupal core’s recommended git practices which involve checking in contents of tarballs: implies loss of version history and duplicates code
dog
— short for “Drupal on git”: unfortunately too immature, because this got me very excitedgit
submodules: too many steps to add or update modules, too easy to forget to pull in submodule updates, nightmare to deal with patchesgit-subtree
: even more complex thangit
submodules- Drush Deploy: only handles the effective “deploy” step, still requires either of the above
git
strategies drush make
: took years for it to “kind of” support updates aka “remakes” (which inspires little confidence), it is highly Drupal-specific and personally I have very bad experiences withdrush make
-
As is the case for any site that has its modules on disk as
git
repositories: you must use the Git deploy module to ensure Drupal core’s “Available Updates” report continues to work. ↩ -
Speed it up by telling it to work in parallel:
mr -j 10 update
would do up to 10 updates in parallel. ↩ -
The only exception: those that are marked as deleted.
mr
does not actually delete them for you. For example, if you’ve marked thedevel
anddrupad
modules as deleted and thedevel
module no longer exists, it’ll output something like this:$ mr --stats updatemr update: /htdocs/wimleers.com/www/sites/all/modules/cdnUpdating 'cdn' to version 7.x-2.5 (from 7.x-2.3)...Done.mr error: /htdocs/wimleers.com/www/sites/all/modules/drupad/ should be deleted yet still exists[…]mr update: finished (29 ok; 1 failed; 1 skipped)mr update: (skipped: /htdocs/wimleers.com/www/sites/all/modules/devel/)mr update: (failed: /htdocs/wimleers.com/www/sites/all/modules/drupad/)
The
devel
module is skipped (no error!) because it’s marked as deleted and doesn’t exist. Thedrupad
module is marked with an error message (because it’s also marked as deleted but still exists). ↩