Managing Local and Remote Environments - Drush for Developers Second Edition (2015)

Drush for Developers Second Edition (2015)

Chapter 5. Managing Local and Remote Environments

I remember the first time I used a Drush site alias. Someone in the team mentioned them and after reading the documentation, I set up one for the production environment. Then, to test it, I entered drush @prod.example.com core-status. Drush silently logged in to the production server, ran the command, and printed the result back to my screen. It was a revelation. Until then, running Drush commands in the production environment involved:

· Opening a remote session with the production environment

· Changing directory to the root of the Drupal project

· Running Drush commands

· Closing the remote session

The fact of being able to run Drush commands for the production environment from my local environment was mind-blowing. A stream of ideas came to my head: I would be able to check module versions, run a small piece of code to test something, download the database and files, and so on. All my respects to the Drush team (especially to Greg Anderson) for creating site aliases.

In this chapter, we will see how Drush site aliases can be configured to manage the different environments of a Drupal project. Here are the topics that we will cover:

· Managing local environments

· Managing remote environments

· Special site aliases

· Running the update path in remote sites

· Copying database and files between environments

Managing local environments

Drush site aliases offer a useful way to manage local environments without having to be within Drupal's root directory.

A site alias consists of an array of settings for Drush to access a Drupal project. They can be defined in different locations, using various file structures. You can find all of its variations at drush topic docs-aliases. In this chapter, we will use the following variations:

· We will define local site aliases at $HOME/.drush/aliases.drushrc.php, which are accessible anywhere for our command-line user.

· We will define a group of site aliases to manage the development and production environments of our sample Drupal project. These will be defined at sites/all/drush/example.aliases.drushrc.php.

In the following example, we will use the site-alias command to generate a site alias definition for our sample Drupal project:

$ cd /home/juampy/projects/example

$ drush --uri=example.local site-alias --alias-name=example.local @self

$aliases["example.local"] = array (

'root' => '/home/juampy/projects/example',

'uri' => 'example.local',

'#name' => 'self',

);

The preceding command printed an array structure for the $aliases variable. You can see the root and uri options here, which we saw in previous chapters when we needed to tell Drush about the location of our Drupal project. There is also an internal property called #name that we can ignore. Now, we will place the preceding output at $HOME/.drush/aliases.drushrc.php so that we can invoke Drush commands to our local Drupal project from anywhere in the command-line interface:

<?php

/**

* @file

* User-wide site alias definitions.

*

* Site aliases defined here are available everywhere for the current user.

*/

// Sample Drupal project.

$aliases["example.local"] = array (

'root' => '/home/juampy/projects/example',

'uri' => 'example.local',

);

Here is how we use this site alias in a command. The following example is running the core-status command for our sample Drupal project:

$ cd /home/juampy

$ drush @example.local core-status

Drupal version : 7.29-dev

Site URI : example.local

Database driver : mysql

Database username : root

Database name : drupal7x

Database : Connected

...

Drush alias files : /home/juampy/.drush/aliases.drushrc.php

Drupal root : /home/juampy/projects/example

Site path : sites/default

File directory path : sites/default/files

Drush loaded our site alias file and used the root and uri options defined in it to find and bootstrap Drupal. The preceding command is equivalent to the following one, which we saw in previous chapters:

$ drush --root=/home/juampy/projects/example \

--uri=example.local core-status

While $HOME/.drush/aliases.drushrc.php is a good place to define site aliases in your local environment, /etc/drush is a first class directory to place site aliases in servers. Let's discover now how we can connect to remote environments via Drush.

Managing remote environments

