From 1be27e1b121f08d00ee3f0c97837bcb313c903a1 Mon Sep 17 00:00:00 2001 From: MURAKAMI Masahiko Date: Tue, 14 Jan 2025 22:02:36 +0900 Subject: [PATCH] feat: enhance sandbox secret removal command to support removing all secrets --- .changeset/slow-yaks-matter.md | 5 ++ .../sandbox_secret_remove_command.test.ts | 58 +++++++++++++++++-- .../sandbox_secret_remove_command.ts | 52 ++++++++++++----- 3 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 .changeset/slow-yaks-matter.md diff --git a/.changeset/slow-yaks-matter.md b/.changeset/slow-yaks-matter.md new file mode 100644 index 00000000000..0382dafe8fc --- /dev/null +++ b/.changeset/slow-yaks-matter.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-cli': minor +--- + +feat: add --all flag to remove all secrets in sandbox diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.test.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.test.ts index d5e20df3a45..dd6f89e6472 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.test.ts @@ -1,11 +1,15 @@ import { beforeEach, describe, it, mock } from 'node:test'; import yargs, { CommandModule } from 'yargs'; -import { TestCommandRunner } from '../../../test-utils/command_runner.js'; +import { + TestCommandError, + TestCommandRunner, +} from '../../../test-utils/command_runner.js'; import assert from 'node:assert'; import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; import { getSecretClientWithAmplifyErrorHandling } from '@aws-amplify/backend-secret'; import { SandboxSecretRemoveCommand } from './sandbox_secret_remove_command.js'; import { printer } from '@aws-amplify/cli-core'; +import { AmplifyError } from '@aws-amplify/platform-core'; const testSecretName = 'testSecretName'; const testBackendId = 'testBackendId'; @@ -18,6 +22,9 @@ void describe('sandbox secret remove command', () => { 'removeSecret', (): Promise => Promise.resolve() ); + const listSecretsMock = mock.method(secretClient, 'listSecrets', () => + Promise.resolve([{ name: testSecretName }]) + ); const printMock = mock.method(printer, 'print'); const sandboxIdResolver: SandboxBackendIdResolver = { @@ -77,13 +84,56 @@ void describe('sandbox secret remove command', () => { ]); }); + void it('remove all secrets', async () => { + await commandRunner.runCommand('remove --all'); + assert.equal(listSecretsMock.mock.callCount(), 1); + assert.deepStrictEqual(listSecretsMock.mock.calls[0].arguments, [ + { + type: 'sandbox', + namespace: testBackendId, + name: testSandboxName, + }, + ]); + + assert.equal(secretRemoveMock.mock.callCount(), 1); + assert.deepStrictEqual(secretRemoveMock.mock.calls[0].arguments, [ + { + type: 'sandbox', + namespace: testBackendId, + name: testSandboxName, + }, + testSecretName, + ]); + assert.equal( + printMock.mock.calls[0].arguments, + 'Successfully removed all secrets' + ); + }); + void it('show --help', async () => { const output = await commandRunner.runCommand('remove --help'); assert.match(output, /Remove a sandbox secret/); }); - void it('throws error if no secret name argument', async () => { - const output = await commandRunner.runCommand('remove'); - assert.match(output, /Not enough non-option arguments/); + void it('throws error if no secret name argument and all flag', async () => { + await assert.rejects( + () => commandRunner.runCommand('remove'), + (err: TestCommandError) => { + assert.ok(AmplifyError.isAmplifyError(err.error)); + assert.strictEqual( + err.error.message, + 'Either secret-name or all flag must be provided' + ); + assert.strictEqual(err.error.name, 'InvalidCommandInputError'); + return true; + } + ); + }); + + void it('throws error if both --all flag and secret-name argument', async () => { + assert.match( + await commandRunner.runCommand(`remove ${testSecretName} --all`), + /Arguments all and secret-name are mutually exclusive/ + ); }); }); diff --git a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.ts b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.ts index 73974f96b19..4a714b1ceba 100644 --- a/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.ts +++ b/packages/cli/src/commands/sandbox/sandbox-secret/sandbox_secret_remove_command.ts @@ -4,6 +4,7 @@ import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js'; import { ArgumentsKebabCase } from '../../../kebab_case.js'; import { SandboxCommandGlobalOptions } from '../option_types.js'; import { printer } from '@aws-amplify/cli-core'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; /** * Command to remove sandbox secret. @@ -28,7 +29,7 @@ export class SandboxSecretRemoveCommand private readonly sandboxIdResolver: SandboxBackendIdResolver, private readonly secretClient: SecretClient ) { - this.command = 'remove '; + this.command = 'remove [secret-name]'; this.describe = 'Remove a sandbox secret'; } @@ -41,28 +42,53 @@ export class SandboxSecretRemoveCommand const sandboxBackendIdentifier = await this.sandboxIdResolver.resolve( args.identifier ); - await this.secretClient.removeSecret( - sandboxBackendIdentifier, - args.secretName - ); - - printer.print(`Successfully removed secret ${args.secretName}`); + if (args.secretName) { + await this.secretClient.removeSecret( + sandboxBackendIdentifier, + args.secretName + ); + printer.print(`Successfully removed secret ${args.secretName}`); + } else if (args.all) { + const secrets = await this.secretClient.listSecrets( + sandboxBackendIdentifier + ); + await Promise.all( + secrets.map((secret) => + this.secretClient.removeSecret(sandboxBackendIdentifier, secret.name) + ) + ); + printer.print('Successfully removed all secrets'); + } }; /** * @inheritDoc */ builder = (yargs: Argv): Argv => { - return yargs.positional('secret-name', { - describe: 'Name of the secret to remove', - type: 'string', - demandOption: true, - }); + return yargs + .option('all', { + describe: 'Remove all secrets', + type: 'boolean', + conflicts: ['secret-name'], + }) + .check(async (argv) => { + if (!argv.all && !argv['secret-name']) { + throw new AmplifyUserError('InvalidCommandInputError', { + message: 'Either secret-name or all flag must be provided', + resolution: 'Provide either secret-name or all flag', + }); + } + return true; + }); }; } type SecretRemoveCommandOptionsKebabCase = ArgumentsKebabCase< { - secretName: string; + secretName?: string; + /** + * Optional flag to remove all secrets. + */ + all?: boolean; } & SandboxCommandGlobalOptions >;