Automatic Drush alias creation and synchronization between remote and local systems

Public

Blog post / video stepping through all of this -- http://drupal.psu.edu/content/automatic-drush-multisite-alias-creation-a...

First, create the alias file. This code will help facilitate automatically hunting down aliases / sites listed within a multisite and build the aliases array automatically. This is based on two other snippets on this site:
http://dropbucket.org/node/84 -- multi-site spider sites directory for automatic aliasing
http://dropbucket.org/node/748 -- sub-group auto creation

Throughout, replace GROUPNAME with the group name for these aliases whether they be project, server based or whatever you want.

This code goes in ~/.drush/GROUPNAME.aliases.drushrc.php

Get raw version
php
  1. <?php
  2. $aliases = array();
  3. // script assumes $stack.$address.$site relationship for address
  4. $address = 'ADDRESS.COM';
  5. $root = '/var/www/';
  6. $group = 'FILENAMEGROUP';
  7. $stacks = array(
  8. 'stack1',
  9. 'stack2',
  10. 'stack3',
  11. );
  12. // loop through known stacks
  13. foreach ($stacks as $stack) {
  14. // build root alias for the stack
  15. $aliases[$stack] = array(
  16. 'root' => $root . $stack,
  17. 'uri' => "$stack.$address",
  18. );
  19. // assumes structure is root/stack/sites/stack/group. typical setup will be root/stack/sites
  20. $site = new DirectoryIterator("$root$stack/sites/$stack/$group");
  21. while ($site->valid()) {
  22. // Look for directories containing a 'settings.php' file
  23. if ($site->isDir() && !$site->isDot() && !$site->isLink()) {
  24. if (file_exists($site->getPathname() . '/settings.php')) {
  25. // Add site alias
  26. $basename = $site->getBasename();
  27. $aliases["$stack.$basename"] = array(
  28. 'parent' => "@$stack",
  29. 'uri' => "$stack.$address.$basename",
  30. );
  31. }
  32. }
  33. $site->next();
  34. }
  35. }
  36.  
  37. /**
  38.  * Magic to auto produce additional alias sub-groups
  39.  */
  40. $modifier = '-all';
  41. foreach ($aliases as $key => $values) {
  42. $parts = explode('.', $key);
  43. if (count($parts) >= 2) {
  44. // something that's in a subgroup
  45. array_push($aliases[$parts[0] . $modifier]['site-list'], '@' . $key);
  46. }
  47. else {
  48. // something is group-able
  49. $aliases[$key . $modifier] = array('site-list' => array('@' . $key));
  50. }
  51. }

Next, create this file on the remote system:

~/.drush/GROUPNAME.remoteconnect.php

This just runs the previous file but then serializes it and prints it out, this is important for what comes next.

Get raw version
php
  1. <?php
  2. include('GROUPNAME.aliases.drushrc.php');
  3. print serialize($aliases);

Now lets move to our laptop and create some files there. The first file is a class called ExecuteRemote which also does all the work in this operation.

Move to your operating systems's {home directory}/.drush folder and create ExecuteRemote.php

This allows for remote execution of commands over SSH. This assumes that you have SSH pub/priv keys established as there is no input for a password (by design).

Get raw version
php
  1. <?php
  2. /**
  3.  * Class for remote execution over SSH
  4.  */
  5. class ExecuteRemote {
  6. private static $host;
  7. private static $options;
  8. private static $username;
  9. private static $error;
  10. private static $output;
  11. // password is not allowed, use SSH key gens for secure bind
  12. public static function setup($host, $options, $username) {
  13. self::$host = $host;
  14. self::$options = $options;
  15. self::$username = $username;
  16. }
  17.  
  18. public static function executeScriptSSH($script) {
  19. // Setup connection string
  20. $connectionString = self::$options . ' ' . self::$username. '@' . self::$host;
  21. // Execute script
  22. $cmd = "ssh $connectionString $script 2>&1";
  23. self::$output['command'] = $cmd;
  24. exec($cmd, self::$output, self::$error);
  25. if (self::$error) {
  26. throw new Exception ("\nError sshing: ".print_r(self::$output, true));
  27. }
  28. return self::$output;
  29. }
  30. }
  31.  
  32. function _build_aliases($group, $server) {
  33. // Create an array to hold the cache, this prevents unneeded ssh connections
  34. static $aliases = array();
  35.  
  36. if (!empty($aliases)) {
  37. return $aliases;
  38. }
  39. // build remote connection object
  40. $connection = new ExecuteRemote();
  41. // pass same settings we're append for connection
  42. $connection->setup($server['remote-host'], $server['ssh-options'], $server['remote-user']);
  43. // return output of the remote execution of our script, serialized
  44. // change home directory location if not running linux
  45. $return = $connection->executeScriptSSH("php /home/{$server['remote-user']}/.drush/$group.remoteconnect.php");
  46. // unserialize directly into our expected aliases array
  47. $aliases = unserialize($return[0]);
  48. // append remote connection settings
  49. foreach ($aliases as &$alias) {
  50. if (isset($alias['root'])) {
  51. $alias += $server;
  52. }
  53. }
  54. return $aliases;
  55. }

Lastly, we create a matching GROUPNAME.aliases.drushrc.php in our local .drush directory.

By defining the group and server connection settings at the top of this file, we will be able to have this file automatically connect to the remote copy, which automatically sniffs out multisite site aliases, then serialize the info and unserialize it locally. From here we iterate over it and append the drush remote connection settings required to talk back to the system that just told us about its aliases!

This allows for an identical developer experience when running a command against the server whether locally or remotely!

for example, no matter where this command is run, it will always clear the cache of everything that's in stack2:
drush @stack2-all cc all
It does this, dynamically evaluating what actually exists in stack2! This eliminates the need for managing alias files locally as well as remotely when working on the server should you want to execute a drush command there!

Get raw version
php
  1. <?php
  2. /**
  3.  * Server config for everything
  4.  */
  5. $group = 'GROUPNAME';
  6. $server = array(
  7. 'remote-user' => 'USERNAME',
  8. 'ssh-options' => '-p 22', // list options as a string here, port being a common one
  9. 'remote-host' => 'ADDRESS',
  10. );
  11. // execute a remote command via SSH
  12. include_once('ExecuteRemote.php');
  13. $aliases = _build_aliases($group, $server);