Site aliases that reference remote websites can be accessed by Drush through a password-less SSH connection (http://en.wikipedia.org/wiki/Secure_Shell). Before we start with these, let's make sure that we meet the requirements.

Verifying requirements

First, it is recommended to install the same version of Drush in all the servers that host your website. Drush will fail to run a command if it is not installed in the remote machine except for core-rsync, which runs rsync, a non-Drush command that is available in Unix-like systems.

If you can already access the server that hosts your Drupal project through a public key, then skip to the next section. If not, you can either use the pushkey command from Drush extras (https://www.drupal.org/project/drush_extras), or continue reading to set it up manually.

Accessing a remote server through a public key

The first thing that we need to do is generate a public key for our command-line user in our local machine. Open the command-line interface and execute the following command. We will explain the output step by step:

$ cd $HOME

$ ssh-keygen

Generating public/private rsa key pair.

Enter file in which to save the key (/home/juampy/.ssh/id_rsa):

By default, SSH keys are created at $HOME/.ssh/. It is fine to go ahead with the suggested path in the preceding prompt; so, let's hit Enter and continue:

Created directory '/home/juampy/.ssh'.

Enter passphrase (empty for no passphrase): *********

Enter same passphrase again: *********

If the .ssh directory does not exist for the current user, the ssh-keygen command will create it with the correct permissions. We are next prompted to enter a passphrase. It is highly recommended to set one as it makes our private key safer. Here is the rest of the output once we have entered a passphrase:

Your identification has been saved in /home/juampy/.ssh/id_rsa.

Your public key has been saved in /home/juampy/.ssh/id_rsa.pub.

The key fingerprint is:

6g:bf:3j:a2:00:03:a6:00:e1:43:56:7a:a0:c7:e9:f3 juampy@juampy-box

The key's randomart image is:

+--[ RSA 2048]----+

| |

| |

|.. |

|o..* |

|o + . . S |

| + * = . . |

| = O o . . |

| *.o * . . |

| .oE oo. |

+-----------------+

The result is a new hidden directory under our $HOME path named .ssh. This directory contains a private key file (id_rsa) and a public key file (id_rsa.pub). The former is to be kept secret by us, while the latter is the one we will copy into remote servers where we want to gain access.

Now that we have a public key, we will announce it to the SSH agent so that it can be used without having to enter the passphrase every time:

$ ssh-add ~/.ssh/id_rsa

Identity added: /home/juampy/.ssh/id_rsa (/home/juampy/.ssh/id_rsa)

Our key is ready to be used. Assuming that we know an SSH username and password to access the server that hosts the development environment of our website, we will now copy our public key into it. In the following command, replace exampledev anddev.example.com with the username and server's URL of your server:

$ ssh-copy-id exampledev@dev.example.com

exampledev@dev.example.com's password:

Now try logging into the machine, with "ssh

'exampledev@dev.example.com'", and check

in: ~/.ssh/authorized_keys to make sure we

haven't added extra keys that you weren't

expecting.

Our public key has been copied to the server and now we do not need to enter a password to identify ourselves anymore when we log in to it. We could have logged on to the server ourselves and manually copied the key, but the benefit of using the ssh-copy-idcommand is that it takes care of setting the right permissions to the ~/.ssh/authorized_keys file. Let's test it by logging in to the server:

$ ssh exampledev@dev.example.com

Welcome!

We are ready to set up remote site aliases and run commands using the credentials that we have just configured. We will do this in the next section.

If you have any trouble setting up SSH authentication, you can find plenty of debugging tips at https://help.github.com/articles/generating-ssh-keys and http://git-scm.com/book/en/Git-on-the-Server-Generating-Your-SSH-Public-Key.

Defining a group of remote site aliases for our project

Before diving into the specifics of how to define a Drush site alias, let's assume the following scenario: you are part of a development team working on a project that has two environments, each one located in its own server:

· Development, which holds the bleeding edge version of the project's codebase. It can be reached at http://dev.example.com.

· Production, which holds the latest stable release and real data. It can be reached at http://www.example.com.

· Additionally, there might be a variable amount of local environments for each developer in their working machines; although, these do not need a site alias.

Given the preceding scenario and assuming that we have SSH access to the development and production servers, we will create a group of site aliases that identify them. We will define this group at sites/all/drush/example.aliases.drushrc.php within our Drupal project:

<?php

/**

* @file

*

* Site alias definitions for Example project.

*/

// Development environment.

$aliases['dev'] = array(

'root' => '/var/www/exampledev/docroot',

'uri' => 'dev.example.com',

'remote-host' => 'dev.example.com',

'remote-user' => 'exampledev',

);

// Production environment.

$aliases['prod'] = array(

'root' => '/var/www/exampleprod/docroot',

'uri' => 'www.example.com',

'remote-host' => 'prod.example.com',

'remote-user' => 'exampleprod',

);

The preceding file defines two arrays for the $aliases variable keyed by the environment name. Drush will find this group of site aliases when being invoked from the root of our Drupal project. There are many more settings available, which you can find by reading the contents of the drush topic docs-aliases command.

These site aliases contain options known to us: root and uri refer to the remote root path and the hostname of the remote Drupal project. There are also two new settings: remote-host and remote-uri. The former defines the URL of the server hosting the website, while the latter is the user to authenticate Drush when connecting via SSH.

Now that we have a group of Drush site aliases to work with, the following section will cover some examples using them.

Using site aliases in commands

Site aliases prepend a command name for Drush to bootstrap the site and then run the command there. Our site aliases are @example.dev and @example.prod. The word example comes from the filename example.aliases.drushrc.php, while dev and prod are the two keys that we added to the $aliases array. Let's see them in action with a few command examples:

Check the status of the Development environment:

$ cd /home/juampy/projects/example

$ drush @example.dev status

Drupal version : 7.26

Site URI : http://dev.example.com

Database driver : mysql

Database username : exampledev

Drush temp directory : /tmp

...

Drush alias files :

/home/juampy/projects/example/sites/all/drush/example.aliases.drushrc.php

Drupal root : /var/www/exampledev/docroot

...

The preceding output shows the current status of our development environment. Drush sent the command via SSH to our development environment and rendered back the resulting output. Most Drush commands support site aliases. Let's see the next example.

Log in to the development environment and copy all the files from the files directory located at the production environment:

$ drush @example.dev site-ssh

Welcome to example.dev server!

$ cd `drush @example.dev drupal-directory`

$ drush core-rsync @example.prod:%files @self:%files

You will destroy data from /var/www/exampledev/docroot/sites/default/files and replace with data from exampleprod@prod.example.com:/var/www/exampleprod/docroot/sites/default/files/

Do you really want to continue? (y/n): y

Note the use of @self in the preceding command, which is a special Drush site alias that represents the current Drupal project where we are located. We are using @self instead of @example.dev because we are already logged inside the development environment. Now, we will move on to the next example.

Open a connection with the Development environment's database:

$ drush @example.dev sql-cli

Welcome to the MySQL monitor. Commands end with ; or \g.

mysql> select database();

+------------+

| database() |

+------------+

| exampledev |

+------------+

1 row in set (0.02 sec)

The preceding command will be identical to the following set of commands:

drush @example.dev site-ssh

cd /var/www/exampledev

drush sql-cli

However, Drush is so clever that it opens the connection for us. Isn't this neat? This is one of the commands I use most frequently. Let's finish by looking at our last example.

Log in as the administrator user in production:

$ drush @example.prod user-login

http://www.example.com/user/reset/1/some-long-token/login

Created new window in existing browser session.

The preceding command creates a login URL and attempts to open your default browser with it. I love Drush!

Special site aliases

We defined two site aliases for our project: one for the development environment and one for the production environment. However, when we list all the available site aliases, we see a few extra ones:

$ cd /home/juampy/projects/example

$ drush site-alias

example

example.dev

example.local

example.prod

none

self

We can see that this project has six Drush site aliases. We are aware of @example.local, @example.dev, and @example.prod, but what about the others? Those are site aliases defined by Drush automatically. We will explain each of them in the following sections through examples.

Running a command on all site aliases of a group

The example alias is a group site alias for our example project. If you prepend a command with it, the command will be executed on all the site aliases defined under this group. Our example.aliases.drushrc.php file defines two aliases: dev and prod. This can be useful for analysis tasks such as to check which version of a module each environment has. The following example checks this for the Metatag module:

$ cd /home/juampy/projects/example

$ drush @example pm-info --fields=version metatag

You are about to execute 'pm-info metatag' non-interactively (--yes forced) on all of the following targets:

@example.dev

@example.prod

Continue? (y/n): y

@example.dev >> Version : 7.x-1.0-rc2+5-dev

@example.prod >> Version : 7.x-1.0-rc2

As we can see from the preceding code, after prompting for a confirmation, Drush has logged in to each environment, executed the pm-info command, and printed back the result. We are using a slightly more recent version of the Metatag module at the development environment than at production. The +5-dev bit is a syntax used by Drupal.org to inform how far a development release is ahead of a given release.

Avoiding a Drupal bootstrap with @none

The @none alias is another special Drush site alias. It forces Drush not to bootstrap the current Drupal project. We used it in Chapter 2, Keeping Database Configuration and Code Together, in order to download the Registry Rebuild command without bootstrapping Drupal because at that moment it was broken. Here is an example where we change directory to the root of our Drupal project and run the core-status command with the @none site alias, which will make Drush ignore the Drupal project where we are located:

$ cd /home/juampy/projects/example

$ drush @none status

PHP executable : /usr/bin/php

PHP configuration : /etc/php5/cli/php.ini

PHP OS : Linux

Drush version : 7.0.0-alpha3

Drush temp directory : /tmp

Drush configuration : /home/juampy/.drush/drushrc.php

Drush alias files : sites/all/drush/example.aliases.drushrc.php

The result of the command just refers to the Drush environment and not to the Drupal project, thanks to the use of the @none site alias. Drush did not go through any of the Drupal bootstrap phases.

Referencing the current project with @self

Last but not least, @self is used in Drush commands that accept a site alias as an argument when we want to reference the current project where we are located. Commands that support this are, among others, sql-sync and core-rsync. Here is an example where we install a copy of the development environment's database into our local environment:

$ drush sql-sync @example.dev @self

You will destroy data in example and replace with data from server.juampy.com/drupaldev.

Do you really want to continue? (y/n): y

Starting to dump database on Source. [ok]

Copying dump file from Source to Destination. [ok]

Starting to import dump file onto Destination database. [ok]

In the preceding command, @self is the target destination of the database dump. This means that the database dump extracted from the development environment will be copied into our local environment, which is where we are currently located in the command line.

Adding site alias support to the update path

In Chapter 2, Keeping Database Configuration and Code Together, we introduced the update path as a list of steps to update a database so that it gets in sync with the exported configuration in code. Then, in Chapter 4, Error Handling and Debugging, we made the update path more flexible by wrapping it in a Drush command and taking advantage of Drush's command hooks in order to perform steps before and after it runs. In this chapter, we will go one step further by implementing the following improvements:

· Make sure that the registry-rebuild and features-revert-all commands are available.

· Add an example in the command definition using a site alias.

· Implement error handling by inspecting the returned status from each command. If a command fails, we will stop the process immediately.

Inspecting the command implementation and hooks

We will now go through our update path command, located at sites/all/drush/updatepath.drush.inc, explaining the new features hook by hook. The first one at the top of the file is the command definition:

<?php

/**

* @file

* Runs a set of steps to update a database to be in line with code.

*/

/**

* Implements hook_drush_command().

*/

function updatepath_drush_command() {

$items = array();

$items['updatepath'] = array(

'description' => 'Runs the update path in the bootstrapped site performing tasks such as database updates, reverting features, etc.',

'drush dependencies' => array('registry_rebuild', 'features'),

'examples' => array(

'drush updatepath' => 'Runs the updatepath in the current Drupal project.',

'drush @example.dev updatepath' => 'Runs the updatepath in the Drupal project referenced by @example.dev.',

),

);

return $items;

}

We have added a couple of settings to the command definition: the first one is drush dependencies, which tells Drush to make sure that the command files registry_rebuild.drush.inc and features.drush.inc are found or abort otherwise. The second one is an example of how to execute the update path using a site alias. As you can see, all you need to do is prepend the command with the site alias name and Drush will run each of the steps of the update path in the Drupal site referenced by the site alias. Now, we will see the command hook that gets triggered before the command starts:

/**

* Implements drush_hook_pre_command().

*/

function drush_updatepath_pre_updatepath() {

drush_log('Enabling maintenance mode and killing active sessions.', 'status');

$return = drush_invoke_process('@self', 'variable-set', array('maintenance_mode', 1), array(

'yes' => TRUE,

'always-set' => TRUE,

));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_PRE_MAINTENANCE', 'Could not enable maintenance mode.');

}

$return = drush_invoke_process('@self', 'sql-query', array('truncate table sessions'));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_PRE_SESSIONS', 'Could not truncate user sessions.');

}

}

