Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add --all flag to remove all secrets in sandbox #2423

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .changeset/slow-yaks-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-cli': minor
---

feat: add --all flag to remove all secrets in sandbox
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -18,6 +22,9 @@ void describe('sandbox secret remove command', () => {
'removeSecret',
(): Promise<void> => Promise.resolve()
);
const listSecretsMock = mock.method(secretClient, 'listSecrets', () =>
Promise.resolve([{ name: testSecretName }])
);
const printMock = mock.method(printer, 'print');

const sandboxIdResolver: SandboxBackendIdResolver = {
Expand Down Expand Up @@ -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/
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -28,7 +29,7 @@ export class SandboxSecretRemoveCommand
private readonly sandboxIdResolver: SandboxBackendIdResolver,
private readonly secretClient: SecretClient
) {
this.command = 'remove <secret-name>';
this.command = 'remove [secret-name]';
this.describe = 'Remove a sandbox secret';
}

Expand All @@ -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<SecretRemoveCommandOptionsKebabCase> => {
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
>;