/** * Invoke `drush` commands on a Pantheon development site * * <commands>... * : The Drush commands you intend to run. * * [--<flag>=<value>] * : Additional Drush flag(s) to pass in to the command. * * [--site=<site>] * : The name (DNS shortname) of your site on Pantheon. * * [--env=<environment>] * : Your Pantheon environment. Default: dev * */ function __invoke($args, $assoc_args) { $environment = Input::env($assoc_args); $sites = new Sites(); $site = $sites->get(Input::sitename($assoc_args)); if (!$site) { Terminus::error("Command could not be completed. Unknown site specified."); exit; } $server = array('user' => "{$environment}.{$site->get('id')}", 'host' => "appserver.{$environment}.{$site->get('id')}.drush.in", 'port' => '2222'); if (strpos(TERMINUS_HOST, 'onebox') !== FALSE) { $server['user'] = "******"; $server['host'] = TERMINUS_HOST; } # Sanitize assoc args so we don't try to pass our own flags. # TODO: DRY this out? unset($assoc_args['site']); if (isset($assoc_args['env'])) { unset($assoc_args['env']); } # Create user-friendly output $command = implode($args, ' '); $flags = ''; foreach ($assoc_args as $k => $v) { if (isset($v) && (string) $v != '') { $flags .= "--{$k}={$v} "; } else { $flags .= "--{$k} "; } } $this->logger->info("Running drush %s %s on %s-%s", array($command, $flags, $site->get('name'), $environment)); $this->send_command($server, 'drush', $args, $assoc_args); }
/** * Invoke `wp` commands on a Pantheon development site * * <commands>... * : The WP-CLI commands you intend to run. * * [--<flag>=<value>] * : Additional WP-CLI flag(s) to pass in to the command. * * [--site=<site>] * : The name (DNS shortname) of your site on Pantheon. * * [--env=<environment>] * : Your Pantheon environment. Default: dev * */ function __invoke($args, $assoc_args) { $environment = Input::env($assoc_args); $sites = new Sites(); $site = $sites->get(Input::sitename($assoc_args)); if (!$site) { Terminus::error("Command could not be completed. Unknown site specified."); exit; } # see https://github.com/pantheon-systems/titan-mt/blob/master/dashboardng/app/workshops/site/models/environment.coffee $server = array('user' => "{$environment}.{$site->get('id')}", 'host' => "appserver.{$environment}.{$site->get('id')}.drush.in", 'port' => '2222'); if (strpos(TERMINUS_HOST, 'onebox') !== FALSE) { $server['user'] = "******"; $server['host'] = TERMINUS_HOST; } # Sanitize assoc args so we don't try to pass our own flags. # TODO: DRY this out? unset($assoc_args['site']); if (isset($assoc_args['env'])) { unset($assoc_args['env']); } # Create user-friendly output $command = implode($args, ' '); $flags = ''; foreach ($assoc_args as $k => $v) { if (isset($v) && (string) $v != '') { $flags .= "--{$k}=" . escapeshellarg($v) . ' '; } else { $flags .= "--{$k} "; } } $this->logger->info("Running wp %s %s on %s-%s", array($command, $flags, $site->get('name'), $environment)); $this->send_command($server, 'wp', $args, $assoc_args); }
public function __construct($filename, $delimiter = ',') { $this->filePointer = fopen($filename, 'r'); if (!$this->filePointer) { \Terminus::error(sprintf('Could not open file: %s', $filename)); } $this->delimiter = $delimiter; }
/** * Like get(), but calls Terminus::error() instead of returning false. * * @param string $arg The raw CLI argument */ public function get_check($arg) { $item = $this->get($arg); if (!$item) { \Terminus::error(sprintf($this->msg, $arg)); } return $item; }
/** * View Pantheon artwork * * ## Options * * fist * * unicorn * * druplicon * * wordpress */ function __invoke($args, $assoc_args) { $artwork = array_shift($args) ?: array_rand($this->works); if (!empty($artwork) && array_key_exists($artwork, $this->works)) { echo Terminus::colorize("%g" . base64_decode($this->works[$artwork]) . "%n") . "\n"; } else { Terminus::error("No formula for requested artwork"); } }
/** * Commands specific to an environment * * <commands>... * [--site=<value>] * : specify the site on which the command should be performed * [--env=<value>] * : Specificy the environment of a site previously set with --site= * * [--<flag>=<value>] * : Additional argument flag(s) to pass in to the command. */ function __invoke(array $args, array $assoc_args) { if (empty($args)) { Terminus::error("You need to specify a task to perform, site and environment on which to perform."); } else { $this->_handleFuncArg($args, $assoc_args); $this->_handleSiteArg($args, $assoc_args); } $this->_execute($args, $assoc_args); }
protected function _update($args, $assoc_args, $callback) { $status = 0; if (empty($assoc_args)) { \Terminus::error("Need some fields to update."); } foreach ($args as $obj_id) { $params = array_merge($assoc_args, array($this->obj_id_key => $obj_id)); $status = $this->success_or_failure($this->wp_error_to_resp($callback($params), "Updated {$this->obj_type} {$obj_id}.")); } exit($status); }
/** * Get help on a certain command. * * <command> * * ## EXAMPLES * * # get help for `sites` command * terminus help sites * * # get help for `sites list` subcommand * terminus help sites list * * @synopsis [<command>...] */ function __invoke($args, $assoc_args) { $command = self::find_subcommand($args); if ($command) { self::show_help($command); exit; } // WordPress is already loaded, so there's no chance we'll find the command if (function_exists('add_filter')) { \Terminus::error(sprintf("'%s' is not a registered command.", $args[0])); } }
/** * @param $args array of args to parse value from. * @param $exit (boolean) if true throw error when no value is found. * * @return product array */ public static function product($args, $key, $exit = true) { if (isset($assoc_args[$key])) { $product = Products::getByIdOrName($assoc_args[$key]); if (!$product) { \Terminus::error("Couldn't find product: %s", array($assoc_args['product'])); } } else { $product = \Terminus::menu(Products::selectList()); $product = Products::getByIndex($product); } if (!$product and $exit) { \Terminus::error("Product is required."); } return $product; }
/** * Waits on this workflow to finish * * @return [Workflow] $this */ public function wait() { while (!$this->isFinished()) { $this->fetch(); sleep(3); print "."; } if ($this->isSuccessful()) { return $this; } else { $final_task = $this->get('final_task'); if ($final_task != null && !empty($final_task->messages)) { foreach ($final_task->messages as $data => $message) { \Terminus::error(sprintf('[%s] %s', $message->level, $message->message)); exit; } } } }
protected function send_command($server, $remote_exec, $args, $assoc_args) { # unset CLI args unset($assoc_args['site']); $remote_cmd = $remote_exec . ' '; $remote_cmd .= implode(' ', $args); foreach ($assoc_args as $key => $value) { if ($value != 1) { $remote_cmd .= ' --' . $key . '=' . $value; } else { $remote_cmd .= ' --' . $key; } } $cmd = 'ssh -T ' . $server['user'] . '@' . $server['host'] . ' -p ' . $server['port'] . ' -o "AddressFamily inet"' . " '" . $remote_cmd . "'"; passthru($cmd, $exit_code); if ($exit_code == 255) { \Terminus::error("Failed to connect. Check your credentials, and that you are specifying a valid environment."); } return $exit_code; }
/** * Invoke `wp` commands on a Pantheon development site * * <commands>... * : The WP-CLI commands you intend to run. * * [--<flag>=<value>] * : Additional WP-CLI flag(s) to pass in to the command. * * --site=<site> * : The name (DNS shortname) of your site on Pantheon. * * [--env=<environment>] * : Your Pantheon environment. Default: dev * */ function __invoke($args, $assoc_args) { $site_name = $assoc_args['site']; if (isset($assoc_args['env'])) { $environment = $assoc_args['env']; } else { $environment = 'dev'; } $site = SiteFactory::instance($site_name); if (!$site) { Terminus::error("Command could not be completed."); exit; } # see https://github.com/pantheon-systems/titan-mt/blob/master/dashboardng/app/workshops/site/models/environment.coffee $server = array('user' => "{$environment}.{$site->getId()}", 'host' => "appserver.{$environment}.{$site->getId()}.drush.in", 'port' => '2222'); if (strpos(TERMINUS_HOST, 'onebox') !== FALSE) { $server['user'] = "******"; $server['host'] = TERMINUS_HOST; } # Sanitize assoc args so we don't try to pass our own flags. # TODO: DRY this out? unset($assoc_args['site']); if (isset($assoc_args['env'])) { unset($assoc_args['env']); } # Create user-friendly output $command = implode($args, ' '); $flags = ''; foreach ($assoc_args as $k => $v) { if (isset($v) && (string) $v != '') { $flags .= "--{$k}={$v} "; } else { $flags .= "--{$k} "; } } Terminus::line("Running wp %s %s on %s-%s", array($command, $flags, $site->getName(), $environment)); $this->send_command($server, 'wp', $args, $assoc_args); }
/** * Import a new site * @package 2.0 * * ## OPTIONS * * [--url=<url>] * : Url of archive to import * * [--name=<name>] * : Name of the site to create (machine-readable) * * [--label=<label>] * : Label for the site * * [--org=<org>] * : UUID of organization to add this site to * * @subcommand create-from-import */ public function import($args, $assoc_args) { $url = Input::string($assoc_args, 'url', "Url of archive to import"); $label = Input::string($assoc_args, 'label', "Human readable label for the site"); $slug = Utils\sanitize_name($label); $name = Input::string($assoc_args, 'name', "Machine name of the site; used as part of the default URL [ if left blank will be {$slug}]"); $name = $name ? $name : $slug; $organization = Terminus::menu(Input::orglist(), false, "Choose organization"); if (!$url) { Terminus::error("Please enter a url."); } Terminus::launch_self('sites', array('create'), array('label' => $label, 'name' => $name, 'org' => $organization)); Terminus::launch_self('site', array('import'), array('url' => $url, 'site' => $name, 'nocache' => True)); }
/** * Helper function to select valid upstream * * @param [array] $args Args to parse value from * @param [string] $key Index to search for in args * @param [boolean] $exit If true, throw error when no value is found * * @return [Upstream] $upstream */ public static function upstream($args, $key, $exit = true) { $upstreams = new Upstreams(); if (isset($args[$key])) { $upstream = $upstreams->getByIdOrName($args[$key]); if ($upstream == null) { \Terminus::error("Couldn't find upstream: %s", array($args['upstream'])); } } else { $upstream = $upstreams->get(\Terminus::menu($upstreams->getMemberList('id', 'longname'))); } if (!$upstream && $exit) { \Terminus::error('Upstream is required.'); } return $upstream; }
/** * Complete wipe and reset a site * * ## OPTIONS * * [--site=<site>] * : Site to use * * [--env=<env>] * : Specify environment, default = dev */ public function wipe($args, $assoc_args) { try { $env = @$assoc_args['env'] ?: 'dev'; $site = SiteFactory::instance(Input::site($assoc_args)); $site_id = $site->getId(); $env = Input::env($assoc_args, 'env'); Terminus::line("Wiping %s %s", array($site_id, $env)); $resp = $site->environment($env)->wipe(); if ($resp) { $this->waitOnWorkflow('sites', $site_id, $resp['data']->id); Terminus::success("Successfully wiped %s -- %s", array($site->getName(), $env)); } } catch (Exception $e) { Terminus::error("%s", array($e->getMessage())); } }
function handle_exception($exception) { $trace = $exception->getTrace(); if (!empty($trace) and \Terminus::get_config('verbose')) { foreach ($exception->getTrace() as $line) { $out_line = sprintf("%s%s%s [%s:%s]", @$line['class'], @$line['type'], @$line['function'], @$line['file'], @$line['line']); \Terminus\Loggers\Regular::redLine(">> {$out_line}"); } } //\Terminus::line(var_export($exception->getTrace(), 1)); \Terminus::error("Exception thrown - %s", array($exception->getMessage())); }
/** * Read a value, from various formats. * * @param mixed $value * @param array $assoc_args */ static function read_value($raw_value, $assoc_args = array()) { if (isset($assoc_args['format']) && 'json' == $assoc_args['format']) { $value = json_decode($raw_value, true); if (null === $value) { Terminus::error(sprintf('Invalid JSON: %s', $raw_value)); } } else { $value = $raw_value; } return $value; }
/** * List a site's workflows * * ## OPTIONS * [--site=<site>] * : Site to check * * @subcommand workflows */ public function workflows($args, $assoc_args) { $site = SiteFactory::instance(Input::sitename($assoc_args)); $workflows = $site->getWorkflows(); $data = array(); foreach ($workflows as $workflow) { $user = '******'; if (isset($workflow->get('user')->email)) { $user = $workflow->get('user')->email; } $data[] = array('workflow' => $workflow->get('description'), 'user' => $user, 'status' => $workflow->get('phase'), 'last_update' => date('Y-m-dTH:i:s', $workflow->get('created_at') + $workflow->get('total_time')), 'tasks/complete' => $workflow->get('step') . '/' . $workflow->get('number_of_tasks')); } if (count($data) > 0) { $this->constructTableForResponse($data); } else { Terminus::error('No workflows have been run on ' . $site->getName()); } }
/** * Wait on workflow to complete */ public function wait() { \Terminus::set_config('nocache', true); $tries = 0; while ($this->status('result') !== 'succeeded' and $tries < 100) { if ('failed' == $this->status('result') or 'aborted' == $this->status('result')) { if (isset($this->status->final_task) and !empty($this->status->final_task->messages)) { foreach ($this->status->final_task->messages as $data => $message) { \Terminus::error(sprintf('[%s] %s', $message->level, $message->message)); exit; } } else { \Terminus::error(PHP_EOL . "Couldn't complete jobs: '{$this->type}'" . PHP_EOL); } } sleep(3); $this->refresh(); print "."; $tries++; } print PHP_EOL; if ("succeeded" === $this->status('result')) { return $this; } return false; unset($workflow); }
/** * Execute the login based on email,password * * @param $email string (required) * @param $password string (required) * @package Terminus * @version 0.04-alpha * @return string */ private function doLogin($email, $password) { if (Terminus::is_test()) { $data = array('user_uuid' => '77629472-3050-457c-8c3d-32b2cabf992b', 'session' => '77629472-3050-457c-8c3d-32b2cabf992b:7dc42f40-65f8-11e4-b314-bc764e100eb1:ZHR0TgtQYsKcOOwMOd0tk', 'session_expire_time' => '1417727066', 'email' => '*****@*****.**'); return $data; } $options = array('body' => json_encode(array('email' => $email, 'password' => $password)), 'headers' => array('Content-type' => 'application/json')); $response = Terminus_Command::request('login', '', '', 'POST', $options); if (!$response or '200' != @$response['info']['http_code']) { \Terminus::error("[auth_error]: unsuccessful login"); } // Prepare credentials for storage. $data = array('user_uuid' => $response['data']->user_id, 'session' => $response['data']->session, 'session_expire_time' => $response['data']->expires_at, 'email' => $email); // creates a session instance Session::instance()->setData($data); return $data; }
/** * Waits and returns response from workflow. * @package Terminus * @version 2.0 * @param $object_name string -- i.e. sites / users / organization * @param $object_id string -- coresponding id * @param $workflow_id string -- workflow to wait on * * @deprecated Use new WorkFlow() object instead * Example: $this->waitOnWorkflow( "sites", "68b99b50-8942-4c66-b7e3-22b67445f55d", "e4f7e832-5644-11e4-81d4-bc764e111d20"); */ protected function waitOnWorkflow($object_name, $object_id, $workflow_id) { print "Working ."; $workflow = self::request($object_name, $object_id, "workflows/{$workflow_id}", 'GET'); $result = $workflow['data']->result; $desc = $workflow['data']->active_description; $type = $workflow['data']->type; $tries = 0; while (!isset($result)) { if ('failed' == $result or 'aborted' == $result) { if (isset($workflow['data']->final_task) and !empty($workflow['data']->final_task->messages)) { foreach ($workflow['data']->final_task->messages as $data => $message) { sprintf('[%s] %s', $message->level, $message->message); } } else { Terminus::error(PHP_EOL . "Couldn't complete jobs: '{$type}'" . PHP_EOL); } } $workflow = self::request($object_name, $object_id, "workflows/{$workflow_id}", 'GET'); $result = $workflow['data']->result; if (Terminus::get_config('debug')) { print_r($workflow); } sleep(3); print "."; $tries++; } print PHP_EOL; if (in_array($workflow['data']->result, array("succeeded", "aborted"))) { return $workflow['data']; } return false; }
/** * Execute the login based on an existing session token * * @param $session_token string (required) * @return array */ private function doLoginFromSessionToken($session_token) { $options = array('headers' => array('Content-type' => 'application/json'), 'cookies' => array('X-Pantheon-Session' => $session_token)); # Temporarily disable the cache for this GET call $response = TerminusCommand::request('user', '', '', 'GET', $options); if (!$response or '200' != @$response['info']['http_code']) { \Terminus::error("[auth_error]: session token not valid"); } // Prepare credentials for storage. $data = array('user_uuid' => $response['data']->id, 'session' => $session_token, 'session_expire_time' => 0, 'email' => $response['data']->email); // creates a session instance Session::instance()->setData($data); return $data; }
/** * @return array list of invalid $assoc_args keys to unset */ private function validate_args($args, $assoc_args, $extra_args) { $synopsis = $this->get_synopsis(); if (!$synopsis) { return array(); } $validator = new \Terminus\SynopsisValidator($synopsis); $cmd_path = implode(' ', get_path($this)); foreach ($validator->get_unknown() as $token) { \Terminus::warning(sprintf("The `%s` command has an invalid synopsis part: %s", $cmd_path, $token)); } if (!$validator->enough_positionals($args)) { $this->show_usage(); exit(1); } if ($this->name != 'help') { $invalid = $validator->invalid_positionals($args); if ($invalid) { \Terminus::error("Invalid positional value: {$invalid}"); } } $unknown_positionals = $validator->unknown_positionals($args); if (!empty($unknown_positionals)) { \Terminus::error('Too many positional arguments: ' . implode(' ', $unknown_positionals)); } list($errors, $to_unset) = $validator->validate_assoc(array_merge(\Terminus::get_config(), $extra_args, $assoc_args)); foreach ($validator->unknown_assoc($assoc_args) as $key) { $errors['fatal'][] = "unknown --{$key} parameter"; } if (!empty($errors['fatal'])) { $out = 'Parameter errors:'; foreach ($errors['fatal'] as $error) { $out .= "\n " . $error; } \Terminus::error($out); } array_map('\\Terminus::warning', $errors['warning']); return $to_unset; }
/** * Show multiple fields of an object. * * @param object|array Data to display * @param string Format to display the data in */ private static function show_multiple_fields($data, $format) { switch ($format) { case 'table': self::assoc_array_to_table($data); break; case 'json': \Terminus::print_value($data, array('format' => $format)); break; default: \Terminus::error("Invalid format: " . $format); break; } }
/** * Launch system's $EDITOR to edit text * * @param [string] $input Text to be put into the temp file for changing * @param [string] $title Name for the temporary file * @return [string] $output Output string if input has changed, false otherwise */ function launch_editor_for_input($input, $title = 'Terminus') { $tmpfile = wp_tempnam($title); if (!$tmpfile) { \Terminus::error('Error creating temporary file.'); } $output = ''; file_put_contents($tmpfile, $input); $editor = getenv('EDITOR'); if (!$editor) { if (isset($_SERVER['OS']) && strpos($_SERVER['OS'], 'indows') !== false) { $editor = 'notepad'; } else { $editor = 'vi'; } } \Terminus::launch("{$editor} " . escapeshellarg($tmpfile)); $output = file_get_contents($tmpfile); unlink($tmpfile); if ($output == $input) { return false; } return $output; }
public static function loggedIn() { if (false === Session::instance()->getValue('session', false) and !Terminus::is_test()) { \Terminus::error("Please login first with `terminus auth login`"); } }
/** * Update alls dev sites with an available upstream update. * * ## OPTIONS * * [--report] * : If set output will contain list of sites and whether they are up-to-date * * [--upstream=<upstream>] * : Specify a specific upstream to check for updating. * * [--no-updatedb] * : Use flag to skip running update.php after the update has applied * * [--xoption=<theirs|ours>] * : Corresponds to git's -X option, set to 'theirs' by default -- https://www.kernel.org/pub/software/scm/git/docs/git-merge.html * * @subcommand mass-update */ public function mass_update($args, $assoc_args) { $sites = SiteFactory::instance(); $env = 'dev'; $upstream = Input::optional('upstream', $assoc_args, false); $data = array(); $report = Input::optional('report', $assoc_args, false); $confirm = Input::optional('confirm', $assoc_args, false); // Start status messages. if ($upstream) { Terminus::line('Looking for sites using ' . $upstream . '.'); } foreach ($sites as $site) { $updates = $site->getUpstreamUpdates(); if (!isset($updates->behind)) { // No updates, go back to start. continue; } // Check for upstream argument and site upstream URL match. $siteUpstream = $site->info('upstream'); if ($upstream and isset($siteUpstream->url)) { if ($siteUpstream->url != $upstream) { // Uptream doesn't match, go back to start. continue; } } if ($updates->behind > 0) { $data[$site->getName()] = array('site' => $site->getName(), 'status' => "Needs update"); $noupdatedb = Input::optional($assoc_args, 'updatedb', false); $update = $noupdatedb ? false : true; $xoption = Input::optional($assoc_args, 'xoption', 'theirs'); if (!$report) { $confirmed = Input::yesno("Apply upstream updatefs to %s ( run update.php:%s, xoption:%s ) ", array($site->getName(), var_export($update, 1), var_export($xoption, 1))); if (!$confirmed) { continue; } // Suer says No, go back to start. // Backup the DB so the client can restore if something goes wrong. Terminus::line('Backing up ' . $site->getName() . '.'); $backup = $site->environment('dev')->createBackup(array('element' => 'all')); // Only continue if the backup was successful. if ($backup) { Terminus::success("Backup of " . $site->getName() . " created."); Terminus::line('Updating ' . $site->getName() . '.'); // Apply the update, failure here would trigger a guzzle exception so no need to validate success. $response = $site->applyUpstreamUpdates($env, $update, $xoption); $data[$site->getName()]['status'] = 'Updated'; Terminus::success($site->getName() . ' is updated.'); } else { $data[$site->getName()]['status'] = 'Backup failed'; Terminus::error('There was a problem backing up ' . $site->getName() . '. Update aborted.'); } } } else { if (isset($assoc_args['report'])) { $data[$site->getName()] = array('site' => $site->getName(), 'status' => "Up to date"); } } } if (!empty($data)) { sort($data); $this->handleDisplay($data); } else { Terminus::line('No sites in need up updating.'); } }
public function logMessages() { foreach ($this->status->final_task->messages as $data => $message) { \Terminus::error(sprintf('[%s] %s', $message->level, $message->message)); } }
/** * Outputs basic response success/failure messages * * @param [array] $response Array from response * @param [array] $messages Array of response strings * [string] success Displayed on success * [string] failure Displayed on error */ protected function responseOutput($response, $messages = array()) { $default_messages = array('success' => 'The operation has succeeded.', 'failure' => 'The operation was unsuccessful.'); $messages = array_merge($default_messages, $messages); if ($response['status_code'] == 200) { Terminus::success($messages['success']); } else { Terminus::error($messages['failure']); } }
/** * Start a dashboard session. * * It doesn't follow the normal pattern since it's working off Drupal's login * forms directly. This will be refactored when there's a direct CLI auth * mechanism in the API itself. * * Many thanks to Amitai and the gang at: https://drupal.org/node/89710 */ function auth($email, $password) { if (!$email) { $email = drush_get_option('email'); } $host = TERMINUS_HOST; $url = 'https://' . $host . '/login'; $ch = curl_init(); if (strpos(TERMINUS_HOST, 'onebox') !== FALSE) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); } // Set URL and other appropriate options. curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // Grab URL and pass it to the browser. $result = curl_exec($ch); if (curl_errno($ch) != 0) { $err = curl_error($ch); curl_close($ch); return \Terminus::error("Dashboard unavailable: {$err}"); } $form_build_id = get_form_build_id($result); // Attempt to log in. $login_data = array('email' => $email, 'password' => $password, 'form_build_id' => $form_build_id, 'form_id' => 'atlas_login_form', 'op' => 'Login'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $login_data); curl_setopt($ch, CURLOPT_HEADER, 1); $result = curl_exec($ch); if (curl_errno($ch) != 0) { $err = curl_error($ch); return \Terminus::error("Dashboard unavailable: {$err}"); } // Close cURL resource, and free up system resources. curl_close($ch); $set_cookie_header = parse_drupal_headers($result, 'Set-Cookie'); if (!$set_cookie_header) { return \Terminus::error('Authentication failed. Please check your credentials and try again.'); } $session = get_session_from_header($set_cookie_header); if (!$session) { return \Terminus::error('Session not found. Please check your credentials and try again.'); } // Get the UUID. $user_uuid = get_user_uuid_from_headers($result); if (!\Terminus\Utils\is_valid_uuid($user_uuid)) { return \Terminus::error('Could not determine user UUID. Please check your credentials and try again.'); } // Prepare credentials for storage. $data = array('user_uuid' => $user_uuid, 'session' => $session, 'session_expire_time' => get_session_expiration_from_header($set_cookie_header), 'email' => $email); return $data; }