In drush_hook_pre_command(), we perform actions before updating the database. In this case, the actions consist of enabling maintenance mode and killing all user sessions. We have added error checks to these tasks; so if they fail, we stop the process. The next step is our actual command implementation:

/**

* Implements drush_hook_command().

*/

function drush_updatepath() {

// Registry rebuild.

$return = drush_invoke_process('@self', 'registry-rebuild', array(), array('no-cache-clear' => TRUE));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_RR', 'registry-rebuild failed.');

}

// Database updates.

$return = drush_invoke_process('@self', 'updatedb', array(), array('yes' => true));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_UPDB', 'updatedb failed.');

}

// Clear Drush cache (sometimes needed before reverting Features components).

$return = drush_invoke_process('@self', 'cache-clear', array('type' => 'drush'));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_CC_DRUSH', 'cache-clear failed.');

}

// Revert all Features components.

$return = drush_invoke_process('@self', 'features-revert-all', array(), array(

'yes' => TRUE,

));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_FRA', 'features-revert-all failed.');

}

// Clear all caches.

$return = drush_invoke_process('@self', 'cache-clear', array('type' => 'all'));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_CC_ALL', 'cache-clear failed.');

}

}

We have added error checks after each step. If something goes wrong, we log an error and terminate. Note that each command invocation being made with drush_invoke_process() uses @self as the site alias. You might think that in order for this command to support Drush site aliases, it should pick up the site alias from the command line and use it. However, when using a site alias, Drush sends each of these commands to be run at the Drupal project referenced by the site alias. This means that @self would point our local Drupal project if we run drush updatepath or a remote Drupal project if we run drush @example.prod updatepath. This is the beauty of site aliases. We will now see the implementation of the post-command actions that will run if there were no errors up to this point:

