nickwshaw
4/29/2013 - 10:53 AM

dennis.drush.php

<?php

/**
 * @file
 *   drush command.
 */

/**
 * Implementation of hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * See `drush topic docs-commands` for a list of recognized keys.
 *
 * @return
 *   An associative array describing your command(s).
 */
function dennis_drush_command() {
  $items = array();

  // The 'dennis-syncl-db' command
  $items['dennis-sync-db'] = array(
    'description' => "Get a db",
    'arguments' => array(
      'site' => 'The site to download',
    ),
    'options' => array(
      'src' => array(
        'description' => 'The src alias to sync from.',
        'example-value' => '@kontraband_migration',
      ),
      'no-cache' => 'Do not cache the sql-dump file.',
      'updb' => 'Run update db after import.',
    ),
    'examples' => array(
      'drush d-db kontraband' => 'Downloads kontraband.',
    ),
    'aliases' => array('d-db'),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
  );

  // The 'dennis-make-site' command
  $items['dennis-make-site'] = array(
    'description' => "Check out a repo",
    'arguments' => array(
      'site' => 'The repo',
    ),
    'examples' => array(
      'drush d-make kontraband' => 'Makes the kontraband repo.',
    ),
    'aliases' => array('d-make'),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
  );

  $items['dennis-pre-sync-cleanup'] = array(
    'description' => "Calls modules implementations of hook_dennis_sync_pre_cleanup",
  );
  $items['dennis-post-sync-cleanup'] = array(
    'description' => "Calls modules implementations of hook_dennis_sync_post_cleanup",
  );

  return $items;
}



/**
 * Implementation of hook_drush_help().
 *
 * This function is called whenever a drush user calls
 * 'drush help <name-of-your-command>'. This hook is optional. If a command
 * does not implement this hook, the command's description is used instead.
 *
 * This hook is also used to look up help metadata, such as help
 * category title and summary.  See the comments below for a description.
 *
 * @param
 *   A string with the help section (prepend with 'drush:')
 *
 * @return
 *   A string with the help text for your command.
 */
function dennis_drush_help($section) {
  switch ($section) {
    case 'drush:dennis-sync-db':
      return dt("This command will download the db of the requested site.");
    case 'drush:dennis-make-site':
      return dt("This command will check out & make the repo.");

    // The 'title' meta item is used to name a group of
    // commands in `drush help`.  If a title is not defined,
    // the default is "All commands in ___", with the
    // specific name of the commandfile (e.g. sandwich).
    // Command files with less than four commands will
    // be placed in the "Other commands" section, _unless_
    // they define a title.  It is therefore preferable
    // to not define a title unless the file defines a lot
    // of commands.
    case 'meta:dennis:title':
      return dt("Dennis commands");
    // The 'summary' meta item is displayed in `drush help --filter`,
    // and is used to give a general idea what the commands in this
    // command file do, and what they have in common.
    case 'meta:dennis:summary':
      return dt("Automates your dennis workflows.");
  }
}

/**
 * This is where the action takes place.
 *
 * The function name should be same as command name but with dashes turned to
 * underscores and 'drush_commandfile_' prepended, where 'commandfile' is
 * taken from the file 'commandfile.drush.inc', which in this case is 'sandwich'.
 * Note also that a simplification step is also done in instances where
 * the commandfile name is the same as the beginning of the command name,
 * "drush_example_example_foo" is simplified to just "drush_example_foo".
 * To also implement a hook that is called before your command, implement
 * "drush_hook_pre_example_foo".  For a list of all available hooks for a
 * given command, run drush in --debug mode.
 *
 * If for some reason you do not want your hook function to be named
 * after your command, you may define a 'callback' item in your command
 * object that specifies the exact name of the function that should be
 * called.  However, the specified callback function must still begin
 * with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute")
 * if you want that all hook functions are still called (e.g.
 * drush_example_pre_foo_execute, and so on).
 *
 * In this function, all of Drupal's API is (usually) available, including
 * any functions you have added in your own modules/themes.
 *
 * @see drush_invoke()
 * @see drush.api.php
 */
