I'm just going to paste the code comments of what I'm doing right now.

This code is a port from Aegir, and is one of the main tenets behind the system, so it's been very well tested.

/**
 * @file Drush backend API
 *
 * When a drush command is called with the --backend option,
 * it will buffer all output, and instead return a serialized
 * string containing all relevant information on the command that
 * was just executed.
 *
 * Through this mechanism, it is possible for Drush commands to
 * invoke each other.
 *
 * There are many cases where a command might wish to call another
 * command in it's own process, to allow the calling command to
 * intercept and act on any errors that may occur in the script that
 * was called.
 *
 * A simple example is if there exists an 'update' command for running
 * update.php on a specific site. The original command might download
 * a newer version of a module for installation on a site, and then 
 * run the update script in a separate process, so that in the case
 * of an error running a hook_update_n function, the module can revert
 * to a previously made database backup, and the previously installed code.
 *
 * By calling the script in a separate process, the calling script is insulated
 * from any error that occurs in the called script, to the level that if a
 * php code error occurs (ie: misformed file, missing parenthesis, whatever),
 * it is still able to reliably handle any problems that occur.
 *
 * This is nearly a RESTful API. @see http://en.battlestarwiki.org/wiki/REST
 *
 * Instead of :
 *   http://[server]/[apipath]/[command]?[arg1]=[value1],[arg2]=[value2]
 *
 * It will call :
 *  [apipath] [command] --[arg1]=[value1] --[arg2]=[value2] --backend
 *
 * [apipath] in this case will be the path to the drush.php file.
 * [command] is the command you would call, for instance 'status'.
 *
 * GET parameters will be passed as options to the script.
 * POST parameters will be passed to the script as a serialized associative array over STDIN.
 *
 * Because of this standard interface, Drush commands can also be executed on
 * external servers through SSH pipes, simply by appending, 'ssh username@server.com'
 * in front of the command.
 *
 * If the key-based ssh authentication has been set up between the servers, this will just
 * work, otherwise the user will be asked to enter a password.
 */
/**
 * Invoke a drush backend command.
 *
 * @param command
 *    A defined drush command such as 'cron', 'status' or any of the other available commands such as 'drush pm'.
 * @param data
 *    Optional. An array containing options to pass to the call. Common options would be 'uri' if you want to call a command
 *    on a different site, or 'root', if you want to call a command using a different Drupal installation.
 *    Array items with a numeric key are treated as optional arguments to the command.
 * @param method
 *    Optional. Defaults to 'GET'.
 *    If this parameter is set to 'POST', the $data array will be passed to the script being called as a serialized string over
 *    the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
 *    For any other value, the $data array will be collapsed down into a set of command line options to the script.
 * @param integrate
 *    Optional. Defaults to TRUE.
 *    If TRUE, any error statuses or log messages will be integrated into the current process. This might not be what you want,
 *    if you are writing a command that operates on multiple sites.
 * @param drush_path
 *    Optional. Defaults to the current drush.php file. You may also specify a different drush.php script.
 *    You will most likely need to set this when calling drush on a remote server.
 * @param hostname
 *    Optional. A remote host to execute the drush command on.
 * @param username
 *    Optional. Defaults to the current user. If you specify this, you can choose which module to send.
 *
 * @return
 *   If the command could not be completed successfully, FALSE.
 *   If the command was completed, this will return an associative array containing the data from drush_backend_output().
 */
function drush_backend_invoke($command, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL) {
//snip
}

So, if you want to run cron on the site you just updated, it is literally just :

  drush_backend_invoke('cron');

There are many places this is useful, but already for Aegir, I have a command that allows me to do the following :
'drush.php provision migrate mysite.com /some/new/drupal/directory'

That command hits the 'update' for that platform too. So essentially it allows me to do very clean, very error tolerant, very recoverable minor and major version upgrades.

I am using it to manage multiple servers from the web interface, and I'll be working on things like migrating websites between servers, etc.

It is also the only clean way to have commands operate on multiple sites (ie: if you upgrade a module in sites/all, run update on all the sites). The other option is to do all kinds of trippy database switches.

The protection it affords you is very important. It's impossible to recover from many errors that occur during the install/update process. Especially if you are modifying the files that are loaded into memory. This is the only protection I have found against namespace clashes (ie: all the sites are using views from sites/all, but one specific site has it's on copy in sites/sigh.com.). Without
this, the entire process will fail HORRIBLY, without any way to get back to a working site.

This system lives entirely in it's own include file. The only modifications it makes to core drush files is adding the --backend flag, and buffering the output to be sent back in a structured way when the backend flag is set.

I chose serialized strings as the transfer mechanism, because that required the least amount of code. Theoretically this could also be a --xmlrpc, but that feels like overkill. PHP has no built in support for JSON etc, and I only need to communicate between php scripts at the moment. It's very possible to replace or extend it with some other serialization format in the future, if you ever need to write a
ruby daemon or something that has to call drush.

The multi-server stuff is incredibly simple because of how this is all implemented. It's literally 4 lines of additional code.

I'll publish/commit this once I have the POST method completely worked out.

Comments

adrian’s picture

Ruby implementation of php serialize : http://www.aagh.net/projects/ruby-php-serialize
Python implementation of php serialize : http://hurring.com/scott/code/python/serialize/

adrian’s picture

I was mistaken, php 5.2 supports json out the box.

I think in interests of compatibility, we should probably switch to using json_*.

That would make the minimum PHP version for drush 5.2

is that a problem ?

moshe weitzman’s picture

php 5.2 as min for drush is perfectly reasonable.

adrian’s picture

Status: Active » Fixed

Committed in http://drupal.org/cvs?commit=175698

I have taken ownership of the backend.inc file, so any issues relating to this api I will handle.

This is used primarily in Aegir, so I already have a huge library of code that depends on this.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.