/**

* Implements drush_hook_post_command().

*/

function drush_updatepath_post_updatepath() {

drush_log('Disabling maintenance mode.', 'success');

$return = drush_invoke_process('@self', 'variable-delete', array('maintenance_mode'), array(

'yes' => TRUE,

'exact' => TRUE,

));

if ($return['error_status']) {

return drush_set_error('UPDATEPATH_POST_MAINTENANCE', 'Could not disable maintenance mode.');

}

}

We are simply disabling maintenance mode in the preceding code and logging a message. We will now see the last section of our command file that implements rollback hooks to take action if there is an error:

/**

* Implements drush_hook_command_rollback().

*/

function drush_updatepath_rollback() {

drush_log('Oh no! Something went wrong. Review the above log and disable maintenance mode when done.', 'error');

drush_set_context('UPDATEPATH_ROLLBACK', TRUE);

}

/**

* Implements drush_hook_pre_command_rollback().

*/

function drush_updatepath_pre_updatepath_rollback() {

if (!drush_get_context('UPDATEPATH_ROLLBACK')) {

drush_log('Oh no! Something went wrong prior to start the update path. Check the status of the maintenance mode and the sessions table.', 'error');

}

}

We have implemented two rollback hooks: one is for the pre-command actions and the second one is for the command implementation. As you can see, we are simply logging a sensible message with tips on how to proceed if an error happens. Note the use ofdrush_set_context() and drush_get_context(), which is helping us to avoid getting two messages if an error happens during the command execution; one for drush_updatepath_rollback() and then a second one for drush_hook_pre_command_rollback().

