-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(parameters): SecretsProvider support (#1206)
* wip: SecretsProvider * feat: SecretsProvider * refactor: types & auto-transform single * test: unit tests * Update packages/parameters/src/BaseProvider.ts * refactor: readability
- Loading branch information
1 parent
6a37b70
commit 02516b7
Showing
11 changed files
with
966 additions
and
12 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { BaseProvider } from '../BaseProvider'; | ||
import { | ||
SecretsManagerClient, | ||
GetSecretValueCommand | ||
} from '@aws-sdk/client-secrets-manager'; | ||
import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; | ||
import type { | ||
SecretsProviderOptions, | ||
SecretsGetOptionsInterface | ||
} from '../types/SecretsProvider'; | ||
|
||
class SecretsProvider extends BaseProvider { | ||
public client: SecretsManagerClient; | ||
|
||
public constructor (config?: SecretsProviderOptions) { | ||
super(); | ||
|
||
const clientConfig = config?.clientConfig || {}; | ||
this.client = new SecretsManagerClient(clientConfig); | ||
} | ||
|
||
public async get( | ||
name: string, | ||
options?: SecretsGetOptionsInterface | ||
): Promise<undefined | string | Uint8Array | Record<string, unknown>> { | ||
return super.get(name, options); | ||
} | ||
|
||
protected async _get( | ||
name: string, | ||
options?: SecretsGetOptionsInterface | ||
): Promise<string | Uint8Array | undefined> { | ||
const sdkOptions: GetSecretValueCommandInput = { | ||
...(options?.sdkOptions || {}), | ||
SecretId: name, | ||
}; | ||
|
||
const result = await this.client.send(new GetSecretValueCommand(sdkOptions)); | ||
|
||
if (result.SecretString) return result.SecretString; | ||
|
||
return result.SecretBinary; | ||
} | ||
|
||
/** | ||
* Retrieving multiple parameter values is not supported with AWS Secrets Manager. | ||
*/ | ||
protected async _getMultiple( | ||
_path: string, | ||
_options?: unknown | ||
): Promise<Record<string, string | undefined>> { | ||
throw new Error('Method not implemented.'); | ||
} | ||
} | ||
|
||
export { | ||
SecretsProvider, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { DEFAULT_PROVIDERS } from '../BaseProvider'; | ||
import { SecretsProvider } from './SecretsProvider'; | ||
import type { SecretsGetOptionsInterface } from '../types/SecretsProvider'; | ||
|
||
const getSecret = async (name: string, options?: SecretsGetOptionsInterface): Promise<undefined | string | Uint8Array | Record<string, unknown>> => { | ||
if (!DEFAULT_PROVIDERS.hasOwnProperty('secrets')) { | ||
DEFAULT_PROVIDERS.secrets = new SecretsProvider(); | ||
} | ||
|
||
return DEFAULT_PROVIDERS.secrets.get(name, options); | ||
}; | ||
|
||
export { | ||
getSecret | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './SecretsProvider'; | ||
export * from './getSecret'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { GetOptionsInterface } from './BaseProvider'; | ||
import type { SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; | ||
|
||
interface SecretsProviderOptions { | ||
clientConfig?: SecretsManagerClientConfig | ||
} | ||
|
||
interface SecretsGetOptionsInterface extends GetOptionsInterface { | ||
sdkOptions?: Omit<Partial<GetSecretValueCommandInput>, 'SecretId'> | ||
} | ||
|
||
export type { | ||
SecretsProviderOptions, | ||
SecretsGetOptionsInterface, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* Test SecretsProvider class | ||
* | ||
* @group unit/parameters/SecretsProvider/class | ||
*/ | ||
import { SecretsProvider } from '../../src/secrets'; | ||
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; | ||
import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; | ||
import { mockClient } from 'aws-sdk-client-mock'; | ||
import 'aws-sdk-client-mock-jest'; | ||
|
||
const encoder = new TextEncoder(); | ||
|
||
describe('Class: SecretsProvider', () => { | ||
|
||
const client = mockClient(SecretsManagerClient); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('Method: _get', () => { | ||
|
||
test('when called with only a name, it gets the secret string', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
const secretName = 'foo'; | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretString: 'bar', | ||
}); | ||
|
||
// Act | ||
const result = await provider.get(secretName); | ||
|
||
// Assess | ||
expect(result).toBe('bar'); | ||
|
||
}); | ||
|
||
test('when called with only a name, it gets the secret binary', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
const secretName = 'foo'; | ||
const mockData = encoder.encode('my-value'); | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretBinary: mockData, | ||
}); | ||
|
||
// Act | ||
const result = await provider.get(secretName); | ||
|
||
// Assess | ||
expect(result).toBe(mockData); | ||
|
||
}); | ||
|
||
test('when called with a name and sdkOptions, it gets the secret using the options provided', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
const secretName = 'foo'; | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretString: 'bar', | ||
}); | ||
|
||
// Act | ||
await provider.get(secretName, { | ||
sdkOptions: { | ||
VersionId: 'test-version', | ||
} | ||
}); | ||
|
||
// Assess | ||
expect(client).toReceiveCommandWith(GetSecretValueCommand, { | ||
SecretId: secretName, | ||
VersionId: 'test-version', | ||
}); | ||
|
||
}); | ||
|
||
test('when called with sdkOptions that override arguments passed to the method, it gets the secret using the arguments', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
const secretName = 'foo'; | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretString: 'bar', | ||
}); | ||
|
||
// Act | ||
await provider.get(secretName, { | ||
sdkOptions: { | ||
SecretId: 'test-secret', | ||
} as unknown as GetSecretValueCommandInput, | ||
}); | ||
|
||
// Assess | ||
expect(client).toReceiveCommandWith(GetSecretValueCommand, { | ||
SecretId: secretName, | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
describe('Method: _getMultiple', () => { | ||
|
||
test('when called, it throws an error', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
|
||
// Act & Assess | ||
await expect(provider.getMultiple('foo')).rejects.toThrow('Method not implemented.'); | ||
|
||
}); | ||
|
||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* Test getSecret function | ||
* | ||
* @group unit/parameters/SecretsProvider/getSecret/function | ||
*/ | ||
import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; | ||
import { SecretsProvider, getSecret } from '../../src/secrets'; | ||
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; | ||
import { mockClient } from 'aws-sdk-client-mock'; | ||
import 'aws-sdk-client-mock-jest'; | ||
|
||
const encoder = new TextEncoder(); | ||
|
||
describe('Function: getSecret', () => { | ||
|
||
const client = mockClient(SecretsManagerClient); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { | ||
|
||
// Prepare | ||
const secretName = 'foo'; | ||
const secretValue = 'bar'; | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretString: secretValue, | ||
}); | ||
|
||
// Act | ||
const result = await getSecret(secretName); | ||
|
||
// Assess | ||
expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); | ||
expect(result).toBe(secretValue); | ||
|
||
}); | ||
|
||
test('when called and a default provider exists, it uses it and returns the value', async () => { | ||
|
||
// Prepare | ||
const provider = new SecretsProvider(); | ||
DEFAULT_PROVIDERS.secrets = provider; | ||
const secretName = 'foo'; | ||
const secretValue = 'bar'; | ||
const binary = encoder.encode(secretValue); | ||
client.on(GetSecretValueCommand).resolves({ | ||
SecretBinary: binary, | ||
}); | ||
|
||
// Act | ||
const result = await getSecret(secretName); | ||
|
||
// Assess | ||
expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); | ||
expect(result).toStrictEqual(binary); | ||
expect(DEFAULT_PROVIDERS.secrets).toBe(provider); | ||
|
||
}); | ||
|
||
}); |