Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/BrefCloudClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,17 @@ public function findEnvironment(string $teamSlug, string $appName, string $envir
return $result;
}

public function removeEnvironment(int $environmentId): void
/**
* @return string|null A message from Bref Cloud to show once the environment is removed.
*/
public function removeEnvironment(int $environmentId, bool $deleteSecrets = false): ?string
{
$this->client->request('DELETE', '/api/v1/environments/' . $environmentId);
/** @var array{message?: string|null} $result */
$result = $this->client->request('DELETE', '/api/v1/environments/' . $environmentId, [
'json' => ['delete_secrets' => $deleteSecrets],
])->toArray();

return $result['message'] ?? null;
}

/**
Expand Down
18 changes: 15 additions & 3 deletions src/Commands/Remove.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,25 @@
use Bref\Cli\Cli\IO;
use Bref\Cli\Cli\Styles;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use function Amp\delay;

class Remove extends ApplicationCommand
{
public function __construct(
private readonly ?BrefCloudClient $brefCloud = null,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setName('remove')
->setDescription('Remove an environment of an application');
->setDescription('Remove an environment of an application')
->addOption('delete-secrets', null, InputOption::VALUE_NONE, 'Also delete the secrets of the environment (kept by default, so that deploying it again reuses them)');
parent::configure();
}

Expand All @@ -35,12 +43,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int

IO::spin('removing');

$brefCloud = new BrefCloudClient;
$brefCloud = $this->brefCloud ?? new BrefCloudClient;
$environment = $brefCloud->findEnvironment($team, $appName, $environmentName);
$environmentId = $environment['id'];

IO::verbose('Triggering asynchronous removal of environment ' . $environmentId);
$brefCloud->removeEnvironment($environmentId);
$message = $brefCloud->removeEnvironment($environmentId, (bool) $input->getOption('delete-secrets'));

while (true) {
try {
Expand All @@ -56,6 +64,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int

IO::spinSuccess('removed');

if ($message) {
IO::writeln(['', $message]);
}

return 0;
}
}
8 changes: 6 additions & 2 deletions tests/Commands/CommandTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bref\Cli\Test\Commands;

use Bref\Cli\BrefCloudClient;
use Closure;
use Laravel\AgentDetector\AgentDetector;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
Expand Down Expand Up @@ -39,7 +40,8 @@ protected function runByAnAgent(): void
}

/**
* @param array<string, mixed> $routes Responses indexed by path, a MockResponse or data to return as JSON.
* @param array<string, mixed> $routes Responses indexed by path, a MockResponse or data to return as JSON,
* or a closure that returns one of those for the HTTP method.
*/
protected function brefCloud(array $routes): BrefCloudClient
{
Expand All @@ -54,7 +56,9 @@ protected function brefCloud(array $routes): BrefCloudClient
$this->fail("Unexpected request: $method $url");
}

return $routes[$path] instanceof MockResponse ? $routes[$path] : $this->json($routes[$path]);
$response = $routes[$path] instanceof Closure ? $routes[$path]($method) : $routes[$path];

return $response instanceof MockResponse ? $response : $this->json($response);
}, 'https://bref.cloud');

return new BrefCloudClient(client: $client);
Expand Down
63 changes: 63 additions & 0 deletions tests/Commands/RemoveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);

namespace Bref\Cli\Test\Commands;

use Bref\Cli\Cli\IO;
use Bref\Cli\Commands\Remove;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;

class RemoveTest extends CommandTestCase
{
public function test_shows_the_message_from_bref_cloud(): void
{
[$status, $display] = $this->remove(['message' => 'Secrets of this environment are kept in SSM Parameter Store under /shop/prod/.']);

$this->assertSame(0, $status, $display);
$this->assertStringContainsString('Secrets of this environment are kept in SSM Parameter Store under /shop/prod/.', $display);
}

public function test_secrets_are_kept_unless_asked(): void
{
$this->remove([]);
$this->assertSame(['delete_secrets' => false], $this->requestBodies['/api/v1/environments/12']);

$this->remove([], ['--delete-secrets' => true]);
$this->assertSame(['delete_secrets' => true], $this->requestBodies['/api/v1/environments/12']);
}

public function test_bref_cloud_may_return_no_message(): void
{
[$status, $display] = $this->remove([]);

$this->assertSame(0, $status, $display);
$this->assertStringContainsString('removed', $display);
}

/**
* @param array<string, mixed> $deletion What Bref Cloud returns when asked to remove the environment
* @param array<string, mixed> $options Options of the command
* @return array{int, string} The exit code and the output
*/
private function remove(array $deletion, array $options = []): array
{
$command = new Remove($this->brefCloud([
'/api/v1/environments/find' => $this->environment(),
'/api/v1/environments/12' => fn(string $method) => $method === 'DELETE'
? $this->json($deletion, 202)
: $this->json(['message' => 'Not found'], 404),
]));
$configFile = sys_get_temp_dir() . '/bref-cli-test-' . bin2hex(random_bytes(4)) . '.yml';
file_put_contents($configFile, "service: shop\nbref:\n team: acme\n");
$input = new ArrayInput(['--env' => 'prod', '--config' => $configFile, ...$options]);
$input->setInteractive(false);
$output = new BufferedOutput;
IO::init($input, $output);

$status = $command->run($input, $output);
IO::stop();
unlink($configFile);

return [$status, $output->fetch()];
}
}
Loading