function drush_dennis_sync_db($site, $src_alias = NULL) {
  $args = drush_get_arguments();
  $site = $args[1];


  //TODO: What happens if more than one person is dumping the db at the same time?
  // - does the dump get written to by everyone and get buggered?

  $src_alias = drush_get_option('src', '@' . $site . '_live');

  $arguments = array(
    $src_alias,
    '@' . $site,
  );

//var_dump(drush_get_merged_options(), $arguments); exit;

  // NB: --structure-tables-key=common requires drushrc.php (taken from example.drush.php)
  $options = array(
    '-y',
    '--no-ordered-dump',
    '--structure-tables-key=common',
    '--create-db',
    '--disable=cdn,memcache_admin',
    '--vagrant',
  );

  if (drush_get_option('no-cache', FALSE)) {
    $options[] = '--no-cache';
  }

  if (drush_get_option('updb', FALSE)) {
    $options[] = '--updb';
  }

  drush_invoke_process('@' . $site, 'sql-sync', $arguments, $options);

}

/**
 * Implementation of drush_hook_COMMAND_validate().
 *
 * The validate command should exit with
 * `return drush_set_error(...)` to stop execution of
 * the command.  In practice, calling drush_set_error
 * OR returning FALSE is sufficient.  See drush.api.php
 * for more details.
 */
function drush_dennis_sync_db_validate() {
  $args = drush_get_arguments();
  if (empty($args[1])) {
    return drush_set_error('DENNIS_DRUSH', dt('What site?'));
  }
}


/**
 * Implement hook post sql sync.
 *
 */
