diff --git a/app/Commands/BaseCommand.php b/app/Commands/BaseCommand.php
index b5d1eff..4e79b9b 100644
--- a/app/Commands/BaseCommand.php
+++ b/app/Commands/BaseCommand.php
@@ -2,17 +2,17 @@
namespace App\Commands;
+use App\Commands\Concerns\InteractsWithIO;
use App\Helpers\Configuration;
use DeliciousBrains\SpinupWp\SpinupWp;
use Exception;
use GuzzleHttp\Client;
-use Illuminate\Support\Collection;
use LaravelZero\Framework\Commands\Command;
-use Symfony\Component\Console\Formatter\OutputFormatterStyle;
-use Symfony\Component\Console\Helper\Table;
abstract class BaseCommand extends Command
{
+ use InteractsWithIO;
+
protected Configuration $config;
protected SpinupWp $spinupwp;
@@ -35,7 +35,7 @@ public function handle(): int
{
if ($this->requiresToken && !$this->config->isConfigured()) {
$this->error("You must first run 'spinupwp configure' in order to set up your API token.");
- return 1;
+ return self::FAILURE;
}
try {
@@ -52,21 +52,21 @@ public function handle(): int
]));
}
- $this->format($this->action());
-
- return 0;
+ return $this->action();
} catch (Exception $e) {
$this->error($e->getMessage());
- return 1;
+ return self::FAILURE;
}
}
protected function apiToken(): string
{
$apiToken = $this->config->get('api_token', $this->profile());
+
if (!$apiToken) {
throw new Exception("The API token for the profile {$this->profile()} is not yet configured");
}
+
return $apiToken;
}
@@ -75,137 +75,9 @@ protected function profile(): string
if (is_string($this->option('profile'))) {
return $this->option('profile');
}
- return 'default';
- }
-
- protected function format($resource): void
- {
- if (empty($resource) || ($resource instanceof Collection && $resource->isEmpty())) {
- return;
- }
-
- $this->setStyles();
-
- if ($this->displayFormat() === 'table' && $this->largeOutput) {
- $this->largeOutput($resource);
- return;
- }
-
- if ($this->displayFormat() === 'table') {
- $this->toTable($resource);
- return;
- }
-
- $this->toJson($resource);
- }
-
- protected function setStyles(): void
- {
- if (!$this->output->getFormatter()->hasStyle('enabled')) {
- $this->output->getFormatter()->setStyle(
- 'enabled',
- new OutputFormatterStyle('green'),
- );
- }
-
- if (!$this->output->getFormatter()->hasStyle('disabled')) {
- $this->output->getFormatter()->setStyle(
- 'disabled',
- new OutputFormatterStyle('red'),
- );
- }
- }
-
- protected function displayFormat(): string
- {
- if (is_string($this->option('format'))) {
- return $this->option('format');
- }
- return $this->config->get('format', $this->profile());
- }
-
- protected function toJson($resource): void
- {
- if (!is_array($resource)) {
- $resource = $resource->toArray();
- }
- $this->line(json_encode($resource, JSON_PRETTY_PRINT));
- }
-
- protected function toTable($resource): void
- {
- $tableHeaders = [];
-
- if ($resource instanceof Collection) {
- $firstElement = $resource->first();
-
- if (!is_array($firstElement)) {
- $firstElement = $firstElement->toArray();
- }
-
- $tableHeaders = array_keys($firstElement);
-
- $rows = [];
- $resource->each(function ($item) use (&$rows) {
- if (!is_array($item)) {
- $item->toArray();
- }
-
- $row = array_map(function ($value) {
- if (is_array($value)) {
- $value = '';
- }
- if (is_bool($value)) {
- $value = $value ? 'Y' : 'N';
- }
- return $value;
- }, array_values($item));
-
- $rows[] = $row;
- });
- }
-
- $this->table(
- $tableHeaders,
- $rows,
- );
- }
-
- protected function largeOutput(array $resource): void
- {
- $table = new Table($this->output);
- $rows = [];
-
- foreach ($resource as $key => $value) {
- $rows[] = ['' . $key . '', $value];
- }
-
- $table->setRows($rows)->setStyle('default');
-
- if (!empty($this->columnsMaxWidths)) {
- foreach ($this->columnsMaxWidths as $column) {
- $table->setColumnMaxWidth($column[0], $column[1]);
- }
- }
-
- $table->render();
- }
-
- protected function formatBytes(int $bytes, int $precision = 1, bool $trueSize = false): string
- {
- $units = ['B', 'KB', 'MB', 'GB', 'TB'];
- $block = ($trueSize) ? 1024 : 1000;
-
- $bytes = max($bytes, 0);
- $pow = floor(($bytes ? log($bytes) : 0) / log($block));
- $pow = min($pow, count($units) - 1);
- $bytes /= pow($block, $pow);
-
- $total = ($trueSize || $precision > 0) ? round($bytes, $precision) : floor($bytes);
-
- return $total . ' ' . $units[$pow];
+ return 'default';
}
- abstract protected function action();
+ abstract protected function action(): int;
}
diff --git a/app/Commands/Concerns/InteractsWithIO.php b/app/Commands/Concerns/InteractsWithIO.php
new file mode 100644
index 0000000..fa86e8b
--- /dev/null
+++ b/app/Commands/Concerns/InteractsWithIO.php
@@ -0,0 +1,178 @@
+isEmpty())) {
+ return;
+ }
+
+ $this->setStyles();
+
+ if ($this->largeOutput && $this->displayFormat() === 'table') {
+ $this->largeOutput($resource);
+ return;
+ }
+
+ if ($this->displayFormat() === 'table') {
+ $this->toTable($resource);
+ return;
+ }
+
+ $this->toJson($resource);
+ }
+
+ protected function setStyles(): void
+ {
+ if (!$this->output->getFormatter()->hasStyle('enabled')) {
+ $this->output->getFormatter()->setStyle(
+ 'enabled',
+ new OutputFormatterStyle('green'),
+ );
+ }
+
+ if (!$this->output->getFormatter()->hasStyle('disabled')) {
+ $this->output->getFormatter()->setStyle(
+ 'disabled',
+ new OutputFormatterStyle('red'),
+ );
+ }
+ }
+
+ protected function displayFormat(): string
+ {
+ if (is_string($this->option('format'))) {
+ return $this->option('format');
+ }
+
+ return (string) $this->config->get('format', $this->profile());
+ }
+
+ /**
+ * @param mixed $resource
+ */
+ protected function toJson($resource): void
+ {
+ $this->line((string) json_encode($resource->toArray(), JSON_PRETTY_PRINT));
+ }
+
+ /**
+ * @param mixed $resource
+ */
+ protected function toTable($resource): void
+ {
+ $rows = [];
+ $tableHeaders = [];
+
+ if ($resource instanceof Collection) {
+ $firstElement = $resource->first();
+
+ if (!is_array($firstElement)) {
+ $firstElement = $firstElement->toArray();
+ }
+
+ $tableHeaders = array_keys($firstElement);
+
+ $resource->each(function ($item) use (&$rows) {
+ if (!is_array($item)) {
+ $item->toArray();
+ }
+
+ $row = array_map(function ($value) {
+ if (is_array($value)) {
+ $value = '';
+ }
+ if (is_bool($value)) {
+ $value = $value ? 'Y' : 'N';
+ }
+ return $value;
+ }, array_values($item));
+
+ $rows[] = $row;
+ });
+ }
+
+ $this->table($tableHeaders, $rows);
+ }
+
+ public function askToSelectSite(string $question): int
+ {
+ $choices = collect($this->spinupwp->sites->list());
+
+ return $this->askToSelect(
+ $question,
+ $choices->keyBy('id')->map(fn ($site) => $site->domain)->toArray()
+ );
+ }
+
+ public function askToSelectServer(string $question): int
+ {
+ $choices = collect($this->spinupwp->servers->list());
+
+ return $this->askToSelect(
+ $question,
+ $choices->keyBy('id')->map(fn ($server) => $server->name)->toArray()
+ );
+ }
+
+ /**
+ * @param mixed $default
+ */
+ protected function askToSelect(string $question, array $choices, $default = null): int
+ {
+ $question = new class($question, $choices, $default) extends ChoiceQuestion {
+ public function isAssoc(array $array): bool
+ {
+ return true;
+ }
+ };
+
+ return (int) $this->output->askQuestion($question);
+ }
+
+ protected function largeOutput(array $resource): void
+ {
+ $table = new Table($this->output);
+ $rows = [];
+
+ foreach ($resource as $key => $value) {
+ $rows[] = ['' . $key . '', $value];
+ }
+
+ $table->setRows($rows)->setStyle('default');
+
+ if (!empty($this->columnsMaxWidths)) {
+ foreach ($this->columnsMaxWidths as $column) {
+ $table->setColumnMaxWidth($column[0], $column[1]);
+ }
+ }
+
+ $table->render();
+ }
+
+ protected function formatBytes(int $bytes, int $precision = 1, bool $trueSize = false): string
+ {
+ $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ $block = ($trueSize) ? 1024 : 1000;
+
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log($block));
+ $pow = min($pow, count($units) - 1);
+ $bytes /= pow($block, $pow);
+
+ $total = ($trueSize || $precision > 0) ? round($bytes, $precision) : floor($bytes);
+
+ return $total . ' ' . $units[$pow];
+ }
+}
diff --git a/app/Commands/Concerns/InteractsWithRemote.php b/app/Commands/Concerns/InteractsWithRemote.php
new file mode 100644
index 0000000..b6ba71f
--- /dev/null
+++ b/app/Commands/Concerns/InteractsWithRemote.php
@@ -0,0 +1,23 @@
+ config('app.ssh_timeout'),
+ 'ControlMaster' => 'auto',
+ 'ControlPath' => $this->config->sshControlPath(),
+ 'ControlPersist' => 100,
+ 'LogLevel' => 'QUIET',
+ ])->map(function ($value, $option) {
+ return "-o $option=$value";
+ })->implode(' ');
+
+ passthru("ssh {$options} -t {$user}@{$host} -p {$port} '{$command}'", $exitCode);
+
+ return (int) $exitCode;
+ }
+}
diff --git a/app/Commands/ConfigureCommand.php b/app/Commands/ConfigureCommand.php
index 13e9ac1..baf17eb 100644
--- a/app/Commands/ConfigureCommand.php
+++ b/app/Commands/ConfigureCommand.php
@@ -19,16 +19,14 @@ public function handle(): int
}
if (!empty($this->config->get('api_token', $profile))) {
- $this->alert("A profile named {$profile} is already configured");
- $response = $this->ask('Do you want to overwrite the existing configuration? (y/n)', 'y');
+ $this->alert("A profile named \"{$profile}\" is already configured");
- while (!in_array($response, ['y', 'n'])) {
- $this->error("Please type 'y' or 'n'");
- $response = $this->ask('Do you want to overwrite the existing configuration? (y/n)', 'y');
- }
+ do {
+ $response = strtolower($this->ask('Do you want to overwrite the existing profile? (y/n)', 'y'));
+ } while (!in_array($response, ['y', 'n']));
if ($response === 'n') {
- return 0;
+ return self::SUCCESS;
}
}
@@ -41,18 +39,27 @@ public function handle(): int
$defaultFormat = null;
while (!in_array($defaultFormat, config('app.output_formats'))) {
- $defaultFormat = $this->ask('Default output format (json/table)', null);
+ $defaultFormat = $this->anticipate('Default output format (json/table)', [
+ 'json',
+ 'table',
+ ], 'table');
}
$this->config->set('api_token', $apiKey, $profile);
$this->config->set('format', $defaultFormat, $profile);
- $this->info('SpinupWP CLI configured successfully');
- return 0;
+ $this->info('Profile configured successfully.');
+
+ if ($profile !== 'default') {
+ $this->line('To use this profile, add the --profile option to your command:');
+ $this->line("`spinupwp servers:list --profile={$profile}`");
+ }
+
+ return self::SUCCESS;
}
- protected function action()
+ protected function action(): int
{
- return null;
+ return self::INVALID;
}
}
diff --git a/app/Commands/Servers/GetCommand.php b/app/Commands/Servers/GetCommand.php
index ba839ff..b68ec05 100644
--- a/app/Commands/Servers/GetCommand.php
+++ b/app/Commands/Servers/GetCommand.php
@@ -6,46 +6,50 @@
class GetCommand extends BaseCommand
{
- protected $signature = 'servers:get {server_id} {--format=} {--profile=}';
+ protected $signature = 'servers:get
+ {server_id : The server to output}
+ {--format=}
+ {--profile=}';
protected $description = 'Get a server';
protected bool $largeOutput = true;
- public function action()
+ public function action(): int
{
$this->columnsMaxWidths[] = [1, 50];
$serverId = $this->argument('server_id');
-
- $server = $this->spinupwp->servers->get($serverId);
-
- if ($this->displayFormat() === 'json') {
- return $server;
+ $server = $this->spinupwp->servers->get((int) $serverId);
+
+ if ($this->displayFormat() === 'table') {
+ $server = [
+ 'ID' => $server->id,
+ 'Name' => $server->name,
+ 'Provider Name' => $server->provider_name,
+ 'IP Address' => $server->ip_address,
+ 'SSH Port' => $server->ssh_port,
+ 'Ubuntu' => $server->ubuntu_version,
+ 'Timezone' => $server->timezone,
+ 'Region' => $server->region,
+ 'Size' => $server->size,
+ 'Disk Space' => $this->formatBytes($server->disk_space['used']) . ' of ' . $this->formatBytes($server->disk_space['total'], 0) . ' used',
+ 'Database Server' => $server->database['server'],
+ 'Database Host' => $server->database['host'],
+ 'Database Port' => $server->database['port'],
+ 'SSH Public Key' => $server->ssh_publickey,
+ 'Git Public Key' => $server->git_publickey,
+ 'Connection Status' => ucfirst($server->connection_status),
+ 'Reboot Required' => $server->reboot_required ? 'Yes' : 'No',
+ 'Upgrade Required' => $server->upgrade_required ? 'Yes' : 'No',
+ 'Install Notes' => $server->install_notes ?? '',
+ 'Created At' => $server->created_at,
+ 'Status' => ucfirst($server->status),
+ ];
}
- return [
- 'ID' => $server->id,
- 'Name' => $server->name,
- 'Provider Name' => $server->provider_name,
- 'IP Address' => $server->ip_address,
- 'SSH Port' => $server->ssh_port,
- 'Ubuntu' => $server->ubuntu_version,
- 'Timezone' => $server->timezone,
- 'Region' => $server->region,
- 'Size' => $server->size,
- 'Disk Space' => $this->formatBytes($server->disk_space['used']) . ' of ' . $this->formatBytes($server->disk_space['total'], 0) . ' used',
- 'Database Server' => $server->database['server'],
- 'Database Host' => $server->database['host'],
- 'Database Port' => $server->database['port'],
- 'SSH Public Key' => $server->ssh_publickey,
- 'Git Public Key' => $server->git_publickey,
- 'Connection Status' => ucfirst($server->connection_status),
- 'Reboot Required' => $server->reboot_required ? 'Yes' : 'No',
- 'Upgrade Required' => $server->upgrade_required ? 'Yes' : 'No',
- 'Install Notes' => $server->install_notes ?? '',
- 'Created At' => $server->created_at,
- 'Status' => ucfirst($server->status),
- ];
+ $this->format($server);
+
+ return self::SUCCESS;
}
}
diff --git a/app/Commands/Servers/ListCommand.php b/app/Commands/Servers/ListCommand.php
index a8a4b7b..cc30771 100644
--- a/app/Commands/Servers/ListCommand.php
+++ b/app/Commands/Servers/ListCommand.php
@@ -6,29 +6,33 @@
class ListCommand extends BaseCommand
{
- protected $signature = 'servers:list {--format=} {--profile=}';
+ protected $signature = 'servers:list
+ {--format=}
+ {--profile=}';
- protected $description = 'Retrieves a list of servers';
+ protected $description = 'List all servers';
- protected function action()
+ protected function action(): int
{
$servers = collect($this->spinupwp->servers->list());
if ($servers->isEmpty()) {
$this->warn('No servers found.');
- return $servers;
+ return self::SUCCESS;
}
- if ($this->displayFormat() === 'json') {
- return $servers;
+ if ($this->displayFormat() === 'table') {
+ $servers->transform(fn ($server) => [
+ 'ID' => $server->id,
+ 'Name' => $server->name,
+ 'IP Address' => $server->ip_address,
+ 'Ubuntu' => $server->ubuntu_version,
+ 'Database' => $server->database['server'],
+ ]);
}
- return $servers->map(fn ($server) => [
- 'ID' => $server->id,
- 'Name' => $server->name,
- 'IP Address' => $server->ip_address,
- 'Ubuntu' => $server->ubuntu_version,
- 'Database' => $server->database['server'],
- ]);
+ $this->format($servers);
+
+ return self::SUCCESS;
}
}
diff --git a/app/Commands/Servers/SshCommand.php b/app/Commands/Servers/SshCommand.php
new file mode 100644
index 0000000..8526f1d
--- /dev/null
+++ b/app/Commands/Servers/SshCommand.php
@@ -0,0 +1,80 @@
+argument('server_id');
+
+ if (empty($serverId)) {
+ $serverId = $this->askToSelectServer('Which server would you like to start an SSH session for');
+ }
+
+ $server = $this->spinupwp->servers->get((int) $serverId);
+ $user = $this->establishUser();
+
+ $this->line("Establishing a secure connection to [{$server->name}] as [{$user}]...");
+
+ $exitCode = $this->ssh(
+ $user,
+ $server->ip_address,
+ $server->ssh_port,
+ );
+
+ if ($exitCode === 255) {
+ $this->error("Unable to connect to \"{$server->name}\". Have you added your SSH key to the \"{$user}\" user?");
+ }
+
+ return $exitCode;
+ }
+
+ protected function establishUser(): string
+ {
+ $user = $this->argument('user');
+ $defaultUser = $this->config->get('ssh_user', $this->profile(), null);
+
+ if (is_string($user)) {
+ if (is_null($defaultUser)) {
+ $this->askToSetDefaultUser($user);
+ }
+
+ return $user;
+ }
+
+ if (!empty($defaultUser)) {
+ return $defaultUser;
+ }
+
+ $user = $this->ask('Which user would you like to connect as');
+
+ if (is_null($defaultUser)) {
+ $this->askToSetDefaultUser($user);
+ }
+
+ return $user;
+ }
+
+ protected function askToSetDefaultUser(string $user): void
+ {
+ do {
+ $response = strtolower($this->ask("Do you want to set \"{$user}\" as your default SSH user? (y/n)", 'y'));
+ } while (!in_array($response, ['y', 'n']));
+
+ $value = $response === 'y' ? $user : '';
+ $this->config->set('ssh_user', $value, $this->profile());
+ }
+}
diff --git a/app/Commands/Sites/GetCommand.php b/app/Commands/Sites/GetCommand.php
index f10e718..055a850 100644
--- a/app/Commands/Sites/GetCommand.php
+++ b/app/Commands/Sites/GetCommand.php
@@ -7,19 +7,24 @@
class GetCommand extends BaseCommand
{
- protected $signature = 'sites:get {site_id} {--format=} {--profile=}';
+ protected $signature = 'sites:get
+ {site_id : The site to output}
+ {--format=}
+ {--profile=}';
protected $description = 'Get a site';
protected bool $largeOutput = true;
- public function action()
+ public function action(): int
{
- $site = $this->spinupwp->sites->get($this->argument('site_id'));
+ $site = $this->spinupwp->sites->get((int) $this->argument('site_id'));
if ($this->displayFormat() === 'json') {
- return $site;
+ $this->toJson($site);
+ return self::SUCCESS;
}
+
$additionalDomains = '';
if (!empty($site->additional_domains)) {
@@ -59,7 +64,9 @@ public function action()
$data['Created At'] = $site->created_at;
$data['Status'] = ucfirst($site->status);
- return $data;
+ $this->format($data);
+
+ return self::SUCCESS;
}
public function backupsData(Site $site, array $data): array
diff --git a/app/Commands/Sites/ListCommand.php b/app/Commands/Sites/ListCommand.php
index c957dee..484a706 100644
--- a/app/Commands/Sites/ListCommand.php
+++ b/app/Commands/Sites/ListCommand.php
@@ -6,11 +6,14 @@
class ListCommand extends BaseCommand
{
- protected $signature = 'sites:list {server_id? : Only list sites belonging to this server} {--format=} {--profile=}';
+ protected $signature = 'sites:list
+ {server_id? : Only list sites belonging to this server}
+ {--format=}
+ {--profile=}';
- protected $description = 'Retrieves a list of sites';
+ protected $description = 'List all sites';
- protected function action()
+ protected function action(): int
{
$serverId = $this->argument('server_id');
@@ -22,21 +25,23 @@ protected function action()
if ($sites->isEmpty()) {
$this->warn('No sites found.');
- return $sites;
+ return self::SUCCESS;
}
- if ($this->displayFormat() === 'json') {
- return $sites;
+ if ($this->displayFormat() === 'table') {
+ $sites->transform(fn ($site) => [
+ 'ID' => $site->id,
+ 'Server ID' => $site->server_id,
+ 'Domain' => $site->domain,
+ 'Site User' => $site->site_user,
+ 'PHP' => $site->php_version,
+ 'Page Cache' => $site->page_cache['enabled'],
+ 'HTTPS' => $site->https['enabled'],
+ ]);
}
- return $sites->map(fn ($site) => [
- 'ID' => $site->id,
- 'Server ID' => $site->server_id,
- 'Domain' => $site->domain,
- 'Site User' => $site->site_user,
- 'PHP' => $site->php_version,
- 'Page Cache' => $site->page_cache['enabled'],
- 'HTTPS' => $site->https['enabled'],
- ]);
+ $this->format($sites);
+
+ return self::SUCCESS;
}
}
diff --git a/app/Commands/Sites/SshCommand.php b/app/Commands/Sites/SshCommand.php
new file mode 100644
index 0000000..2e653fa
--- /dev/null
+++ b/app/Commands/Sites/SshCommand.php
@@ -0,0 +1,61 @@
+argument('site_id');
+
+ if (empty($siteId)) {
+ $siteId = $this->askToSelectSite('Which site would you like to start an SSH session for');
+ }
+
+ $site = $this->spinupwp->sites->get((int) $siteId);
+ $server = $this->spinupwp->servers->get($site->server_id);
+
+ $this->line("Establishing a secure connection to [{$server->name}] as [{$site->site_user}]...");
+
+ $exitCode = $this->ssh(
+ $site->site_user,
+ $server->ip_address,
+ $server->ssh_port,
+ $this->cdCommand(),
+ );
+
+ if ($exitCode === 255) {
+ $this->error("Unable to connect to \"{$server->name}\". Have you added your SSH key to the \"{$site->site_user}\" user?");
+ }
+
+ return $exitCode;
+ }
+
+ protected function cdCommand(): string
+ {
+ $cdCommand = '';
+ $cdFlags = ['files', 'logs'];
+
+ foreach ($cdFlags as $flag) {
+ if ($this->option($flag)) {
+ $cdCommand = "cd ./{$flag}; bash --login";
+ break;
+ }
+ }
+
+ return $cdCommand;
+ }
+}
diff --git a/app/Helpers/Configuration.php b/app/Helpers/Configuration.php
index 621f812..0d034d1 100644
--- a/app/Helpers/Configuration.php
+++ b/app/Helpers/Configuration.php
@@ -3,6 +3,7 @@
namespace App\Helpers;
use Illuminate\Support\Arr;
+use Illuminate\Support\Facades\File;
class Configuration
{
@@ -21,7 +22,7 @@ public function isConfigured(): bool
return file_exists($this->configFilePath());
}
- public function get(string $key, string $profile = 'default', string $default = ''): string
+ public function get(string $key, string $profile = 'default', ?string $default = ''): ?string
{
$this->config = $this->readConfig();
if (empty($this->config)) {
@@ -74,10 +75,21 @@ protected function readConfig(): array
public function configFilePath(): string
{
- if (!file_exists($this->path)) {
- mkdir($this->path);
+ if (!File::isDirectory($this->path)) {
+ File::makeDirectory($this->path);
}
return $this->path . 'config.json';
}
+
+ public function sshControlPath(): string
+ {
+ $sshPath = $this->path . 'ssh/';
+
+ if (!File::isDirectory($sshPath)) {
+ File::makeDirectory($sshPath);
+ }
+
+ return $sshPath . '%h-%p-%r';
+ }
}
diff --git a/config/app.php b/config/app.php
index 4994197..a2bdd8b 100644
--- a/config/app.php
+++ b/config/app.php
@@ -62,4 +62,6 @@
'table',
],
+ 'ssh_timeout' => 5,
+
];
diff --git a/tests/Feature/Commands/ConfigureCommandTest.php b/tests/Feature/Commands/ConfigureCommandTest.php
index 6f9103d..a6edfbd 100644
--- a/tests/Feature/Commands/ConfigureCommandTest.php
+++ b/tests/Feature/Commands/ConfigureCommandTest.php
@@ -13,7 +13,7 @@
$this->artisan('configure')
->expectsQuestion('SpinupWP API token', 'my-spinupwp-api-token')
->expectsQuestion('Default output format (json/table)', 'json')
- ->expectsOutput('SpinupWP CLI configured successfully');
+ ->expectsOutput('Profile configured successfully.');
expect((resolve(Configuration::class))->get('api_token'))->toEqual('my-spinupwp-api-token');
});
diff --git a/tests/Feature/Commands/ServersSshCommandTest.php b/tests/Feature/Commands/ServersSshCommandTest.php
new file mode 100644
index 0000000..4424464
--- /dev/null
+++ b/tests/Feature/Commands/ServersSshCommandTest.php
@@ -0,0 +1,108 @@
+clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('servers:ssh 1 johndoe')
+ ->expectsQuestion('Do you want to set "johndoe" as your default SSH user? (y/n)', 'y')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [johndoe]...')
+ ->assertExitCode(255);
+});
+
+test('ssh command with server ID supplied and no SSH user', function () {
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('servers:ssh 1')
+ ->expectsQuestion('Which user would you like to connect as', 'johndoe')
+ ->expectsQuestion('Do you want to set "johndoe" as your default SSH user? (y/n)', 'y')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [johndoe]...')
+ ->assertExitCode(255);
+});
+
+test('ssh command with no server ID supplied', function () {
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers?page=1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ],
+ 'pagination' => [
+ 'previous' => null,
+ 'next' => null,
+ 'count' => 1,
+ ],
+ ]))
+ );
+
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('servers:ssh')
+ ->expectsQuestion('Which server would you like to start an SSH session for', '1')
+ ->expectsQuestion('Which user would you like to connect as', 'johndoe')
+ ->expectsQuestion('Do you want to set "johndoe" as your default SSH user? (y/n)', 'y')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [johndoe]...')
+ ->assertExitCode(255);
+});
+
+test('ssh command with server ID supplied and default SSH user', function () {
+ setTestConfigFile([
+ 'ssh_user' => 'janedoe',
+ ]);
+
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('servers:ssh 1')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [janedoe]...')
+ ->assertExitCode(255);
+});
diff --git a/tests/Feature/Commands/SitesSshCommandTest.php b/tests/Feature/Commands/SitesSshCommandTest.php
new file mode 100644
index 0000000..0ed939b
--- /dev/null
+++ b/tests/Feature/Commands/SitesSshCommandTest.php
@@ -0,0 +1,86 @@
+clientMock->shouldReceive('request')->once()->with('GET', 'sites/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'server_id' => 1,
+ 'domain' => 'hellfishmedia.com',
+ 'site_user' => 'hellfish',
+ ],
+ ]))
+ );
+
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('sites:ssh 1 --profile=johndoe')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [hellfish]...')
+ ->assertExitCode(255);
+});
+
+test('ssh command with no site ID', function () {
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'sites?page=1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ [
+ 'id' => 1,
+ 'server_id' => 1,
+ 'domain' => 'hellfishmedia.com',
+ 'site_user' => 'hellfish',
+ ],
+ ],
+ 'pagination' => [
+ 'previous' => null,
+ 'next' => null,
+ 'count' => 1,
+ ],
+ ]))
+ );
+
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'sites/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'server_id' => 1,
+ 'domain' => 'hellfishmedia.com',
+ 'site_user' => 'hellfish',
+ ],
+ ]))
+ );
+
+ $this->clientMock->shouldReceive('request')->once()->with('GET', 'servers/1', [])->andReturn(
+ new Response(200, [], json_encode([
+ 'data' => [
+ 'id' => 1,
+ 'name' => 'hellfishmedia',
+ 'ip_address' => '123.123.123.123',
+ 'ssh_port' => 22,
+ ],
+ ]))
+ );
+
+ $this->artisan('sites:ssh --profile=johndoe')
+ ->expectsQuestion('Which site would you like to start an SSH session for', '1')
+ ->expectsOutput('Establishing a secure connection to [hellfishmedia] as [hellfish]...')
+ ->assertExitCode(255);
+});
diff --git a/tests/Pest.php b/tests/Pest.php
index e82e738..9af839a 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -22,6 +22,7 @@
->beforeEach(function () {
$this->clientMock = Mockery::mock(Client::class);
$this->spinupwp = resolve(SpinupWp::class)->setClient($this->clientMock)->setApiKey('123');
+ config()->set('app.ssh_timeout', -1);
})
->in('Feature', 'Unit');
@@ -51,14 +52,14 @@
|
*/
-function setTestConfigFile()
+function setTestConfigFile($profileData = [])
{
$config = resolve(Configuration::class);
file_put_contents($config->configFilePath(), json_encode([
- 'default' => [
+ 'default' => array_merge([
'api_token' => 'myapikey123',
'format' => 'json',
- ],
+ ], $profileData),
], JSON_PRETTY_PRINT));
}