Another one of the features in http://groups.drupal.org/project/provision, is an extensible api that hooks into error handling and logging.

Essentially, when defining you command in drush_commands, you specify :

  $cmd['my command'] = array(
     'callback' => 'drush_invoke',
     'callback arguments' => array('mycommand'),
  );

By doing this, you automatically have access to the following hooks:

$hook_drush_mycommand_validate() : which allows you to check for the valid input.
$hook_drush_pre_mycommand() : Do initial setup for your command.
$hook_drush_command() : Do the actual work required.
$hook_drush_post_command() : Finalize and clean up after yourself.

Now the beauty of this is, is that not just your module, but any module have access to these hooks.
We use it in provision to separate concerns, so when provisioning a new site, the pre_command of the
mysql sets up the database, for the rest of the process to happen. We could add additional modules
(such as dns hosting), which can interact with these commands.

Additionally, if at any point a drush_set_error is called, it will automatically stop recurring through the hooks,
and run through the options it tried in reverse, but adding rollback to the command, such as :

$hook_drush_pre_mycommand_rollback()
$hook_drush_command_rollback()
$hook_drush_post_command_rollback()

Allowing you to back out problems you have created for yourself.

What is also useful in this, is that it allows the commands that use this api to call each other.
Say for instance you are doing a site upgrade, and you need to back up before you run the update,
you can just do 'drush_invoke("backup")'.

The mechanism that we communicate between the hooks, is we pass a context reference through all the functions
we call, so when the mysql_pre_install hook has created the database, it sets the mysql_user and mysql_password
in the context array, and passes it to the other modules that need to create the configuration files and the like.

A couple of notes: In provision we have 2 extra hooks, for all modules (which would need to be integrated into drush
more directly), namely 'provision_init' and 'provision_finalize'. Because we don't have the drupal init hooks available,
we tend to use these, and they work pretty well for us.

In provision, we doing just look for *.drush.inc files, but we auto-load these additional crosscuts dynamically from all
modules we find them.

An example of such a file is here : http://drupalbin.com/4343

Simply by creating the filename with the name : disable.provision.inc, in the provision_drupal.module directory, it will get loaded automatically, making it incredibly easy to extend any command on the system.

This code is a bit more complex to un-provision-i-fy, but these are the basic requirements for this patch to land, it uses all of these,
so it's impossible to create a modular patch without them :

- #349908: Drush logging API.
- #212323: Drush error handling API
- #349996: callback arguments support in drush_command

CommentFileSizeAuthor
#9 drush_invoke.diff13.91 KBadrian
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

adrian’s picture

adrian’s picture

Priority: Normal » Critical

All the requirements for this is now in, but i just need to decide how to go ahead with the auto-included files.

In provision currently, provision_invoke looks for [command].provision.inc files to include for the hooks.
It uses the commandfile list for a list of directories to source these files from.

I've found that even though I have a lot more files, these files are much easier to manage, and have a very rigid structure (ie: $command_validate, pre_$command, pre_$command_rollback, $command, $command_rollback, post_$command, post_$command_rollback), which makes them very simple to document and maintain.

Any drush commandfile can provide additional hooks solely by creating a file and adding a function to it.

I'm going to be looking for ways to make this nicer in drush itself. For instance, instead of requiring the use of the 'callback arguments' field, I am going to try and use introspection to match it to the command sent.

Personally, I like reversing the order of the words in the command in the auto-loaded filename (ie: install.provision.inc and not provision.install.inc.). I have too many files starting with 'provision' =P

I am thinking i might actually resurrect the *.command.inc filename.

adrian’s picture

I'm currently busy porting http://drupal.org/node/380688 , which is an api that call other commands externally.

I need to tackle this to be able to do the provision_invoke port properly.

A major part of how provision_command works currently, is it bubbles a $data reference through all the calls, to allow the various functions to pass information to each other.

At the end of the command, it does a provision_output($data), which returns the structured data.

To be able to allow all commands to be scriptable, I need to change how that data is being passed along. Because the backend port works by publishing this information in the drush_shutdown function (which is now always called) and the shutdown command can't have the data parameter passed to it, I need to provide an api for setting and getting this content.

This also provides additional API safety, as we've had bugs where someone initialized the $data array in their hook, which caused the intra-api communication to bug out.

The output function is the last provision specific function called in provision_invoke, so once the replacement for that is done, I will be able to simply rename the function to drush_invoke and drush_command, and it will just work.

adrian’s picture

I have this crazy plan to use introspection to make implementing commands with this API even simpler.

The default would be this callback, and you could use the api to add a new command simply by doing (in your own *.drush.inc):

function example_drush_command() {
   $items['apply patch'] = array(
      'description' => dt('Apply a patch to the drupal directory'),
      'arguments' => array(
        'path/to/patch' => 'One or more file paths. Paths may be absolute or relative to the current working dir.',
      ),
   );
   return $items;
}

Then you could just write (either in a conditionally included file, or the *drush.inc) :

  function example_apply_patch_validate($file) {
     // check if the argument is a valid file, or whatever. If you want execution to stop just do a 
     drush_set_error('INVALID_FILENAME', dt('Could not find file @file', array('@file' => $file)));
  }

  function example_pre_apply_patch($file) {
     // do whatever setup you need to do here, such as extracting the file to a temporary location, or making a backup of the database
  }

  function example_apply_patch($file) {
     // Apply the patch, setting an error if it failed.
  }

  function example_apply_patch_rollback($file) {
     // patch only partialy applied, revert the bits that were succesful.
  }

  function example_post_apply_patch($file) {
    // run simpletests, doing a drush_set_error if it fails.
    drush_invoke('simpletest');
  }

  function example_post_apply_patch_rollback($file) {
    // Simpletests failed, run the patch with -R to revert to previous code.
  }

Any command that uses this API could then be re-used in the same php process as drush_invoke('apply patch'), or as a separate process with drush_backend_invoke('apply patch').

You could just add a custom.drush.inc into your ~/.drush directory, that could access any of those hooks. Say for instance a command that emails a dev list (or does an xmlrpc call) if the patch couldn't be applied or the simpletests failed.

moshe weitzman’s picture

Issue tags: +2.0

Marking this as 2.0 though Adrian should feel fre to remove it if the code is not likely to be ready within 2 weeks. There is always 3.0

moshe weitzman’s picture

Can we get a patch here?

adrian’s picture

I really want this in 2.0.

getting close to a patch.

anarcat’s picture

Component: Code » Interoperability
adrian’s picture

Status: Active » Needs review
FileSize
13.91 KB

Here's the patch. It also removes some code re: commandfiles that weren't necessary anymore.

This also ports all the core commands to the invoke api.

moshe weitzman’s picture

Status: Needs review » Fixed

Code is strong. Commands still work so I committed this. Thanks Adrian.

Status: Fixed » Closed (fixed)
Issue tags: -2.0

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