You are here

task.hosting.inc in Hosting 7.4

Same filename and directory in other branches
  1. 5 task.hosting.inc
  2. 6.2 task.hosting.inc
  3. 7.3 task.hosting.inc

Drush include for the Hosting module's hosting task command.

File

task.hosting.inc
View source
<?php

/**
 * @file
 * Drush include for the Hosting module's hosting task command.
 */

/**
 * Run a command, sending output to drush logs in real time.
 *
 * The Symfony\Component\Process\Process Object is used to run this command.
 * After implementing provision_process(), you can get the Process result object
 * via drush context:
 *
 *   $process = drush_get_context('provision_process_result');
 *   print $process->getExitCode();
 *
 * @param string|array $command_input
 *   The command to run
 * @param null $cwd
 *   The directory to run the command in.
 * @param string $label
 *   A string to display above the command block in the front-end.
 * @param array $env
 *   A list of environment variables to set for the process.
 * @param bool $log_output
 *   Whether or not to send output to drush_log in real time.
 * @param null $error_message
 *   The error message to show after a failure. Defaults to NULL because the UI turning red and the error output is usually enough.
 * @param bool $throw_drush_error
 *   Whether or not to throw a drush error if the process fails. Defaults to TRUE.
 *
 * @return string|void
 *   The output or error output of the command.
 */
function hosting_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = NULL, $throw_drush_error = TRUE, $log_type = 'p_info') {
  if (empty($command)) {
    return;
  }

  // @TODO: new Process() below isn't accepting an array.
  if (is_array($command)) {
    $command = implode(' ', $command);
  }

  // Merge in env vars, inheriting the CLI's
  if (is_array($env)) {
    $env = array_merge($_SERVER, $env);
  }
  else {
    $env = $_SERVER;
  }

  // Make sure colors always come through
  $env['TERM'] = 'xterm';
  $process = new \Symfony\Component\Process\Process(escapeshellcmd($command), $cwd, $env);
  $process
    ->setTimeout(NULL);
  if ($log_output) {
    drush_log("[{$label}] {$command}", 'p_command');
    $exit_code = $process
      ->run(function ($type, $buffer) use ($log_type) {
      drush_log($buffer, $log_type);
    });
  }
  else {
    $exit_code = $process
      ->run();
  }

  // Save the Provision Process object to Drush Context so that implementors can access the full object.
  drush_set_context('provision_process_result', $process);
  drush_log("Provision process command exited with {$exit_code}", 'debug');

  // check exit code
  if ($exit_code === 0) {
    if ($log_output) {
      drush_log('', 'p_ok');
    }
    return $process
      ->getOutput();
  }
  else {
    if ($log_output) {
      drush_log('', 'p_error');
    }
    if ($throw_drush_error) {
      drush_set_error('PROVISION_PROCESS_ERROR', !empty($error_message) ? $error_message : $process
        ->getErrorOutput());
    }
    return $process
      ->getErrorOutput();
  }
}

/**
 * Log a message to the current task's node if possible, the screen otherwise.
 */
function _hosting_task_log($entry) {
  $task = drush_get_context('HOSTING_TASK');
  if ($task->vid) {
    hosting_task_log($task->vid, $entry['type'], $entry['message'], $entry['error'], $entry['timestamp']);
  }
  else {
    _hosting_task_log_print($entry);
  }
  if (drush_get_option('debug', FALSE)) {
    _hosting_task_log_print($entry);
  }
}

/**
 * Switch between Drush 8's OO logging and the older private function.
 */
function _hosting_task_log_print($entry) {
  if (function_exists('_drush_print_log')) {

    // Remove after dropping Drush 6 comaptibility.
    return _drush_print_log($entry);
  }
  else {
    $logger = new Drush\Log\Logger();
    return $logger
      ->log($entry['type'], $entry['message'], $entry);
  }
}

/**
 * Validate hook for the hosting-task Drush command.
 *
 * We do some magic in this command to allow the user to run either a specifc
 * task by specifying a node id or chosen task type by specifying the type of
 * task, e.g. 'verify' or 'migrate'.
 *
 * @see drush_hosting_task()
 */
