/** * Get help on a certain command. * * [<commands>...] * : The command you want information on * * [--recursive] * : Display full information on all subcommands and their subcommands * * ## EXAMPLES * * # get help for `sites` command * terminus help sites * * # get help for `sites` command and all its subcommands * terminus help sites --recursive * * # get help for `sites list` subcommand * terminus help sites list */ public function __invoke($args, $assoc_args) { $this->recursive = Input::optional(array('key' => 'recursive', 'choices' => $assoc_args, 'default' => false)); $command = $this->findSubcommand($args); if ($command) { $status = $this->showHelp($command); exit($status); } $this->failure('"{cmd}" is not a registered command.', array('cmd' => $args[0])); }
/** * Lists available backups * * @params [array] $assoc_args Parameters and flags from the command line * @return [array] $data Elements as follows: * [string] file The backup's file name * [string] size The backup file's size * [string] date The datetime of the backup's creation */ private function listBackups($assoc_args) { $site = $this->sites->get(Input::sitename($assoc_args)); $env = $site->environments->get(Input::env(array('args' => $assoc_args, 'site' => $site))); $element = null; if (isset($assoc_args['element']) && $assoc_args['element'] != 'all') { $element = Input::backupElement(array('args' => $assoc_args)); } $backups = $env->backups->getFinishedBackups($element); $latest = (bool) Input::optional('latest', $assoc_args, false); if (empty($backups)) { $this->log()->warning('No backups found.'); } else { if ($latest) { array_splice($backups, 1); } $data = array(); foreach ($backups as $id => $backup) { $data[] = array('file' => $backup->get('filename'), 'size' => $backup->getSizeInMb(), 'date' => $backup->getDate(), 'initiator' => $backup->getInitiator()); } return $data; } }
/** * 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.'); } }
/** * Change the site payment instrument * * ## OPTIONS * * [--site=<site>] * : Site to use * * [--change-to-org=<org>] * : Change the instrument to an Org by setting the id. ( must be admin ) * * ## EXAMPLES * * terminus site instrument --site=sitename */ public function instrument($args, $assoc_args) { $site = SiteFactory::instance(Input::site($assoc_args)); $org = Input::optional('change-to-org', $assoc_args); $data = $site->instrument($org); // @TODO we need a "workflow" class to handle these exceptions and whatnot if ($org) { if ('failed' == $data->result || 'aborted' == $data->result) { if (isset($data->final_task) and !empty($data->final_task->messages)) { foreach ((array) $data->final_task->messages as $date => $message) { \Terminus::error('%s', $message->message); } } } if ($data->result != 'succeeded') { $this->waitOnWorkflow('workflow', $site->getId(), $data->id); } $data = $site->instrument(); } \Terminus::line("Successfully updated payment instrument."); $this->handleDisplay($data); }
/** * Get, load, create, or list backup information * * ## OPTIONS * * <get|load|create|list> * : Function to run - get, load, create, or list * * [--site=<site>] * : Site to load * * [--env=<env>] * : Environment to load * * [--element=<code|files|db|all>] * : Element to download or create. `all` is only used for 'create' * * [--to=<directory|file>] * : Absolute path of a directory or filename to save the downloaded backup to * * [--file=<filename>] * : Select one of the files from the list subcommand. Only used for 'get' * * [--latest] * : If set the latest backup will be selected automatically * * [--keep-for] * : Number of days to keep this backup * * @subcommand backups * */ public function backups($args, $assoc_args) { $action = array_shift($args); $site = $this->sites->get(Input::sitename($assoc_args)); $env = $site->environments->get(Input::env($assoc_args, 'env')); //Backward compatability supports "database" as a valid element value. if (isset($assoc_args['element']) && $assoc_args['element'] == 'database') { $assoc_args['element'] = 'db'; } switch ($action) { case 'get': $file = Input::optional('file', $assoc_args, false); if ($file) { $backup = $env->getBackupByFile($file); $element = $backup->element; } else { $element = Input::backupElement(array('args' => $assoc_args)); $latest = (bool) Input::optional('latest', $assoc_args, false); $backups = $env->getFinishedBackups($element); if ($latest) { $backup = array_pop($backups); } else { $context = array('site' => $site->get('name'), 'env' => $env->get('id')); $backup = Input::backup(array('backups' => $backups, 'context' => $context)); } } $url = $env->getBackupUrl($backup->folder, $element); if (isset($assoc_args['to'])) { $target = str_replace('~', $_SERVER['HOME'], $assoc_args['to']); if (is_dir($target)) { $filename = Utils\getFilenameFromUrl($url->url); $target = sprintf('%s/%s', $target, $filename); } $this->log()->info('Downloading ... please wait ...'); if ($this->download($url->url, $target)) { $this->log()->info('Downloaded {target}', compact('target')); return $target; } else { $this->failure('Could not download file'); } } $this->output()->outputValue($url->url, 'Backup URL'); return $url->url; break; case 'load': $assoc_args['to'] = '/tmp'; $assoc_args['element'] = 'database'; if (isset($assoc_args['database'])) { $database = $assoc_args['database']; } else { $database = escapeshellarg(Terminus::prompt('Name of database to import to')); } if (isset($assoc_args['username'])) { $username = $assoc_args['username']; } else { $username = escapeshellarg(Terminus::prompt('Username')); } if (isset($assoc_args['password'])) { $password = $assoc_args['password']; } else { $password = escapeshellarg(Terminus::prompt('Password')); } exec('mysql -e "show databases"', $stdout, $exit); if ($exit != 0) { $this->failure('MySQL does not appear to be installed on your server.'); } $assoc_args['env'] = $env->get('id'); $target = $this->backup(array('get'), $assoc_args); $target = '/tmp/' . Utils\getFilenameFromUrl($target); if (!file_exists($target)) { $this->failure('Cannot read database file {target}', array('target' => $target)); } $this->log()->info('Unziping database'); exec("gunzip {$target}", $stdout, $exit); // trim the gz of the target $target = Utils\sqlFromZip($target); $target = escapeshellarg($target); exec("mysql {$database} -u {$username} -p'{$password}' < {$target}", $stdout, $exit); if ($exit != 0) { $this->failure('Could not import database'); } $this->log()->info('{target} successfully imported to {db}', array('target' => $target, 'db' => $database)); return true; break; case 'create': if (!array_key_exists('element', $assoc_args)) { $options = array('code', 'db', 'files', 'all'); $assoc_args['element'] = $options[Input::menu($options, 'all', 'Select element')]; } $workflow = $env->createBackup($assoc_args); $workflow->wait(); $this->workflowOutput($workflow); break; case 'list': default: $backups = $env->getBackups(); $element_name = false; if (isset($assoc_args['element']) && $assoc_args['element'] != 'all') { $element_name = $assoc_args['element']; } if ($element_name == 'db') { $element_name = 'database'; } $data = array(); foreach ($backups as $id => $backup) { if (!isset($backup->filename) || $element_name && !preg_match(sprintf('/_%s/', $element_name), $id)) { continue; } $date = 'Pending'; if (isset($backup->finish_time)) { $date = date('Y-m-d H:i:s', $backup->finish_time); } $size = 0; if (isset($backup->size)) { $size = $backup->size / 1048576; } if ($size > 0.1) { $size = sprintf('%.1fMB', $size); } elseif ($size > 0) { $size = '0.1MB'; } else { //0-byte backups should not be recommended for restoration $size = 'Incomplete'; } $data[] = array('file' => $backup->filename, 'size' => $size, 'date' => $date); } if (empty($data)) { $this->log()->warning('No backups found.'); } $this->output()->outputRecordList($data, array('file' => 'File', 'size' => 'Size', 'date' => 'Date')); return $data; break; } }
/** * 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 * * [--tag=<tag>] * : Tag to filter by * * [--org=<id>] * : Only necessary if using --tag. Organization which has tagged the site thusly * * [--cached] * : Set to prevent rebuilding of sites cache * * @subcommand mass-update */ public function mass_update($args, $assoc_args) { // Ensure the sitesCache is up to date if (!isset($assoc_args['cached'])) { $this->sites->rebuildCache(); } $upstream = Input::optional('upstream', $assoc_args, false); $data = array(); $report = Input::optional('report', $assoc_args, false); $confirm = Input::optional('confirm', $assoc_args, false); $tag = Input::optional('tag', $assoc_args, false); $org = ''; if ($tag) { $org = Input::orgid($assoc_args, 'org'); } $sites = $this->sites->filterAllByTag($tag, $org); // Start status messages. if ($upstream) { $this->log()->info('Looking for sites using ' . $upstream . '.'); } foreach ($sites as $site) { $site->fetch(); $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->get('name')] = array('site' => $site->get('name'), 'status' => "Needs update"); $env = $site->environments->get('dev'); if ($env->getConnectionMode() == 'sftp') { $this->log()->warning('{site} has available updates, but is in SFTP mode. Switch to Git mode to apply updates.', array('site' => $site->get('name'))); $data[$site->get('name')] = array('site' => $site->get('name'), 'status' => "Needs update - switch to Git mode"); continue; } $updatedb = !Input::optional($assoc_args, 'updatedb', false); $xoption = Input::optional($assoc_args, 'xoption', 'theirs'); if (!$report) { $confirmed = Input::yesno("Apply upstream updates to %s ( run update.php:%s, xoption:%s ) ", array($site->get('name'), var_export($updatedb, 1), var_export($xoption, 1))); if (!$confirmed) { continue; } // User says No, go back to start. // Backup the DB so the client can restore if something goes wrong. $this->log()->info('Backing up ' . $site->get('name') . '.'); $backup = $env->createBackup(array('element' => 'all')); // Only continue if the backup was successful. if ($backup) { $this->log()->info("Backup of " . $site->get('name') . " created."); $this->log()->info('Updating ' . $site->get('name') . '.'); // Apply the update, failure here would trigger a guzzle exception so no need to validate success. $response = $site->applyUpstreamUpdates($env->get('id'), $updatedb, $xoption); $data[$site->get('name')]['status'] = 'Updated'; $this->log()->info($site->get('name') . ' is updated.'); } else { $data[$site->get('name')]['status'] = 'Backup failed'; $this->log()->error('There was a problem backing up ' . $site->get('name') . '. Update aborted.'); } } } else { if (isset($assoc_args['report'])) { $data[$site->get('name')] = array('site' => $site->get('name'), 'status' => "Up to date"); } } } if (!empty($data)) { sort($data); $this->output()->outputRecordList($data); } else { $this->log()->info('No sites in need of updating.'); } }
/** * Get, load, create, or list backup information * * ## OPTIONS * * <get|load|create|list> * : Function to run - get, load, create, or list * * [--site=<site>] * : Site to load * * [--env=<env>] * : Environment to load * * [--element=<code|files|db|all>] * : Element to download or create. `all` is only used for 'create' * * [--to=<directory|file>] * : Absolute path of a directory or filename to save the downloaded backup to * * [--file=<filename>] * : Select one of the files from the list subcommand. Only used for 'get' * * [--latest] * : If set the latest backup will be selected automatically * * [--keep-for] * : Number of days to keep this backup * * @subcommand backups * */ public function backups($args, $assoc_args) { $action = array_shift($args); $site = $this->sites->get(Input::sitename($assoc_args)); $env = Input::env($assoc_args, 'env'); //Backward compatability supports "database" as a valid element value. if (isset($assoc_args['element']) && $assoc_args['element'] == 'database') { $assoc_args['element'] = 'db'; } switch ($action) { case 'get': $file = Input::optional('file', $assoc_args, false); if ($file) { $regex = sprintf("/%s_%s_\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}_UTC_(.*).tar.gz/", $site->get('name'), $env); preg_match($regex, $file, $matches); $element = $matches[1]; } elseif (isset($assoc_args['element'])) { $element = $assoc_args['element']; } else { $element = Terminus::menu(array('code', 'files', 'db'), null, 'Select backup element', true); if (!in_array($element, array('code', 'files', 'db'))) { $this->failure('Invalid backup element specified.'); } } $latest = (bool) Input::optional('latest', $assoc_args, false); if (!in_array($element, array('code', 'files', 'db'))) { $this->failure('Invalid backup element specified.'); } $latest = Input::optional('latest', $assoc_args, false); $backups = $site->environments->get($env)->backups($element); if (empty($backups)) { $this->failure('No backups available. Create one with `terminus site backup create --site={site} --env={env}`', array('site' => $site->get('name'), 'env' => $env)); } //Ensure that that backups being presented for retrieval have finished $backups = array_filter($backups, function ($backup) { return isset($backup->finish_time) && $backup->finish_time; }); if ($latest) { $backup = array_pop($backups); } elseif ($file) { do { try { $candidate = array_pop($backups); } catch (\Exception $e) { $this->failure("{$file} is not a valid backup archive"); } if ($candidate->filename == $file) { $backup = $candidate; } } while (!isset($backup)); } if (!isset($backup)) { $menu = $folders = array(); foreach ($backups as $folder => $backup) { if (!isset($backup->filename)) { continue; } if (!isset($backup->folder)) { $backup->folder = $folder; } $menu[] = $backup->filename; } $index = Terminus::menu($menu, null, 'Select backup'); $backup_elements = array_values($backups); $backup = $backup_elements[$index]; } if (empty($menu)) { $this->failure('No backups available. Create one with `terminus site backup create --site={site} --env={env}`', array('site' => $site->get('name'), 'env' => $env)); } $index = 0; if (!$latest) { $index = Terminus::menu($menu, null, 'Select backup'); } $bucket = $buckets[$index]; $filename = $menu[$index]; $url = $site->environments->get($env)->backupUrl($bucket, $element); if (isset($assoc_args['to'])) { $target = str_replace('~', $_SERVER['HOME'], $assoc_args['to']); if (is_dir($target)) { $filename = Utils\getFilenameFromUrl($url->url); $target = sprintf('%s/%s', $target, $filename); } $this->log()->info('Downloading ... please wait ...'); if ($this->download($url->url, $target)) { $this->log()->info('Downloaded {target}', array('target' => $target)); return $target; } else { $this->failure('Could not download file'); } } $this->output()->outputValue($url->url, 'Backup URL'); return $url->url; break; case 'load': $assoc_args['to'] = '/tmp'; $assoc_args['element'] = 'database'; if (isset($assoc_args['database'])) { $database = $assoc_args['database']; } else { $database = escapeshellarg(Terminus::prompt('Name of database to import to')); } if (isset($assoc_args['username'])) { $username = $assoc_args['username']; } else { $username = escapeshellarg(Terminus::prompt('Username')); } if (isset($assoc_args['password'])) { $password = $assoc_args['password']; } else { $password = escapeshellarg(Terminus::prompt('Password')); } exec('mysql -e "show databases"', $stdout, $exit); if ($exit != 0) { $this->failure('MySQL does not appear to be installed on your server.'); } $assoc_args['env'] = $env; $target = $this->backup(array('get'), $assoc_args); $target = '/tmp/' . Utils\getFilenameFromUrl($target); if (!file_exists($target)) { $this->failure('Cannot read database file {target}', array('target' => $target)); } $this->log()->info('Unziping database'); exec("gunzip {$target}", $stdout, $exit); // trim the gz of the target $target = Utils\sqlFromZip($target); $target = escapeshellarg($target); exec("mysql {$database} -u {$username} -p'{$password}' < {$target}", $stdout, $exit); if ($exit != 0) { $this->failure('Could not import database'); } $this->log()->info('{target} successfully imported to {db}', array('target' => $target, 'db' => $database)); return true; break; case 'create': if (!array_key_exists('element', $assoc_args)) { $options = array('code', 'db', 'files', 'all'); $assoc_args['element'] = $options[Input::menu($options, 'all', 'Select element')]; } $workflow = $site->environments->get($env)->createBackup($assoc_args); $workflow->wait(); $this->workflowOutput($workflow); break; case 'list': default: $backups = $site->environments->get($env)->backups(); //die(print_r($backups, true)); $element_name = false; if (isset($assoc_args['element']) && $assoc_args['element'] != 'all') { $element_name = $assoc_args['element']; } if ($element_name == 'db') { $element_name = 'database'; } $data = array(); foreach ($backups as $id => $backup) { if (!isset($backup->filename) || $element_name && !preg_match(sprintf('/_%s/', $element_name), $id)) { continue; } $date = 'Pending'; if (isset($backup->finish_time)) { $date = date('Y-m-d H:i:s', $backup->finish_time); } $size = 0; if (isset($backup->size)) { $size = $backup->size / 1048576; } if ($size > 0.1) { $size = sprintf('%.1fMB', $size); } elseif ($size > 0) { $size = '0.1MB'; } else { //0-byte backups should not be recommended for restoration $size = 'Incomplete'; } $data[] = array('file' => $backup->filename, 'size' => $size, 'date' => $date); } if (empty($data)) { $this->log()->warning('No backups found.'); } $this->output()->outputRecordList($data, array('file' => 'File', 'size' => 'Size', 'date' => 'Date')); return $data; break; } }
function testOptionalReturnsDefaultFromFunction() { $default_null = Input::optional(array('key' => 'key', 'choices' => array('not' => 'me'))); $this->assertEquals(null, $default_null); }
/** * Get, load, create, or list backup information * * ## OPTIONS * * <get|load|create|list> * : Function to run - get, load, create, or list * * [--site=<site>] * : Site to load * * [--env=<env>] * : Environment to load * * [--element=<code|files|db|all>] * : Element to download or create. *all* only used for 'create' * * [--to-directory=<directory>] * : Absolute path of directory to download the file * * [--latest] * : If set the latest backup will be selected automatically * * [--keep-for] * : Number of days to keep this backup * * @subcommand backups * */ public function backups($args, $assoc_args) { $action = array_shift($args); $site = SiteFactory::instance(Input::sitename($assoc_args)); $env = Input::env($assoc_args, 'env'); switch ($action) { case 'get': //Backward compatability supports "database" as a valid element value. if (@$assoc_args['element'] == 'database') { $assoc_args['element'] = 'db'; } // prompt for backup type if (!($element = @$assoc_args['element'])) { $element = Terminus::menu(array('code', 'files', 'db'), null, "Select type backup", TRUE); } if (!in_array($element, array('code', 'files', 'db'))) { Terminus::error("Invalid backup element specified."); } $latest = Input::optional('latest', $assoc_args, false); $backups = $site->environment($env)->backups($element); // Ensure that that backups being presented for getting have finished $backups = array_filter($backups, function ($backup) { return isset($backup->finish_time) && $backup->finish_time; }); if ($latest) { $backups = array(array_pop($backups)); } if (empty($backups)) { \Terminus::error('No backups available.'); } $menu = $folders = array(); // build a menu for selecting back ups foreach ($backups as $folder => $backup) { if (!isset($backup->filename)) { continue; } if (!isset($backup->folder)) { $backup->folder = $folder; } $buckets[] = $backup->folder; $menu[] = $backup->filename; } if (empty($menu)) { Terminus::error("No backups available. Create one with `terminus site backup create --site=%s --env=%s`", array($site->getName(), $env)); } $index = 0; if (!$latest) { $index = Terminus::menu($menu, null, "Select backup"); } $bucket = $buckets[$index]; $filename = $menu[$index]; $url = $site->environment($env)->backupUrl($bucket, $element); if (isset($assoc_args['to-directory'])) { Terminus::line("Downloading ... please wait ..."); $filename = \Terminus\Utils\get_filename_from_url($url->url); $target = sprintf("%s/%s", $assoc_args['to-directory'], $filename); if (TerminusCommand::download($url->url, $target)) { Terminus::success("Downloaded %s", $target); return $target; } else { Terminus::error("Could not download file"); } } echo $url->url; return $url->url; break; case 'load': $assoc_args['to-directory'] = '/tmp'; $assoc_args['element'] = 'database'; $database = @$assoc_args['database'] ?: false; $username = @$assoc_args['username'] ?: false; $password = @$assoc_args['password'] ?: false; exec("mysql -e 'show databases'", $stdout, $exit); if (0 != $exit) { Terminus::error("MySQL does not appear to be installed on your server."); } $assoc_args['env'] = $env; $target = $this->backup(array('get'), $assoc_args); $target = \Terminus\Utils\get_filename_from_url($target); $target = "/tmp/{$target}"; if (!file_exists($target)) { Terminus::error("Can't read database file %s", array($target)); } Terminus::line("Unziping database"); exec("gunzip {$target}", $stdout, $exit); // trim the gz of the target $target = Terminus\Utils\sql_from_zip($target); $target = escapeshellarg($target); if (!$database) { $database = escapeshellarg(Terminus::prompt("Name of database to import to")); } if (!$username) { $username = escapeshellarg(Terminus::prompt("Username")); } if (!$password) { $password = escapeshellarg(Terminus::prompt("Password")); } exec("mysql {$database} -u {$username} -p'{$password}' < {$target}", $stdout, $exit); if (0 != $exit) { Terminus::error("Could not import database"); } Terminus::success("%s successfuly imported to %s", array($target, $database)); return true; break; case 'create': if (!array_key_exists('element', $assoc_args)) { $assoc_args['element'] = Input::menu(array('code', 'db', 'files', 'all'), 'all', "Select element"); } $result = $site->environment($env)->createBackup($assoc_args); if ($result) { Terminus::success("Created backup"); } else { Terminus::error("Couldn't create backup."); } break; case 'list': default: $backups = $site->environment($env)->backups(); $element_name = isset($assoc_args['element']) && $assoc_args['element'] != 'all' ? $assoc_args['element'] : false; if ($element_name == 'db') { $element_name = 'database'; } $data = array(); foreach ($backups as $id => $backup) { if (!isset($backup->filename)) { continue; } if ($element_name && !preg_match(sprintf('/backup_%s/', $element_name), $id)) { continue; } $date = 'Pending'; if (isset($backup->finish_time)) { $date = date("Y-m-d H:i:s", $backup->finish_time); } $size = $backup->size / 1024 / 1024; if ($size > 0.1) { $size = sprintf("%.1fMB", $size); } elseif ($size > 0) { $size = "0.1MB"; } else { // 0-byte backups should not be recommended for restore $size = "Incomplete"; } $data[] = array($backup->filename, $size, $date); } if (empty($backups)) { \Terminus::error("No backups found."); return false; } else { //munging data $this->handleDisplay($data, $args, array('File', 'Size', 'Date')); return $data; } break; } }
/** * Retrieves a single backup or downloads it as requested * * @params [array] $assoc_args Parameters and flags from the command line * @return [string] $url->url */ private function getBackup($assoc_args) { $site = $this->sites->get(Input::sitename($assoc_args)); $env = $site->environments->get(Input::env(array('args' => $assoc_args, 'site' => $site))); $file = Input::optional('file', $assoc_args, false); if ($file) { $backup = $env->backups->getBackupByFileName($file); $element = $backup->getElement(); } else { $element = Input::backupElement(array('args' => $assoc_args)); $latest = (bool) Input::optional('latest', $assoc_args, false); $backups = $env->backups->getFinishedBackups($element); if ($latest) { $backup = array_pop($backups); } else { $context = array('site' => $site->get('name'), 'env' => $env->get('id')); $backup = Input::backup(array('backups' => $backups, 'context' => $context)); } } $url = $backup->getUrl(); if (isset($assoc_args['to'])) { $target = str_replace('~', $_SERVER['HOME'], $assoc_args['to']); if (is_dir($target)) { $filename = Utils\getFilenameFromUrl($url); $target = sprintf('%s/%s', $target, $filename); } $this->log()->info('Downloading ... please wait ...'); if (Request::download($url, $target)) { $this->log()->info('Downloaded {target}', compact('target')); return $target; } else { $this->failure('Could not download file'); } } return $url; }
/** * Get, load, create, or list backup information * * ## OPTIONS * * <get|load|create|list> * : Function to run - get, load, create, or list * * [--site=<site>] * : Site to load * * [--env=<env>] * : Environment to load * * [--element=<code|files|db|all>] * : Element to download or create. *all* only used for 'create' * * [--to-directory=<directory>] * : Absolute path of directory to download the file * * [--latest] * : If set the latest backup will be selected automatically * * [--keep-for] * : Number of days to keep this backup * * @subcommand backups * */ public function backups($args, $assoc_args) { $action = array_shift($args); $site = $this->sites->get(Input::sitename($assoc_args)); $env = Input::env($assoc_args, 'env'); //Backward compatability supports "database" as a valid element value. if (isset($assoc_args['element']) && $assoc_args['element'] == 'database') { $assoc_args['element'] = 'db'; } switch ($action) { case 'get': if (isset($assoc_args['element'])) { $element = $assoc_args['element']; } else { $element = Terminus::menu(array('code', 'files', 'db'), null, 'Select backup element', true); } if (!in_array($element, array('code', 'files', 'db'))) { Terminus::error('Invalid backup element specified.'); } $latest = Input::optional('latest', $assoc_args, false); $backups = $site->environments->get($env)->backups($element); //Ensure that that backups being presented for retrieval have finished $backups = array_filter($backups, function ($backup) { return isset($backup->finish_time) && $backup->finish_time; }); if ($latest) { $backups = array(array_pop($backups)); } if (empty($backups)) { \Terminus::error('No backups available.'); } $menu = $folders = array(); foreach ($backups as $folder => $backup) { if (!isset($backup->filename)) { continue; } if (!isset($backup->folder)) { $backup->folder = $folder; } $buckets[] = $backup->folder; $menu[] = $backup->filename; } if (empty($menu)) { Terminus::error('No backups available. Create one with ' . '`terminus site backup create --site=%s --env=%s`', array($site->get('name'), $env)); } $index = 0; if (!$latest) { $index = Terminus::menu($menu, null, 'Select backup'); } $bucket = $buckets[$index]; $filename = $menu[$index]; $url = $site->environments->get($env)->backupUrl($bucket, $element); if (isset($assoc_args['to-directory'])) { Terminus::line('Downloading ... please wait ...'); $filename = \Terminus\Utils\get_filename_from_url($url->url); $target = sprintf('%s/%s', $assoc_args['to-directory'], $filename); if (TerminusCommand::download($url->url, $target)) { Terminus::success('Downloaded %s', $target); return $target; } else { Terminus::error('Could not download file'); } } Terminus::success($url->url); return $url->url; break; case 'load': $assoc_args['to-directory'] = '/tmp'; $assoc_args['element'] = 'database'; if (isset($assoc_args['database'])) { $database = $assoc_args['database']; } else { $database = escapeshellarg(Terminus::prompt('Name of database to import to')); } if (isset($assoc_args['username'])) { $username = $assoc_args['username']; } else { $username = escapeshellarg(Terminus::prompt('Username')); } if (isset($assoc_args['password'])) { $password = $assoc_args['password']; } else { $password = escapeshellarg(Terminus::prompt('Password')); } exec('mysql -e "show databases"', $stdout, $exit); if ($exit != 0) { Terminus::error('MySQL does not appear to be installed on your server.'); } $assoc_args['env'] = $env; $target = $this->backup(array('get'), $assoc_args); $target = '/tmp/' . \Terminus\Utils\get_filename_from_url($target); if (!file_exists($target)) { Terminus::error('Cannot read database file %s', array($target)); } Terminus::line('Unziping database'); exec("gunzip {$target}", $stdout, $exit); // trim the gz of the target $target = Terminus\Utils\sql_from_zip($target); $target = escapeshellarg($target); exec("mysql {$database} -u {$username} -p'{$password}' < {$target}", $stdout, $exit); if ($exit != 0) { Terminus::error('Could not import database'); } Terminus::success('%s successfuly imported to %s', array($target, $database)); return true; break; case 'create': if (!array_key_exists('element', $assoc_args)) { $assoc_args['element'] = Input::menu(array('code', 'db', 'files', 'all'), 'all', 'Select element'); } $workflow = $site->environments->get($env)->createBackup($assoc_args); $workflow->wait(); $this->workflowOutput($workflow); break; case 'list': default: $backups = $site->environments->get($env)->backups(); $element_name = false; if (isset($assoc_args['element']) && $assoc_args['element'] != 'all') { $element_name = $assoc_args['element']; } if ($element_name == 'db') { $element_name = 'database'; } $data = array(); foreach ($backups as $id => $backup) { if (!isset($backup->filename) || $element_name && !preg_match(sprintf('/backup_%s/', $element_name), $id)) { continue; } $date = 'Pending'; if (isset($backup->finish_time)) { $date = date('Y-m-d H:i:s', $backup->finish_time); } $size = $backup->size / 1048576; if ($size > 0.1) { $size = sprintf('%.1fMB', $size); } elseif ($size > 0) { $size = '0.1MB'; } else { //0-byte backups should not be recommended for restoration $size = 'Incomplete'; } $data[] = array('file' => $backup->filename, 'size' => $size, 'date' => $date); } if (empty($backups)) { \Terminus::error('No backups found.'); return false; } else { $this->outputter->outputRecordList($data, array('file' => 'File', 'size' => 'Size', 'date' => 'Date')); return $data; } break; } }
/** * Update all 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 * * [--tag=<tag>] * : Tag to filter by * * [--org=<id>] * : Only necessary if using --tag. Organization which has tagged the site * * [--cached] * : Set to prevent rebuilding of sites cache * * @subcommand mass-update */ public function massUpdate($args, $assoc_args) { // Ensure the sitesCache is up to date if (!isset($assoc_args['cached'])) { $this->sites->rebuildCache(); } $upstream = Input::optional(array('key' => 'upstream', 'choices' => $assoc_args, 'default' => false)); $data = array(); $report = Input::optional(array('key' => 'report', 'choices' => $assoc_args, 'default' => false)); $confirm = Input::optional(array('key' => 'confirm', 'choices' => $assoc_args, 'default' => false)); $tag = Input::optional(array('key' => 'tag', 'choices' => $assoc_args, 'default' => false)); $org = ''; if ($tag) { $org = Input::orgId(array('args' => $assoc_args)); } $sites = $this->sites->filterAllByTag($tag, $org); // Start status messages. if ($upstream) { $this->log()->info('Looking for sites using {upstream}.', compact('upstream')); } foreach ($sites as $site) { $context = array('site' => $site->get('name')); $site->fetch(); $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 && isset($siteUpstream->url)) { if ($siteUpstream->url != $upstream) { // Uptream doesn't match, go back to start. continue; } } if ($updates->behind > 0) { $data[$site->get('name')] = array('site' => $site->get('name'), 'status' => 'Needs update'); $env = $site->environments->get('dev'); if ($env->getConnectionMode() == 'sftp') { $message = '{site} has available updates, but is in SFTP mode.'; $message .= ' Switch to Git mode to apply updates.'; $this->log()->warning($message, $context); $data[$site->get('name')] = array('site' => $site->get('name'), 'status' => 'Needs update - switch to Git mode'); continue; } $updatedb = !Input::optional(array('key' => 'updatedb', 'choices' => $assoc_args, 'default' => false)); $xoption = !Input::optional(array('key' => 'xoption', 'choices' => $assoc_args, 'default' => 'theirs')); if (!$report) { $message = 'Apply upstream updates to %s '; $message .= '( run update.php:%s, xoption:%s ) '; $confirmed = Input::confirm(array('message' => $message, 'context' => array($site->get('name'), var_export($updatedb, 1), var_export($xoption, 1)), 'exit' => false)); if (!$confirmed) { continue; // User says No, go back to start. } // Backup the DB so the client can restore if something goes wrong. $this->log()->info('Backing up {site}.', $context); $backup = $env->createBackup(array('element' => 'all')); // Only continue if the backup was successful. if ($backup) { $this->log()->info('Backup of {site} created.', $context); $this->log()->info('Updating {site}.', $context); $response = $site->applyUpstreamUpdates($env->get('id'), $updatedb, $xoption); $data[$site->get('name')]['status'] = 'Updated'; $this->log()->info('{site} is updated.', $context); } else { $data[$site->get('name')]['status'] = 'Backup failed'; $this->failure('There was a problem backing up {site}. Update aborted.', $context); } } } else { if (isset($assoc_args['report'])) { $data[$site->get('name')] = array('site' => $site->get('name'), 'status' => 'Up to date'); } } } if (!empty($data)) { sort($data); $this->output()->outputRecordList($data); } else { $this->log()->info('No sites in need of updating.'); } }
/** * Print and save drush aliases * * ## OPTIONS * * [--print] * : print aliases to screen * * [--location=<location>] * : specify the location of the alias file, default it ~/.drush/pantheon.drushrc.php * */ public function aliases($args, $assoc_args) { $user = new User(); $print = Input::optional('print', $assoc_args, false); $json = \Terminus::get_config('json'); $location = Input::optional('location', $assoc_args, getenv("HOME") . '/.drush/pantheon.aliases.drushrc.php'); $message = "Pantheon aliases updated."; if (!file_exists($location)) { $message = "Pantheon aliases created."; } $content = $user->getAliases(); $h = fopen($location, 'w+'); fwrite($h, $content); fclose($h); chmod($location, 0777); Logger::coloredOutput("%2%K{$message}%n"); if ($json) { include $location; print \Terminus\Utils\json_dump($aliases); } elseif ($print) { print $content; } }