I am preparing to release this blog as a Drupal distribution in the next week or so, "when it's ready" as is commonly said in the Drupal community. One of the pieces I was working on this last weekend was a demo-mode module that uses Drush to generate content. My requirements were simple enough:

  • Run Drush or any bash command in a module
  • Post notices to watchdog
  • Report any errors

The first piece was creating a helper function for executing a command line command. I'll use this function to pass executable commands ($cmd) that I want run.

/**
 * Helper function for executing a command line command
 */
function _rjs_blog_demo_mode_exec($cmd, $msg = '', $die_on_error = TRUE) {
  exec($cmd . ' 2>&1', $output, $status);
 
  _rjs_blog_demo_mode_debug($cmd);
  _rjs_blog_demo_mode_debug('Status: ' . $status . ' | Output: ');
  _rjs_blog_demo_mode_debug($output);
 
  _rjs_blog_demo_mode_error_check($status, $msg, $die_on_error);
 
  return $status == 0;
}

The function above is doing a couple things; it executes the command ($cmd), sends $status and $output to watchdog, and then runs an error check and outputs messagas to the command prompt if you happen to be enabling demo mode from the command line. Here are the 2 functions referenced above for watchdog and error checking:

/**
 * Print debug to watchdog.
 *
 * @param $var
 *   The variable or message to print.
 */
function _rjs_blog_demo_mode_debug($var) {
  if(is_array($var)) {
    watchdog('demo mode', print_r($var, true), array(), WATCHDOG_NOTICE);
  } else {
    watchdog('demo mode', $var, array(), WATCHDOG_NOTICE);
  }
}
 
/**
 * Helper function for determining if a command line error occured. Exit if error occurs.
 *
 * @param $status
 *   The exit status of a command line command. If this is 0 then no error has occured.
 *
 * @param $message
 *   The error mesage to display.
 */
function _rjs_blog_demo_mode_error_check($status, $message = '', $die_on_error = TRUE) {
  if ($status != 0) {
    $message = empty($message) ? t('An error occured.') : $message;
    if ($die_on_error) {
      $result = array('status' => FALSE, 'data' => $message);
      die(json_encode($result));
    }
  }
}

I'm finally ready to run a command, which I'll do in hook_install().

/**
 * @file
 * Implements hook_install().
 */
function rjs_blog_demo_mode_install() {
  // Get the path to drush
  $drush = trim(shell_exec('which drush'));
  // Generate terms
  $cmd = $drush . ' generate-terms tags 10 --kill=true';
  _rjs_blog_demo_mode_exec($cmd);
  // generate blog content
  $cmd = $drush . ' generate-content 50 20 --types=article --kill=true --since="last year"';
  _rjs_blog_demo_mode_exec($cmd);
}

I should mention you'll need to use the patch from this issue to use the --since flag when generating content via drush.

Comments

Hey, good luck releasing this to the world. In shared hosting plans usually shell_exec is disabled; which makes tasks like this hard.

you may want to create a hook_requirements and assure shell_exec doesnt appear in the output of ini_get("disable_functions").

Given the way you are calling everything this *should* be fine from a security perspective because there is no user input in the $cmd, but...I suggest splitting the function to use commands and args and then using 2 php functions to filter those to gain security: http://us2.php.net/escapeshellcmd and http://us3.php.net/escapeshellarg

Once this function exists it becomes tempting to abuse it and let user input, so it's better to add the filtering at the beginning than later.

Add new comment

The content of this field is kept private and will not be shown publicly.