function drush_hosting_task_validate($id = NULL, $type = NULL) {
  if (empty($id)) {
    return drush_set_error(DRUSH_APPLICATION_ERROR, dt('Missing argument: Task NID or Type. Use --help option for more information.'));
  }
  drush_set_option('user', 1);
  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
  if (is_numeric($id)) {
    $task = node_load($id);
    drush_log(dt("Task node found: !url", array(
      '!url' => url("node/{$task->nid}", array(
        'absolute' => true,
      )),
    )), 'notice');
  }
  elseif (is_string($id) && isset($type)) {
    $ref = hosting_context_load($id);
    if ($ref->nid) {

      // Get additional arguments to the drush command and convert them to "task_args" as expected by the task node.
      $task_args = array();

      // Parse task_arguments passed to drush in the format "name=value"
      $arguments = func_get_args();
      $drush_args = array_splice($arguments, 2);
      foreach ($drush_args as $i => $arg) {
        list($name, $value) = explode('=', $arg);
        $task_args[$name] = $value;
      }
      $task = hosting_add_task($ref->nid, $type, $task_args);
      drush_log(dt("Task node created: !url", array(
        '!url' => url("node/{$task->nid}", array(
          'absolute' => true,
        )),
      )), 'ok');
    }
  }
  if ($task->type == 'task') {
    $task->ref = node_load($task->rid);
    $task->options = array();
    $task->context_options = array(
      'context_type' => $task->ref->type,
      'root' => NULL,
      'uri' => NULL,
    );
    $task->args = array();
    $task->changed = REQUEST_TIME;
    $task->executed = REQUEST_TIME;

    /* if not already running, remove the task from the queue
     * this is to avoid concurrent task runs */
    if ($task->task_status == HOSTING_TASK_PROCESSING && !drush_get_option('force', FALSE)) {
      return drush_set_error('HOSTING_TASK_RUNNING', dt("This task is already running, use --force"));
    }
    if ($task->task_status != HOSTING_TASK_QUEUED && !drush_get_option('force', FALSE)) {
      return drush_set_error('HOSTING_TASK_NOT_QUEUED', dt("This task is not queued, use --force"));
    }
    $task->task_status = HOSTING_TASK_PROCESSING;
    $task->revision = TRUE;
    node_save($task);
    drush_set_context('HOSTING_TASK', $task);
    drush_set_context('DRUSH_LOG_CALLBACK', '_hosting_task_log');

    // Load Task Info.
    $tasks_info = hosting_available_tasks($task->ref->type);

    // Find task type and pass through if it needs provision_save.
    if (isset($tasks_info[$task->task_type])) {
      $task->task_info = $tasks_info[$task->task_type];
    }
  }
  else {
    drush_set_error('HOSTING_INVALID_TASK', t("Could not find or create a '!type' task for hosting context '!context'.", array(
      '!type' => $type,
      '!context' => $id,
    )));
  }
}

/**
 * Drush hosting task command.
 *
 * This is the main way that the frontend communicates with the backend. Tasks
 * correspond to backend drush commands, and the results and log of the command
 * are attached to the task for reference.
 *
 * @see drush_hosting_task_validate()
 * @see hook_hosting_TASK_OBJECT_context_options()
 */
