-
Notifications
You must be signed in to change notification settings - Fork 156
feat(parameters): SecretsProvider support #1206
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
Merged
dreamorosi
merged 6 commits into
main
from
1175-feature-request-implement-secretsprovider
Jan 5, 2023
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d0a3d23
wip: SecretsProvider
dreamorosi e778fdd
feat: SecretsProvider
dreamorosi ae9bac4
refactor: types & auto-transform single
dreamorosi bbfa196
test: unit tests
dreamorosi 31a493f
Update packages/parameters/src/BaseProvider.ts
dreamorosi 2e3c5b2
refactor: readability
dreamorosi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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 hidden or 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,57 @@ | ||
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 = { | ||
SecretId: name, | ||
}; | ||
if (options?.sdkOptions) { | ||
this.removeNonOverridableOptions(options.sdkOptions as GetSecretValueCommandInput); | ||
Object.assign(sdkOptions, options.sdkOptions); | ||
} | ||
|
||
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.'); | ||
} | ||
|
||
/** | ||
* Explicit arguments passed to the constructor will take precedence over ones passed to the method. | ||
* For users who consume the library with TypeScript, this will be enforced by the type system. However, | ||
* for JavaScript users, we need to manually delete the properties that are not allowed to be overridden. | ||
*/ | ||
protected removeNonOverridableOptions(options: GetSecretValueCommandInput): void { | ||
if (options.hasOwnProperty('SecretId')) { | ||
delete options.SecretId; | ||
} | ||
} | ||
} | ||
|
||
export { | ||
SecretsProvider, | ||
}; |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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,128 @@ | ||
/** | ||
* 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', | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
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.'); | ||
|
||
}); | ||
|
||
}); | ||
|
||
describe('Method: removeNonOverridableOptions', () => { | ||
|
||
class DummyProvider extends SecretsProvider { | ||
public removeNonOverridableOptions(options: GetSecretValueCommandInput): void { | ||
super.removeNonOverridableOptions(options); | ||
} | ||
} | ||
|
||
test('when called with a valid GetSecretValueCommandInput, it removes the non-overridable options', () => { | ||
|
||
// Prepare | ||
const provider = new DummyProvider(); | ||
const options: GetSecretValueCommandInput = { | ||
SecretId: 'test-secret', | ||
VersionId: 'test-version', | ||
}; | ||
|
||
// Act | ||
provider.removeNonOverridableOptions(options); | ||
|
||
// Assess | ||
expect(options).toEqual({ | ||
VersionId: 'test-version', | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
}); |
This file contains hidden or 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); | ||
|
||
}); | ||
|
||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the comment above the
removeNonOverridableOptions
function and instead of explicitly deleting properties, spreadoptions.sdkOptions
first (if any) and then overwrite them withname
would work:It may be less readable, but I like it more than duplicating
removeNonOverridableOptions
in every provider. Am I missing something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I have applied your suggestion. Thank you!