function drush_dennis_post_sql_sync($source = NULL, $destination = NULL) {

  drush_invoke_process($destination, 'registry-rebuild');

  //var_dump(drush_get_merged_options());

  $site = str_replace('@', '', $destination);

  // See if a site want to do post sync stuff before the default calls
  drush_invoke_process($destination, 'dennis-pre-sync-cleanup');

  $modules_to_enable = drush_get_option_list('enable');
  if (!empty($modules_to_enable)) {
    drush_log(dt("Enable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_enable))), 'ok');
    drush_invoke_process($destination, 'pm-enable', $modules_to_enable, array('yes' => TRUE));
  }
  $modules_to_disable = drush_get_option_list('disable');
  if (!empty($modules_to_disable)) {
    drush_log(dt("Disable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_disable))), 'ok');
    drush_invoke_process($destination, 'pm-disable', $modules_to_disable, array('yes' => TRUE));
  }

  drush_invoke_process($destination, 'solr-set-env-url', array('http://localhost:8080/solr3/solr3'));

  // Set some variables
  drush_invoke_process($destination, 'vset', array('varnish_control_terminal', '127.0.0.1:6082'));
  drush_invoke_process($destination, 'vdel', array('googleanalytics-account'));
  drush_invoke_process($destination, 'vset', array('file_public_path', 'sites/' . $site . '/files'));

  drush_invoke_process($destination, 'cc', array('all'));

  if (drush_get_option('updb')) {
    // run update db
    drush_print('---- Running updatedb ----', 2);
    drush_invoke_process($destination, 'updatedb');
  }

  // See if a site want to do post sync stuff after the default calls
  drush_invoke_process($destination, 'dennis-post-sync-cleanup');

  drush_print();
  drush_print("#");
  drush_print('# Site should now be viewable at: http://' . $site . '.vm.didev.co.uk');
  drush_print("#");
  drush_print();
}

/**
 *
 */
function drush_dennis_make_site($site) {
  $args = drush_get_arguments();
  $site = $args[1];
   // TODO - nonkludgify
   // currently a straightish port of the repo checkout part of downloadDb.sh
  $exec = 'hg clone http://fogbugz.dennisinteractive.co.uk/kiln/Repo/Drupal-Sites/' . $site . '/0-1 /home/vagrant/repos/' . $site;

  $exec_result = drush_op_system($exec);
  $result = ($exec_result == 0);

  if (!$result) {
    drush_set_error('DRUSH_DENNIS_FAILED', dt("Could not clone repo for !site", array('!site' => $site)));
  }

  if (file_exists('/home/vagrant/repos/' . $site . '/site.make')) {
    $arguments = array(
      '/home/vagrant/repos/' . $site . '/site.make', // makefile
    );

    $options = array(
      '-y',
      '--working-copy',
      '--no-gitinfofile',
      '--no-core',
      '--contrib-destination=.',
    );

    drush_invoke_process('@' . $site, 'make', $arguments, $options);


  }

  // setting.php etc
  if (!file_exists('/home/vagrant/repos/' . $site . '/settings.php')) {

  }

}

/**
 * Implementation of drush_hook_COMMAND_validate().
 *
 * The validate command should exit with
 * `return drush_set_error(...)` to stop execution of
 * the command.  In practice, calling drush_set_error
 * OR returning FALSE is sufficient.  See drush.api.php
 * for more details.
 */
function drush_dennis_make_site_validate() {
  $args = drush_get_arguments();
  if (empty($args[1])) {
    return drush_set_error('DENNIS_DRUSH', dt('What site?'));
  }
}

function drush_dennis_pre_sync_cleanup() {
  module_invoke_all('dennis_sync_pre_cleanup');
}

function drush_dennis_post_sync_cleanup() {
  module_invoke_all('dennis_post_sync_cleanup');
}

/**
 * Give a progress report of the sql import
 * @param string $file
 * @param string $import_command
 */
function drush_dennis_import_progress($file, $import_command) {

  $size = filesize($file);
  $bytes = 0;
  $percent = 0;

  $handle = popen($import_command, "w");
  if (!$handle) {
    echo "FAILED to open $import_command\n";
    exit;
  }
  $sql_fp = fopen($file, 'r');
  if (!$sql_fp) {
    echo "FAILED to open $file\n";
    exit;
  }

  echo "\nImporting: ";
  while (!feof($sql_fp)) {
    $contents = fread($sql_fp, 8192);
    $bytes += 8192;
    $current_precent = floor($bytes / $size * 100);

    if ($current_precent > $percent) {
      $percent = $current_precent;
      echo $percent . "% ";
    }

    fwrite($handle, $contents);
  }
  echo "\n";
  pclose($handle);
  fclose($sql_fp);
}

/**
 * Replacement for drush_op_system() used in sql_sync so that we can get a progress report from sql_sync
 * @param string $import_exec
 */
function drush_dennis_sql_sync_op_system($import_exec) {

  // To get the progress we need to pipe the sql into mysql via php php rather than a system call

  // get the $pre_import_commands that may be getting piped into the mysql command
  $piped_parts = explode('|', $import_exec);
  if (isset($piped_parts[1])) {
    $pre_import_commands = $piped_parts[0];
    $cmd = trim($piped_parts[1]);
  }
  else {
    $cmd = trim($piped_parts[0]);
  }

  // sql_sync uses '<' to import the sql file into mysql
  $cmd_parts = explode('<', $cmd);
  if (!isset($cmd_parts[1])) {
    // not what's expected so use the original function
    drush_op_system($import_exec);
    return;
  }
  $file = trim($cmd_parts[1]);
  $import_command = trim($cmd_parts[0]);

  if (!empty($pre_import_commands)) {
    // the command will have two db commands separated by a ';'
    $cmd_parts = explode(';', $import_command);
    if (!isset($cmd_parts[1])) {
      // not what's expected so use the original function
      drush_op_system($import_exec);
      return;
    }

    $pre_command = trim($cmd_parts[0]);
    $import_command = trim($cmd_parts[1]);

    $import_exec = $pre_import_commands . ' | ' . $pre_command;
    // do the pre commands
    drush_op_system($import_exec);
  }

  // now do the import command with progress
  drush_dennis_import_progress($file, $import_command);

}