function drush_hosting_task() {
  $task =& drush_get_context('HOSTING_TASK');
  $output = array();
  $mode = drush_get_option('debug', FALSE) ? 'GET' : 'POST';

  // Make sure argument order is correct.
  ksort($task->args);

  // If this task type needs it, run provision-save to create the named context.
  if (!empty($task->task_info['provision_save'])) {

    // Invoke hook_hosting_TASK_OBJECT_context_options()
    // We copy module_invoke_all() here because it doesn't pass by
    // reference and it breaks under PHP 5.3
    $hook = 'hosting_' . $task->ref->type . '_context_options';
    foreach (module_implements($hook) as $module) {
      $function = $module . '_' . $hook;
      call_user_func_array($function, array(
        &$task,
      ));
    }

    // Launch a provision-save for the platform before running it for the site.
    if ($task->ref->type == 'site') {

      // Create a verify task to use as a mock so we can load provision-save options and trigger post_hosting_verify hooks.
      $platform_task = _drush_hosting_task_create_platform_task($task);
      $platform_node = $platform_task->ref;

      // drush_hosting_task_provision_save($platform_node->hosting_name, $platform_task->context_options, t('Saving codebase metadata...'));
      // Save Platform Alias file.
      drush_invoke_process('@none', 'provision-save', array(
        '@' . $platform_node->hosting_name,
      ), $platform_task->context_options, array(
        'method' => $mode,
        'integrate' => TRUE,
      ));

      // Run platform verify task if this is a site install. Provision MUST run platform verify before provision-install.
      // @TODO: Modernize installation of sites.
      if ($task->task_type == 'install' && empty($task->ref->platform_verified)) {
        $output = provision_backend_invoke("@{$platform_node->hosting_name}", "provision-verify", $task->args, $task->options);
        hosting_platform_post_hosting_verify_task($platform_task, $output);
      }
    }

    // Save Current Context Alias file.
    drush_invoke_process('@none', 'provision-save', array(
      '@' . $task->ref->hosting_name,
    ), $task->context_options, array(
      'method' => $mode,
      'integrate' => TRUE,
    ));
  }
  if ($task->ref->type == 'site' && $task->ref->site_status == HOSTING_SITE_DELETED || $task->ref->type == 'platform' && $task->ref->platform_status == HOSTING_PLATFORM_DELETED) {

    // We're performing a task on a site that has been deleted...
    // d() will not be returning a site object.
    $alias = '@none';
  }
  else {
    $alias = $task->ref->hosting_name;
  }
  if (!isset($task->task_command)) {
    $task->task_command = 'provision-' . $task->task_type;
  }

  // Tell provision this is being run by drush hosting-task
  $task->options['runner'] = 'hosting_task';

  // Run the actual command. Adding alias here to work around Drush API.
  foreach ($task->options as $name => $value) {
    $options[] = "--{$name}={$value}";
  }
  $args = implode(' ', $task->args);
  $opts = implode(' ', $options);
  $command = "drush @{$alias} {$task->task_command} {$args} {$opts}";
  drush_log(dt("Running command: {$command}"), 'p_log');
  $output = provision_backend_invoke($alias, $task->task_command, $task->args, $task->options, $mode);

  // Pass the drush output back to the HOOK_post_hosting_TASK_task() and
  // HOOK_hosting_TASK_task_rollback() hooks, as the $data argument.
  drush_set_context('HOSTING_DRUSH_OUTPUT', $output);

  // On delete error, throw a warning.
  if ($task->task_type === 'delete') {
    if (drush_get_error()) {
      drush_log(dt('Command "provision-delete" failed. Not running "@@alias provision-save --delete".', [
        '@alias' => $task->ref->hosting_name,
      ]), 'warning');
      drush_clear_error();
    }
    else {

      // If no error, run 'provision-save --delete'
      drush_invoke_process('@none', 'provision-save', array(
        '@' . $task->ref->hosting_name,
      ), array(
        'delete' => TRUE,
      ), array(
        'method' => $mode,
        'integrate' => TRUE,
      ));
    }
  }

  // New revision is created at the beginning of function.
  $task->revision = FALSE;
  $task->delta = microtime(TRUE) - $task->executed;
  node_save($task);

  // @TODO: END OF PROVISION 3 CODE

  /** @TODO: BEGINNING OF MODERNIZATION HACKS:
    $task = &drush_get_context('HOSTING_TASK');
    $output = array();
    $mode = drush_get_option('debug', FALSE) ? 'GET' : 'POST';

    // Make sure argument order is correct.
    ksort($task->args);

    // If this task type needs it, run provision-save to create the named context.
    if (!empty($task->task_info['provision_save'])) {

      // Launch a provison-save for the platform before running it for the site.
      if ($task->ref->type == 'site') {

        // Create a verify task
        $platform_node = node_load($task->ref->platform);

        $platform_task = clone($task);
        $platform_task->nid = null;
        $platform_task->vid = null;
        $platform_task->title = "Verify: " . $platform_node->hosting_name;
        $platform_task->task_type = 'verify';
        $platform_task->ref = $platform_node;
        $platform_task->rid = $platform_node->nid;
        $platform_task->context_options = array();

        // Save platform
        $hook = 'hosting_platform_context_options';
        foreach (module_implements($hook) as $module) {
          $function = $module . '_' . $hook;
          call_user_func_array($function, array(&$platform_task));
        }
        $platform_task->context_options['context_type'] = 'platform';

        drush_hosting_task_provision_save($platform_node->hosting_name, $platform_task->context_options, t('Saving codebase metadata...'));

        // Run platform verify task if this is a site or platform verify.
        if ($task->task_type == 'verify' || $task->task_type == 'install') {
  //        hosting_process(array(
  //          "drush",
  //          "@{$platform_task->ref->hosting_name}",
  //          "provision-verify",
  //        ), null, t('Preparing codebase...'));
          $output = provision_backend_invoke("@{$platform_task->ref->hosting_name}", "provision-verify", $mode);
        }
      }


      // Prepare context options and run provision-save.
      // Invoke hook_hosting_TASK_OBJECT_context_options()
      // We copy module_invoke_all() here because it doesn't pass by
      // reference and it breaks under PHP 5.3
      $hook = 'hosting_' . $task->ref->type . '_context_options';
      foreach (module_implements($hook) as $module) {
        $function = $module . '_' . $hook;
        call_user_func_array($function, array(&$task));
      }

      drush_hosting_task_provision_save($task->ref->hosting_name, $task->context_options, t('Saving metadata...'));

      /** @TODO: uncomment when pro4 cli is ready.
      // Save services to the context
      // Servers have services, platforms and sites have service subscriptions.
      if (is_array($task->ref->services)) {
        foreach ($task->ref->services as $service_type => $service_class) {
          $command = array(
            variable_get('provision_bin_command', '/usr/share/devshop/provision/bin/provision'),
            "@{$task->ref->hosting_name}",
            "-n",
            "--ansi",
            'service',
            'add',
            $service_type,
            "--service_type={$service_class->type}"
          );

          foreach ($service_class->provisionProperties() as $name => $value) {
            $value = escapeshellarg($value);
            $command[] = "--{$name}={$value}";
          }
          hosting_process($command, null, t('Saving service metadata...'));
        }
      }
      elseif (is_array($task->ref->servers)) {
        foreach ($task->ref->servers as $service_type => $server_node) {
          $command = array(
            variable_get('provision_bin_command', '/usr/share/devshop/provision/bin/provision'),
            "@{$task->ref->hosting_name}",
            "-n",
            "--ansi",
            "service",
            "add",
            $service_type,
            $server_node->hosting_name,
          );

          // @TODO: Provision4 support for properties.
  //        foreach ($subscription->properties as $name => $value) {
  //          $value = escapeshellarg($value);
  //          $command[] = "--{$name}={$value}";
  //        }
          hosting_process($command, null, t('Assigning servers...'));
        }
      }
    }

    if (($task->ref->type == 'site' && $task->ref->site_status == HOSTING_SITE_DELETED)
      || ($task->ref->type == 'platform' && $task->ref->platform_status == HOSTING_PLATFORM_DELETED)) {

      // We're performing a task on a site that has been deleted...
      // d() will not be returning a site object.
      $alias = '@none';
    }
    else {
      $alias = $task->ref->hosting_name;
    }

    if (empty($task->task_command)) {
      $task->task_command = 'provision-' . $task->task_type;
    }

    // Run the actual command. Adding alias here to work around Drush API.
  // @TODO: Convert from Legacy
    $output = provision_backend_invoke($alias, $task->task_command, $task->args, $task->options, $mode);

    /**
    // Build the task command.
    // @TODO: Create a function for this?
    $command = array(
      variable_get('provision_bin_command', '/usr/share/devshop/provision/bin/provision'),
      "@{$task->ref->hosting_name}",
      "-n",
      "--ansi",
      $task->task_command
    );

    // Build command as an array for compatibility.
    $command = array(
      "drush",
      "@{$task->ref->hosting_name}",
      $task->task_command,
    );

    // Append each argument.
    foreach ($task->args as $argument) {
      if (!empty($argument)) {
        $command[] = escapeshellarg($argument);
      }
    }

    // Append each option.
    foreach ($task->options as $name => $value) {
      if (!empty($value)) {
        // If $value is just TRUEish, don't show value in the command line
        if ((string) $value == "1") {
          $command[] = "--{$name}";
        }
        else {
          $value = escapeshellarg($value);
          $command[] = "--{$name}={$value}";
        }
      }
    }

    // Append --strict option because... hosting tasks often pass arbitrary options.
    $command[] = '--strict=0';
    hosting_process($command, null, t('Running Task...'));

    // Pass the drush output back to the HOOK_post_hosting_TASK_task() and
    // HOOK_hosting_TASK_task_rollback() hooks, as the $data argument.
    drush_set_context('HOSTING_DRUSH_OUTPUT', $output);

    // On successful delete, remove the named context.
    if ($task->task_type == 'delete' && !drush_get_error()) {

      // Use the task_arg if there is one, hosting_name as a backup. $node->ref->hosting_name may be missing.
      $context_to_delete = $task->task_args['hosting_context']?:
        ($task->ref->hosting_name?: FALSE);

      // Run the "provision-save --delete" command on $context_to_delete.
      if ($context_to_delete) {
        drush_log(dt("Deleting back-end context file for !context.", array(
          '!context' => $context_to_delete,
        )), 'notice');

        /* Reverting modernization for now
        // Build command as an array for compatibility.
        $command = array(
          "drush",
          "provision-save",
          "@{$task->ref->hosting_name}",
          "--delete"
        );
        hosting_process($command, null, t('Deleting metadata...'));
        drush_invoke_process('@none', 'provision-save', array('@' . $task->ref->hosting_name), array('delete' => TRUE), array('method' => $mode, 'integrate' => TRUE));
      }
      // If no context name was found, throw a warning.
      else {
        drush_log(dt('No "hosting_name" property was found on the !type node (!url). Unable to delete back-end context file.', array(
          '!type' => $task->ref->type,
          '!url' => url("node/$task->rid", array('absolute' => TRUE)),
        )), 'warning');
      }
    }

    // New revision is created at the beginning of function.
    $task->revision = FALSE;
    $task->delta = microtime(TRUE) - $task->executed;
    node_save($task);
    // @TODO: END OF MODERNIZATION HACKS
     */
}