Running the update path with a site alias

Our updatepath command has a built-in site alias support. We actually did not have to add anything special to it apart from using @self at each of the update commands.

Assuming that we have deployed the updatepath command into the development environment of our example project, let's now see the result of running it from our local environment using a site alias:

$ cd /home/juampy/projects/example

$ drush @example.dev updatepath

Enabling maintenance mode and killing active sessions. [status]

maintenance_mode was set to "1". [success]

The registry has been rebuilt via registry_rebuild (A). [success]

The registry has been rebuilt via

drush_registry_rebuild_cc_all (B). [success]

The caches have not been cleared. It is recommended you

clear the Drupal caches as soon as possible. [warning]

All registry rebuilds have been completed. [success]

No database updates required [success]

'all' cache was cleared. [success]

Finished performing updates. [ok]

'drush' cache was cleared. [success]

Current state already matches defaults, aborting. [ok]

'all' cache was cleared. [success]

Disabling maintenance mode. [success]

maintenance_mode was deleted. [success]

The preceding command was executed at our remote server where http://dev.example.com runs. Drush logged in to the server via SSH and executed the sequence of commands printing its progress in real time. If we want to run the update path on the production environment, we would do it with drush @example.prod updatepath or we could also do drush @example.prod ssh and then run it there with drush updatepath. Let's now see an example when an error happens. The rollback mechanism should start and our rollback hooks will be executed:

$ drush @example.dev updatepath

Enabling maintenance mode and killing active sessions. [status]

maintenance_mode was set to "1". [success]

The registry has been rebuilt via registry_rebuild (A). [success]

The registry has been rebuilt via

drush_registry_rebuild_cc_all (B). [success]

The caches have not been cleared. It is recommended you

clear the Drupal caches as soon as possible. [warning]

All registry rebuilds have been completed. [success]

No database updates required [success]

'all' cache was cleared. [success]

Finished performing updates. [ok]

ouch! [error]

Oh no! Something went wrong. Review the above log

and disable maintenance mode when done. [error]

We can see that the command stopped its execution and the rollback mechanism logged as message alerting us that the maintenance mode is still active. We could now log in to the development environment with drush @example.dev ssh and inspect what went wrong.

