From 25bb0f0729376a0ce9e1bf860beeee6106a40034 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Wed, 4 Nov 2026 19:09:31 +0100 Subject: [PATCH 1/2] Show the message Bref Cloud returns after removing an environment Bref Cloud now says where the environment's secrets are kept and how to delete them. The text comes from the API so that it can change without a CLI release. --- src/BrefCloudClient.php | 10 ++++-- src/Commands/Remove.php | 14 ++++++-- tests/Commands/CommandTestCase.php | 8 +++-- tests/Commands/RemoveTest.php | 53 ++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 tests/Commands/RemoveTest.php diff --git a/src/BrefCloudClient.php b/src/BrefCloudClient.php index 7fab979..ed79594 100644 --- a/src/BrefCloudClient.php +++ b/src/BrefCloudClient.php @@ -299,9 +299,15 @@ 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): ?string { - $this->client->request('DELETE', '/api/v1/environments/' . $environmentId); + /** @var array{message?: string|null} $result */ + $result = $this->client->request('DELETE', '/api/v1/environments/' . $environmentId)->toArray(); + + return $result['message'] ?? null; } /** diff --git a/src/Commands/Remove.php b/src/Commands/Remove.php index 2daa9cc..6e07132 100644 --- a/src/Commands/Remove.php +++ b/src/Commands/Remove.php @@ -12,6 +12,12 @@ class Remove extends ApplicationCommand { + public function __construct( + private readonly ?BrefCloudClient $brefCloud = null, + ) { + parent::__construct(); + } + protected function configure(): void { $this @@ -35,12 +41,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); while (true) { try { @@ -56,6 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int IO::spinSuccess('removed'); + if ($message) { + IO::writeln(['', $message]); + } + return 0; } } diff --git a/tests/Commands/CommandTestCase.php b/tests/Commands/CommandTestCase.php index d4819a0..e4ac6a9 100644 --- a/tests/Commands/CommandTestCase.php +++ b/tests/Commands/CommandTestCase.php @@ -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; @@ -39,7 +40,8 @@ protected function runByAnAgent(): void } /** - * @param array $routes Responses indexed by path, a MockResponse or data to return as JSON. + * @param array $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 { @@ -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); diff --git a/tests/Commands/RemoveTest.php b/tests/Commands/RemoveTest.php new file mode 100644 index 0000000..94e78aa --- /dev/null +++ b/tests/Commands/RemoveTest.php @@ -0,0 +1,53 @@ +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_bref_cloud_may_return_no_message(): void + { + [$status, $display] = $this->remove([]); + + $this->assertSame(0, $status, $display); + $this->assertStringContainsString('removed', $display); + } + + /** + * @param array $deletion What Bref Cloud returns when asked to remove the environment + * @return array{int, string} The exit code and the output + */ + private function remove(array $deletion): 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]); + $input->setInteractive(false); + $output = new BufferedOutput; + IO::init($input, $output); + + $status = $command->run($input, $output); + IO::stop(); + unlink($configFile); + + return [$status, $output->fetch()]; + } +} From 08d46ca4f015a58e5cbeedb1437c8da439b567ee Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Wed, 4 Nov 2026 20:18:43 +0100 Subject: [PATCH 2/2] Add bref remove --delete-secrets Secrets are kept by default so that deploying the environment again reuses them. An older Bref Cloud ignores the option, and its message then says the secrets are kept. --- src/BrefCloudClient.php | 6 ++++-- src/Commands/Remove.php | 6 ++++-- tests/Commands/RemoveTest.php | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/BrefCloudClient.php b/src/BrefCloudClient.php index ed79594..6873cea 100644 --- a/src/BrefCloudClient.php +++ b/src/BrefCloudClient.php @@ -302,10 +302,12 @@ public function findEnvironment(string $teamSlug, string $appName, string $envir /** * @return string|null A message from Bref Cloud to show once the environment is removed. */ - public function removeEnvironment(int $environmentId): ?string + public function removeEnvironment(int $environmentId, bool $deleteSecrets = false): ?string { /** @var array{message?: string|null} $result */ - $result = $this->client->request('DELETE', '/api/v1/environments/' . $environmentId)->toArray(); + $result = $this->client->request('DELETE', '/api/v1/environments/' . $environmentId, [ + 'json' => ['delete_secrets' => $deleteSecrets], + ])->toArray(); return $result['message'] ?? null; } diff --git a/src/Commands/Remove.php b/src/Commands/Remove.php index 6e07132..5a819a8 100644 --- a/src/Commands/Remove.php +++ b/src/Commands/Remove.php @@ -6,6 +6,7 @@ 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; @@ -22,7 +23,8 @@ 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(); } @@ -46,7 +48,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $environmentId = $environment['id']; IO::verbose('Triggering asynchronous removal of environment ' . $environmentId); - $message = $brefCloud->removeEnvironment($environmentId); + $message = $brefCloud->removeEnvironment($environmentId, (bool) $input->getOption('delete-secrets')); while (true) { try { diff --git a/tests/Commands/RemoveTest.php b/tests/Commands/RemoveTest.php index 94e78aa..5ac4377 100644 --- a/tests/Commands/RemoveTest.php +++ b/tests/Commands/RemoveTest.php @@ -17,6 +17,15 @@ public function test_shows_the_message_from_bref_cloud(): void $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([]); @@ -27,9 +36,10 @@ public function test_bref_cloud_may_return_no_message(): void /** * @param array $deletion What Bref Cloud returns when asked to remove the environment + * @param array $options Options of the command * @return array{int, string} The exit code and the output */ - private function remove(array $deletion): array + private function remove(array $deletion, array $options = []): array { $command = new Remove($this->brefCloud([ '/api/v1/environments/find' => $this->environment(), @@ -39,7 +49,7 @@ private function remove(array $deletion): array ])); $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]); + $input = new ArrayInput(['--env' => 'prod', '--config' => $configFile, ...$options]); $input->setInteractive(false); $output = new BufferedOutput; IO::init($input, $output);