/**
 * Helper to just run provision-save
 */
function drush_hosting_task_provision_save($hosting_name, $context_options, $message = NULL) {

  /*
  // Build command as an array for compatibility.
  $command = array(
    "drush",
    "provision-save",
    "@{$hosting_name}",
  );

  // Prepare "context_options" by converting them to command line options.
  foreach ($context_options as $name => $value) {
    if (!is_string($value)) {
      continue;
    }

    // Keep empty values here because it's being saved to context.
    // If values don't exist here, the existing drush context properties will remain unchanged.
    $value = escapeshellarg($value);
    $command[] = "--{$name}={$value}";
  }

  // Run the process using "drush_log" to present output.
  // "drush_log then saves each line to the front-end database.
  // @TODO: Leaving legacy provision in place for this PR.
  $message = $message?: t('Saving metadata...');
  hosting_process($command, null, t($message));
  */
  $mode = drush_get_option('debug', FALSE) ? 'GET' : 'POST';
  drush_invoke_process('@none', 'provision-save', array(
    '@' . $hosting_name,
  ), $context_options, array(
    'method' => $mode,
    'integrate' => TRUE,
  ));

  // hosting_process($command, null, t($message));
}

/**
 * @param $site_task'
 */
function _drush_hosting_task_create_platform_task($site_task) {
  $platform_node = node_load($site_task->ref->platform);
  $platform_task = clone $site_task;
  $platform_task->nid = null;
  $platform_task->vid = null;
  $platform_task->title = "Verify: " . $platform_node->hosting_name;
  $platform_task->task_type = 'verify';
  $platform_task->ref = $platform_node;
  $platform_task->rid = $platform_node->nid;
  $platform_task->context_options = array();

  // Save platform
  $hook = 'hosting_platform_context_options';
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    call_user_func_array($function, array(
      &$platform_task,
    ));
  }
  $platform_task->context_options['context_type'] = 'platform';
  return $platform_task;
}