Copying database and files between environments

Now that we have our site aliases configured, we can benefit from running two of the most powerful Drush commands: sql-sync and core-rsync. The former is used to copy the database from a Drupal project to another, while the latter copies files between Drupal projects. In this section, we will see some suggestions to make them safer and efficient in our projects.

Previously in this chapter, we have seen examples of both these commands. They take two site aliases as arguments. The first one is the from (also known as source) and the second one is the to (also known as destination). I like to mentally tell it to myself when I type these commands so that I make sure that I copy them in the right direction. I mumble drush sql-sync from to. The reason for such thoroughness is that when running core-rsync and sql-sync commands, the order does matter a lot. Here is an example: while working in a development team, we would run these commands every once in a while to update our local environment with the latest code and database:

$ cd /home/juampy/projects/example

# Gets latest version of code in the current branch.

$ git pull --rebase

$ drush sql-sync @example.prod @self # Downloads production database.

$ drush updatepath # Syncs the database with code.

However, some day, we might not pay enough attention and mistype the sql-sync command in the following way:

$ drush sql-sync @self @example.prod

Oh no! We just copied our local database full of testing content with photos of bunnies into production.

As you can see, the flexibility and power that Drush site aliases bring to a team comes at a risk. In order to prevent the preceding catastrophe, we could do one or more of the following options:

· Only give SSH access to the production environment to a few developers, so the preceding command wouldn't work as Drush would not be able to log in to production

· Copy production's database into development every night and ask the team to sync with development instead of production

· Suggest the team to use a Drush shell alias when running sql-sync

· Block these commands through Drush's API when the destination is production

The first option is probably the safest, but you still need to give the rest of the team a chance to download a relatively fresh copy of the production environment so that it can test its code changes locally. The second option can be achieved with the help of a Continuous Integration tool such as Jenkins, which will be covered in Chapter 6, Setting Up a Development Workflow. The last two options are the ones that we will implement in this chapter.

Defining Drush shell aliases for a team

Drush shell aliases are command shortcuts. It resembles Unix's command-line aliases, which are normally defined at $HOME/.bashrc. Here are some of the command-line aliases I have in my local environment:

$ alias

...

alias example='cd /home/juampy/projects/example'

alias egrep='egrep --exclude=*~ --exclude-dir=.git --exclude-dir=files'

alias chmod8='sudo setfacl -R -m u:www-data:rwX -m u:`whoami`:rwX sites/default/files'

alias killd8='rm -rf sites/default/files && rm sites/default/settings.php && dr...'

...

Given the preceding list, if I type example in the command line, it would be the same as if I had run cd /home/juampy/projects/example. Likewise, Drush supports shell aliases in configuration files for commands that we use frequently. Drush configuration files can be placed at several places in our system for Drush to load them, and you can find the full list at drush topics docs-configuration. In our case, we will just create a Drush configuration file for our Drupal project at sites/all/drush/drushrc.php with the following aliases:

<?php

/**

* @file

* Drush configuration for Sample project.

*/

// Shell aliases.

$options['shell-aliases']['syncdb'] = '--verbose --yes sql-sync @example.dev @self --create-db';

$options['shell-aliases']['syncfiles'] = '--verbose --yes core-rsync @example.dev:%files/ @self:%files/';

We have defined two Drush shell aliases in the preceding code: syncdb and syncfiles. Whenever we run drush syncdb or drush syncfiles, Drush will execute the command that these two wrap. Before we try them, let's make sure that Drush can load them with theshell-alias command:

$ cd /home/juampy/projects/example/

$ drush shell-alias

wipe : cache-clear all

unsuck : pm-disable -y overlay,dashboard

offline : variable-set -y --always-set maintenance_mode 1

online : variable-delete -y --exact maintenance_mode

pm-clone : pm-download --gitusername=juampy@git.drupal.org

--package-handler=git_drupalorg

syncdb : --verbose --yes sql-sync @example.dev @self --create-db

syncfiles : --verbose --yes rsync @example.dev:%files/ @self:%files/

We can see our custom Drush shell aliases at the bottom of the list. The other ones listed are some useful shortcuts that you can find at drush topics docs-configuration. I encourage you to run drush topic docs-configuration > $HOME/.drush/drushrc.php and then adjust the resulting file as it comes with very useful settings for Drush that will be available for all your Drupal projects.

Let's now test our syncdb shell alias:

