From 1c9a410e768a35b1b83a152fe923c70042623d0c Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Fri, 12 Jul 2024 08:00:37 -0500 Subject: [PATCH] Add "delete secret" command (so I can clean up mispelled keys) --- apps/cli/src/cli-controller/secrets.ts | 9 ++++++ .../src/commands/delete-secret.test.ts | 30 +++++++++++++++++++ .../secrets/src/commands/delete-secret.ts | 5 ++++ packages/secrets/src/commands/index.ts | 1 + .../src/lib/adapters/aws-param-store.ts | 23 +++++++++++--- .../secrets/src/lib/adapters/in-memory.ts | 4 +++ packages/secrets/src/lib/types.ts | 1 + 7 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 packages/secrets/src/commands/delete-secret.test.ts create mode 100644 packages/secrets/src/commands/delete-secret.ts diff --git a/apps/cli/src/cli-controller/secrets.ts b/apps/cli/src/cli-controller/secrets.ts index abb01182..621b6666 100644 --- a/apps/cli/src/cli-controller/secrets.ts +++ b/apps/cli/src/cli-controller/secrets.ts @@ -13,6 +13,15 @@ export const addSecretCommands = (ctx: Context, cli: Command) => { }) .description('secrets management commands'); + cmd + .command('delete') + .description('delete a secret') + .argument('', 'secret key name') + .action(async (key: string) => { + const vault = await getSecretsVault(ctx.file); + await commands.deleteSecret(vault, key); + }); + cmd .command('get') .description('get a secret value') diff --git a/packages/secrets/src/commands/delete-secret.test.ts b/packages/secrets/src/commands/delete-secret.test.ts new file mode 100644 index 00000000..fe8f39ed --- /dev/null +++ b/packages/secrets/src/commands/delete-secret.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; + +import { createInMemorySecretsVault } from '../lib'; +import { getSecretKeyList } from './get-secret-key-list'; +import { deleteSecret } from './delete-secret'; + +const getTestVault = (vaultData: any) => { + const result = createInMemorySecretsVault(JSON.stringify(vaultData)); + if (result.success) { + return result.data; + } else { + throw new Error('Error creating in-memory test vault'); + } +}; + +describe('delete-secret command', () => { + it('removes key', async () => { + const vault = getTestVault({ + 'secret-key-1': 'value-1', + }); + await deleteSecret(vault, 'secret-key-1'); + expect(await vault.getSecretKeys()).toEqual([]); + }); + + it('silently handles non-existent keys', async () => { + const vault = getTestVault({}); + await deleteSecret(vault, 'secret-key-1'); + expect(await vault.getSecretKeys()).toEqual([]); + }); +}); diff --git a/packages/secrets/src/commands/delete-secret.ts b/packages/secrets/src/commands/delete-secret.ts new file mode 100644 index 00000000..1de128f7 --- /dev/null +++ b/packages/secrets/src/commands/delete-secret.ts @@ -0,0 +1,5 @@ +import type { SecretKey, SecretsVault } from '../lib/types'; + +export const deleteSecret = async (vault: SecretsVault, key: SecretKey) => { + return await vault.deleteSecret(key); +}; diff --git a/packages/secrets/src/commands/index.ts b/packages/secrets/src/commands/index.ts index 9c81c81d..d6fe8e09 100644 --- a/packages/secrets/src/commands/index.ts +++ b/packages/secrets/src/commands/index.ts @@ -1,3 +1,4 @@ +export { deleteSecret } from './delete-secret'; export { getSecret } from './get-secret'; export { getSecrets } from './get-secrets'; export { getSecretKeyList } from './get-secret-key-list'; diff --git a/packages/secrets/src/lib/adapters/aws-param-store.ts b/packages/secrets/src/lib/adapters/aws-param-store.ts index 3b1d4fca..769dd6a8 100644 --- a/packages/secrets/src/lib/adapters/aws-param-store.ts +++ b/packages/secrets/src/lib/adapters/aws-param-store.ts @@ -1,16 +1,31 @@ import { - SSMClient, - GetParameterCommand, - GetParametersCommand, - PutParameterCommand, + DeleteParameterCommand, DescribeParametersCommand, DescribeParametersCommandOutput, + GetParameterCommand, + GetParametersCommand, ParameterNotFound, + PutParameterCommand, + SSMClient, } from '@aws-sdk/client-ssm'; import type { SecretKey, SecretMap, SecretValue, SecretsVault } from '../types'; export class AWSParameterStoreSecretsVault implements SecretsVault { + async deleteSecret(key: SecretKey) { + const client = new SSMClient(); + try { + await client.send( + new DeleteParameterCommand({ + Name: key, + }) + ); + console.log(`Secret "${key}" deleted successfully.`); + } catch (error) { + console.warn('Skipped deleting parameter due to error:', error); + } + } + async getSecret(key: SecretKey) { const client = new SSMClient(); diff --git a/packages/secrets/src/lib/adapters/in-memory.ts b/packages/secrets/src/lib/adapters/in-memory.ts index c6696a5f..6fd9e57a 100644 --- a/packages/secrets/src/lib/adapters/in-memory.ts +++ b/packages/secrets/src/lib/adapters/in-memory.ts @@ -3,6 +3,10 @@ import type { SecretMap, SecretsVault } from '../types'; export class InMemorySecretsVault implements SecretsVault { constructor(private secretMap: SecretMap) {} + async deleteSecret(key: string) { + delete this.secretMap[key]; + } + async getSecret(key: string) { return this.secretMap[key]; } diff --git a/packages/secrets/src/lib/types.ts b/packages/secrets/src/lib/types.ts index 9fe46528..9c3c956a 100644 --- a/packages/secrets/src/lib/types.ts +++ b/packages/secrets/src/lib/types.ts @@ -26,6 +26,7 @@ export const getSecretMapFromJsonString = ( }; export interface SecretsVault { + deleteSecret(key: SecretKey): Promise; getSecret(key: SecretKey): Promise; getSecrets(keys: SecretKey[]): Promise; setSecret(key: SecretKey, value: SecretValue): Promise;