/**
 * Rollback hook for the hosting-task Drush command.
 *
 * @see hook_hosting_TASK_TYPE_task_rollback()
 */
function drush_hosting_task_rollback() {
  $task =& drush_get_context('HOSTING_TASK');
  $hook = sprintf("hosting_%s_task_rollback", str_replace('-', '_', $task->task_type));
  drush_log(dt('Invoking :hook hooks.', array(
    ':hook' => $hook,
  )));
  module_invoke_all($hook, $task, drush_get_context('HOSTING_DRUSH_OUTPUT'));
}

/**
 * Post completion hook for the hosting-task Drush command.
 *
 * @see hook_post_hosting_TASK_TYPE_task()
 *
 * Used primarily to trigger hosting_context_delete() for site, platform and
 * server nodes, but only AFTER the task has completed successfully.
 */
function drush_hosting_post_hosting_task($task) {
  $task =& drush_get_context('HOSTING_TASK');
  $hook = sprintf("post_hosting_%s_task", str_replace('-', '_', $task->task_type));
  drush_log(dt('Invoking :hook hooks.', array(
    ':hook' => $hook,
  )));
  module_invoke_all($hook, $task, drush_get_context('HOSTING_DRUSH_OUTPUT'));
}

Functions

Namesort descending Description
drush_hosting_post_hosting_task Post completion hook for the hosting-task Drush command.
drush_hosting_task Drush hosting task command.
drush_hosting_task_provision_save Helper to just run provision-save
drush_hosting_task_rollback Rollback hook for the hosting-task Drush command.
drush_hosting_task_validate Validate hook for the hosting-task Drush command.
hosting_process Run a command, sending output to drush logs in real time.
_drush_hosting_task_create_platform_task
_hosting_task_log Log a message to the current task's node if possible, the screen otherwise.
_hosting_task_log_print Switch between Drush 8's OO logging and the older private function.