Labs

Keeping your Drupal Features in sync

By John Ennew | 2nd August 2013

This covers some best practice tips for automating your development processes using Features, updatedb and cloud hooks.

We use Features for putting db components into code in Drupal 7. Best practice will see any of your Features get updated as soon as the relevant component is modified on a development site. This ensures the codebase always has the latest version of the Feature within it so that it can be committed to the version control system (VCS) and tracked.

Sometimes you'll have a number of Features with modifications queued up ready for release to live. If some time has elapsed since they were modified or if several developers have made changes then you'll need some way of recording what Features have changed via release notes to TODO tasks so you know what to revert after the code has been pushed to live. Also, you'll need to share modifications between other developers so that when they get the latest codebase they need to know to revert a Feature which has changed.

Ideally, this would just happen without the need to remember to do it. One solution is to record that Feature reversions are required using update hooks. So, for example, you make a modification to the Feature mysite_news, you'll add an update hook to one of your custom modules install files. Many sites run a master or system module for this purpose to keep these hooks neatly together in one place (although they could easily sit in mysite_news.install)

/**
 * Revert all features in mysite_news.
 */
function mysite_system_update_7001() {
  features_revert_module('mysite_news');
}

Now when the update process is run (e.g. via drush updatedb), mysite_news will be reverted. And because you added it when you made the change, you don't have to remember that it needs doing when you release because you always run updatedb after updating your code base (don't you? You should, it's liberating).

But, features_revert_module function doesn't actually exist in Features, you'll need this patch (and let the module maintainers know if you like it) ...
https://drupal.org/node/1871986#comment-7712147

Automating updatedb with cloud hooks

In an ideal world, updatedb will just happen on code deployment. Acquia hosting (and others) provide cloud hooks for this purpose. On Acquia hosting, add the following code to a file called /hooks/common/post-code-deploy/update-db.sh to the hooks directory (same level as docroot)…

#!/bin/sh
# Cloud Hook: update-db
site="$1"
target_env="$2"
drush @$site.$target_env updatedb --yes

Now when code is released to any Acquia environment, drush updatedb is executed after deployment and Features that need an update are updated.

For more information on cloud hooks, check out these links:
https://www.acquia.com/blog/how-cloud-api-and-cloud-hooks
https://github.com/acquia/cloud-hooks

Comments

Prasad Shir's picture
Prasad Shir
Prasad Shir

This is a great post! Just one suggestion, instead of writing a hook_update with features_revert_module() function and calling updatedb from Drush in the Cloud Hook, why not write drush features-revert-all command in the cloud hook itseld?

The hook will look something like this

#!/bin/bash
site=$1
env=$2
drush @$site.$env features-revert-all
dv-admin's picture
dv-admin
dv-admin

This is certainly possible and I expect many people use this approach. I prefer not to do it this way as features-revert-all is quite an intensive operation on larger sites, update hooks allow more targeted updates.

Given you are already using Acquia's webhooks, you can just add another one for "drush fra -y". That way your features are always reverted each time you deploy.

John Ennew's picture
John Ennew
John Ennew

Also, if you like the excellent master module, drush master-ensure-modules -y ensures all the required modules for the environment are enabled or disabled.

But the function features_revert() does exist, you can use it! It's potentially even more useful -- as you explicitly state which components within your feature to revert... like this:

  features_revert(array(
    'my_first_module' => array(
      'field',
    ),
    'my_other_module' => array(
      'field',
      'field_group',
      'views_view',
    ),
  ));

That way, you don't have to revert everything in a features module -- which is incredibly handy when there's a possibility that some things in a feature have been (deliberately) overridden in a production environment by an administrator. So doing it this way only reverts the bits that you've made changes to, and leaves the rest untouched.

Also, it looks like  features_revert_module() has now been committed :-) So use that when you absolutely know that you want to intentionally revert everything in a feature module, or features_revert() when you actually only want to update the things that you meant to change (which I suspect is more often, depending on people's workflows).

There's also a similar features_rebuild() function for when you don't necessarily want to 'revert' but you do want to 'rebuild' -- which means intentionally overridden items (i.e. those that have been edited & saved) will remain untouched and overridden, but any changes in a feature that were in the default state (e.g. those using 'default' hooks such as views, fields, etc - but which haven't been saved/overridden since the last rebuild) will be applied.

John Ennew's picture
John Ennew
John Ennew

Hi James - some great tips there, thanks for sharing! I prefer the revert the whole module approach because its more straight forward, but I can see there will be occasions where more fine grained control would be useful.

Add new comment