$ drush syncdb

Initialized Drupal 7.31 root directory at

/home/juampy/projects/example [notice]

You will destroy data in example and replace with data from

dev.example.com/drupaldev.

Do you really want to continue? (y/n): y

Starting to create database on Destination. [ok]

Creating database example. Any possible existing database

will be dropped!

Do you really want to continue? (y/n): y

Starting to dump database on Source. [ok]

Database dump saved to /home/exampledev/drush-backups/exampledev/

201409231429/exampledev_20140923_1429.sql.gz [success]

Starting to discover temporary files directory on

Destination. [ok]

Copying dump file from Source to Destination. [ok]

Starting to import dump file onto Destination database. [ok]

Command dispatch complete [notice]

The preceding output is a simplified version. The original one is longer because we used the --verbose option that shows how Drush bootstraps each project (local and development), generates a database dump, downloads it, and installs it. It's handy to leave the verbose option set, so if there are any errors or warnings, they can be easily spotted. Now, let's try our site alias to sync files from development into our local environment:

$ drush syncfiles

Initialized Drupal 7.31 root directory at

/home/juampy/projects/example [notice]

You will destroy data from

/home/juampy/projects/example/sites/default/files/

andreplace with data from

exampledev@dev.example.com:

/var/www/exampledev/docroot/sites/default/files/

Do you really want to continue? (y/n): y

receiving incremental file list

...

sent 14.71M bytes received 31 bytes 1.28M bytes/sec

total size is 16.18M speedup is 1.10

Command dispatch complete [notice]

The preceding Drush site alias synced the files directory from the development environment into our local environment. As you can see, these two aliases come in handy for the rest of the team, so these do not need to deal with having to type the full syntax for sql-sync and core-rsync commands.

Blocking the execution of certain commands

Using Drush shell aliases instead of manually typing sql-sync and core-rsync commands is definitely an improvement, but there is still the chance of someone writing the command manually in the wrong way and causing a disaster. We can go one step further in securing these commands and leverage Drush's command API to block certain commands. Drush has a section in its documentation with a few default policy rules. We will use this file as a template to make our own policy file for the example project:

$ drush topic docs-policy > sites/all/drush/policy.drush.inc

After editing the resulting file, we have the following policy rules for our project:

<?php

/**

* @file

* Policy rules for Example project.

*/

/**

* Implements drush_hook_COMMAND_validate().

*

* Prevent overriding Production's database.

*/

function drush_policy_sql_sync_validate($source = NULL, $destination = NULL) {

if ($destination == '@example.prod') {

return drush_set_error('POLICY_DENY_SQL', dt('Oops, you almost copied your database onto Production. Please use drush syncdb instead.'));

}

}

/**

* Implements drush_hook_COMMAND_validate().

*

* Prevent modifying Production's files directory.

*/

function drush_policy_core_rsync_validate($source = NULL, $destination = NULL) {

if (strpos($destination, '@example.prod') === 0) {

return drush_set_error('POLICY_DENY_RSYNC', dt('Oops, you almost copied files onto Production. Please use drush syncfiles instead.'));

}

}

We have implemented a validate hook for sql-sync and core-rsync commands, verifying that the destination site of the command being executed is not production and throwing an error if so. Let's try copying our local database into production and see what happens:

$ drush cache-clear drush

$ drush sql-sync @self @example.dev

You will destroy data in dev.example.com/exampledev and replace with data from example.

Do you really want to continue? (y/n): y

Oops, you almost copied your database onto Production. Please

use drush syncdb instead. [error]

As we added a new command file, we cleared Drush's cache so that it could discover it. Next, we tried to copy our database into the production environment and our policy file aborted it as we expected. Let's try now to sync our files directory into the production environment:

$ drush rsync @self:%files @example.dev:%files

@example.dev:%files

Oops, you almost copied files onto Production. Please use

drush syncfiles instead. [error]

It's the same case here. By using Drush shell aliases and implementing policy rules, you can limit some of the flexibility of Drush site aliases that can damage your project and at the same time provide shorter commands for the team to use.

Ignoring tables on sql-sync

The sql-sync command accepts a list of tables whose data will be skipped and a list of tables whose data and structure will be skipped. This feature speeds up the command considerably, especially on large databases. Here is an example where we manually define the list of tables to ignore when copying development's database into our local environment:

$ drush sql-sync \

--structure-tables-list=cache,history,sessions,watchdog \

@example.dev @self

