Skip to content

Commit

Permalink
Merge pull request #1237 from pantheon-systems/fix/command_with_ssh
Browse files Browse the repository at this point in the history
Fixed CommandWithSSH
  • Loading branch information
TeslaDethray authored Sep 26, 2016
2 parents 6e72c1a + c033cab commit 03e10ab
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 340 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project starting with the 0.6.0 release will be docu
- Fixed `site upstream-info`. (#1224)
- Fixed the notices emitting from `sites list`. (#1231)
- Fixed the memberships column in `sites list` to accurately reflect when your user is a both a team and organizational member of a site. (#1231)
- Fixed `wp` and `drush` commands. (#1237)

## [0.13.1] - 2016-09-19
### Changed
Expand Down
217 changes: 60 additions & 157 deletions php/Terminus/Commands/CommandWithSSH.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Terminus\Commands;

use Terminus\Collections\Sites;
use Terminus\Config;

/**
* Base class for Terminus commands that deal with sending SSH commands
Expand All @@ -14,12 +13,18 @@ abstract class CommandWithSSH extends TerminusCommand
* @var string Name of the client to run a command on the platform
*/
protected $client = '';

/**
* @var string Name of the command to be run as it will be used on server
*/
protected $command = '';

/**
* @var Environment
*/
protected $environment;
/**
* @var string
*/
protected $ssh_command;
/**
* @var string[] A hash of commands which do not work in Terminus. The key
* is the Drush command, and the value is the Terminus equivalent, and
Expand All @@ -39,182 +44,80 @@ public function __construct(array $options = [])
parent::__construct($options);
}

/**
* Parent invoke function
*
* @param array $args Parameters from the command line
* @param array $assoc_args Options from the command line
* @return void
*/
public function __invoke($args, $assoc_args)
{
$command = array_shift($args);
$this->ensureCommandIsPermitted($command);

$sites = new Sites();
$site = $sites->get($this->input()->siteName(['args' => $assoc_args,]));
$this->environment = $site->environments->get(
$this->input()->env(['args' => $assoc_args, 'site' => $site,])
);
$this->checkConnectionMode($this->environment);

$this->ssh_command = "{$this->command} $command";
$this->log()->info(
'Running {command} on {site}-{env}',
[
'command' => $this->ssh_command,
'site' => $site->get('name'),
'env' => $this->environment->id,
]
);
$this->log()->debug(
'Command "{command}" is being run.',
['command' => escapeshellarg($this->ssh_command),]
);
}

/**
* Checks to see if the command is not available in Terminus and, if not,
* it will refer you to an equivalent Terminus command, if such exists.
*
* @param string[] $args Command-line arguments
* @param string[] $assoc_args Command-line parameters and flags
* @param string $command The command to be sent to Pantheon via SSH
* @return void
*/
protected function checkCommand($args, $assoc_args)
protected function ensureCommandIsPermitted($command)
{
$command_array = explode(' ', $args[0]);
$command_array = explode(' ', $command);
foreach ($command_array as $element) {
if ((strpos($element, '--') === 0)
|| !isset($this->unavailable_commands[$element])
) {
if ((strpos($element, '--') === 0) || !isset($this->unavailable_commands[$element])) {
continue;
}
$alternative = $this->unavailable_commands[$element];
$error_message = "$element is not available via Terminus. "
. 'Please run it via ' . $this->client;
if (!empty($alternative)) {
$command = sprintf(
'%s %s%s',
'terminus',
$alternative,
$this->helpers->launch->assocArgsToStr($assoc_args)
$message = "That command is not available via Terminus. Please run it via {client}";
if (!empty($alternative = $this->unavailable_commands[$element])) {
$this->failure(
"$message, or you can use `{suggestion}` to the same effect.",
['client' => $this->client, 'suggestion' => "terminus $alternative",]
);
$error_message .= ', or you can use `{command}` to the same effect';
} else {
$this->failure("$message.", ['client' => $this->client,]);
}
$error_message .= '.';
$this->failure($error_message, compact('command'));
}
}

/**
* Checks the site's mode and suggests SFTP if it is not set.
*
* @param Environment $environment Environment object to check mode of
* @param Environment $env Environment object to check mode of
* @return void
*/
protected function checkConnectionMode($environment)
{
if ($environment->info('connection_mode') != 'sftp') {
$message = 'Note: This environment is in read-only Git mode. If you ';
$message .= 'want to make changes to the codebase of this site ';
$message .= '(e.g. updating modules or plugins), you will need to ';
$message .= 'toggle into read/write SFTP mode first.';
$this->log()->warning($message);
}
}

/**
* Verifies that there is only one argument given and no extaneous params
*
* @param string[] $args Command(s) given in the command line
* @param string[] $assoc_args Arguments and flags passed into the former
* @return bool True if correct
*/
protected function ensureQuotation($args, $assoc_args)
protected function checkConnectionMode($env)
{
unset($assoc_args['site']);
unset($assoc_args['env']);
if (!empty($assoc_args) || (count($args) !== 1)) {
$message = 'Your {client} subcommands and arguments must be in ';
$message .= "quotation marks.\n Example: terminus {command} ";
$message .= '"subcommand --arg=value" --site=<site> --env=<env>';

$this->failure(
$message,
['client' => $this->client, 'command' => $this->command]
if ($env->info('connection_mode') != 'sftp') {
$this->log()->warning(
"Note: This environment is in read-only Git mode. If you want to make changes to the
codebase of this site (e.g. updating modules or plugins), you will need to toggle into
read/write SFTP mode first."
);
}
return true;
}

/**
* Parses server information for connections
*
* @param array $site_info Elements as follows:
* [string] site Site UUID
* [string] environment Environment name
* @return array Connection info
*/
protected function getAppserverInfo(array $site_info = [])
{
$config = Config::getAll();
$site_id = $site_info['site'];
$env_id = $site_info['environment'];
$server = [
'user' => "$env_id.$site_id",
'host' => "appserver.$env_id.$site_id.drush.in",
'port' => '2222',
];
if (!is_null($ssh_host = $config['ssh_host'])) {
$server['user'] = "appserver.$env_id.$site_id";
$server['host'] = $ssh_host;
} elseif (strpos($config['host'], 'onebox') !== false) {
$server['user'] = "appserver.$env_id.$site_id";
$server['host'] = $config['host'];
}
return $server;
}

/**
* Parent function to SSH-based command invocations
*
* @param string[] $args Command(s) given in the command line
* @param string[] $assoc_args Arguments and flags passed into the former
* @return array Elements as follow:
* Site site Site being invoked
* string env_id Name of the environment being invoked
* string command Command to run remotely
* string server Server connection info
*/
protected function getElements($args, $assoc_args)
{
$this->ensureQuotation($args, $assoc_args);
$this->checkCommand($args, $assoc_args);

$sites = new Sites();
$site = $sites->get($this->input()->siteName(['args' => $assoc_args,]));
if (!$site) {
$this->failure('Command could not be completed. Unknown site specified.');
}

$env_id = $this->input()->env(['args' => $assoc_args, 'site' => $site,]);
if (!in_array($env_id, ['test', 'live',])) {
$this->checkConnectionMode($site->environments->get($env_id));
}

$elements = [
'site' => $site,
'env_id' => $env_id,
'command' => $args[0],
'server' => $this->getAppserverInfo(
['site' => $site->id, 'environment' => $env_id,]
),
];
return $elements;
}

/**
* Sends command through SSH
*
* @param array $options Elements as follow:
* Site site Site being invoked
* string env_id Name of the environment being invoked
* string command Command to run remotely
* string server Server connection info
* @return array
*/
protected function sendCommand(array $options = [])
{
$this->log()->info(
sprintf('Running %s {cmd} on {site}-{env}', $this->command),
[
'cmd' => $options['command'],
'site' => $options['site']->get('name'),
'env' => $options['env_id'],
]
);
$server = $options['server'];

$cmd = 'ssh -T ' . $server['user'] . '@' . $server['host'] . ' -p '
. $server['port'] . ' -o "AddressFamily inet"' . " "
. escapeshellarg(
$this->command . ' ' . $options['command'] . ' '
);
$this->log()->debug(
'Command "{command}" is being run.',
['command' => escapeshellarg($cmd),]
);

ob_start();
passthru($cmd, $exit_code);
$result = ob_get_clean();
return $result;
}
}
18 changes: 9 additions & 9 deletions php/Terminus/Commands/DrushCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@
class DrushCommand extends CommandWithSSH
{
/**
* {@inheritdoc}
* @inheritdoc
*/
protected $client = 'Drush';

/**
* {@inheritdoc}
* @inheritdoc
*/
protected $command = 'drush';

/**
* {@inheritdoc}
* @inheritdoc
*/
protected $unavailable_commands = [
'sql-connect' => 'site connection-info --field=mysql_command',
Expand All @@ -40,11 +38,13 @@ class DrushCommand extends CommandWithSSH
*/
public function __invoke($args, $assoc_args)
{
$elements = $this->getElements($args, $assoc_args);
parent::__invoke($args, $assoc_args);
$command = $this->ssh_command;
if ($this->log()->getOptions('logFormat') != 'normal') {
$elements['command'] .= ' --pipe';
$command .= ' --pipe';
}
$result = $this->sendCommand($elements);
$this->output()->outputDump($result);
$result = $this->environment->sendCommandViaSsh($command);
$this->output()->outputDump($result['output']);
exit($result['exit_code']);
}
}
15 changes: 8 additions & 7 deletions php/Terminus/Commands/WpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
class WpCommand extends CommandWithSSH
{
/**
* {@inheritdoc}
* @inheritdoc
*/
protected $client = 'WP-CLI';

/**
* {@inheritdoc}
* @inheritdoc
*/
protected $command = 'wp';

/**
* {@inheritdoc}
* @inheritdoc
*/
protected $unavailable_commands = [
'db' => '',
Expand All @@ -27,7 +27,7 @@ class WpCommand extends CommandWithSSH
/**
* Invoke `wp` commands on a Pantheon development site
*
* <commands>...
* <commands>
* : The WP-CLI command you intend to run with its arguments, in quotes
*
* [--site=<site>]
Expand All @@ -39,8 +39,9 @@ class WpCommand extends CommandWithSSH
*/
public function __invoke($args, $assoc_args)
{
$elements = $this->getElements($args, $assoc_args);
$results = $this->sendCommand($elements);
$this->output()->outputDump($results);
parent::__invoke($args, $assoc_args);
$result = $this->environment->sendCommandViaSsh($this->ssh_command);
$this->output()->outputDump($result['output']);
exit($result['exit_code']);
}
}
Loading

0 comments on commit 03e10ab

Please sign in to comment.