Now, the list of tables provided will just be created in our local database, but its data won't be downloaded from the development environment. The --structure-tables-list is actually an option of the sql-dump command, which sql-sync calls in order to obtain a database dump from the development environment and then download it. Managing this list of tables can be tedious as the list would be changing frequently during the development stage of a project. In order to simplify this process, we can instead use the --structure-tables-key option and define an array of tables at our Drush configuration file. Here is our sites/all/drush/drushrc.php file with the list of tables to ignore:

<?php

/**

* @file

* Drush configuration for Sample project.

*/

/**

* List of tables whose *data* is skipped by the 'sql-dump' and 'sql-sync'

* commands when the "--structure-tables-key=common" option is provided.

*/

$options['structure-tables']['common'] = array('cache', 'cache_*', 'history', 'search_*', 'sessions', 'watchdog');

// Shell aliases.

$options['shell-aliases']['syncdb'] = '--verbose --yes sql-sync @example.dev @self --create-db';

$options['shell-aliases']['syncfiles'] = '--verbose --yes rsync @example.dev:%files/ @self:%files/';

We have defined a list of structure-tables under the common key. This list supports wildcards, which makes it considerably shorter. Now, here is how we can reference this list when we run sql-sync:

$ drush sql-sync --structure-tables-key=common @example.dev @self

The preceding command will ignore the data of many more tables than the previous one, which was using --structure-tables-list. Drush loads our configuration file at sites/all/drush/drushrc.php and is able to relate the common key provided in the command line with the list of tables to ignore in the development environment. We can even go one step further and move the --structure-tables-key option into our development's site alias, so we do not even have to type this option anymore. Here is our site alias definition after adding the --structure-tables-key option at sites/all/drush/example.aliases.drushrc.php:

<?php

/**

* @file

*

* Site alias definitions for Example project.

*/

// Development environment.

$aliases['dev'] = array(

'root' => '/var/www/exampledev',

'uri' => 'http://dev.example.com',

'remote-host' => 'dev.example.com',

'remote-user' => 'exampledev',

'command-specific' => array (

'sql-dump' => array (

'structure-tables-key' => 'common',

),

),

);

// Production environment.

$aliases['prod'] = array(

'root' => '/var/www/exampleprod/docroot',

'uri' => 'www.example.com',

'remote-host' => 'www.example.com',

'remote-user' => 'exampleprod',

);

We have added an option to the sql-dump command whenever @example.dev is used. As we said before, sql-sync internally calls sql-dump to obtain a database dump from the source site alias; hence, the option is set for sql-dump and not sql-sync. Now, we can use sql-sync, and Drush will silently ignore the list of tables that we defined previously:

$ drush sql-sync @example.dev @self

The preceding code will load the --structure-tables-key option from development's site alias and the list of tables from our Drush configuration file. Our Drush shell alias will behave in the same way so that the rest of the team can keep on using drush syncdb and Drush will take care of ignoring unnecessary tables.

Drush site aliases offer many more options such as --source-command-specific and --target-command-specific, which should offer enough flexibility to fit your team's needs. Take a look at drush topic docs-aliases for further examples that you can consider useful for your project.

Summary

Site aliases open a world of possibilities. They are one of the gems of Drush (and perhaps Drupal as well). The community built a lot of tools that rely on them and you can discover these at Drupal.org.

In this chapter, we covered practical examples with site aliases. We started by defining a site alias for our local Drupal project, and then went on to write a group of site aliases to manage remote environments for a hypothetical Drupal project with a development and production site. Before using site aliases for our remote environments, we covered the basics of setting up SSH in order for Drush to connect to these servers and run commands there.

We also learned that Drush automatically defines a set of special site aliases: @self, @none, plus one for each group of site aliases that we define. The @self alias means Bootstrap the current project, @none means Don't bootstrap the current project, and a group site alias such as @example means Run the command in all the sites defined within the group.

Next, we tested a custom command with a remote site alias and took the chance to improve it, exploring Drush's APIs even further. We showed how running the update path in our local and in a remote site makes little difference to Drush. As a matter of fact, when I finished writing this chapter, I published the command in Drupal.org. You can find this at https://www.drupal.org/project/updatepath or by running drush dl updatepath.

We finished the chapter configuring two commands that use site aliases as arguments: core-rsync and sql-sync. The tips that we learned will help us to make these two commands easier and safer to use within a team of developers. This setup will be the foundation of our next and last chapter, where we will leverage all of Drush's features to set up a development workflow for a team.