From c6b7597d8b4ffcb8e562ddea584d7061255ab5af Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Wed, 25 Jan 2023 12:20:43 +0400 Subject: [PATCH 1/4] feat(parameters): add support for custom sdk client for appconfig provider --- .../src/appconfig/AppConfigProvider.ts | 11 ++- .../parameters/src/types/AppConfigProvider.ts | 46 +++++++++-- .../tests/unit/AppConfigProvider.test.ts | 79 +++++++++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 0042722aec..9e9e2b0152 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -23,7 +23,16 @@ class AppConfigProvider extends BaseProvider { */ public constructor(options: AppConfigProviderOptions) { super(); - this.client = new AppConfigDataClient(options.clientConfig || {}); + if (options?.awsSdkV3Client) { + if (options?.awsSdkV3Client instanceof AppConfigDataClient) { + this.client = options.awsSdkV3Client; + } else { + throw Error('Not a valid AppConfigDataClient provided'); + } + } else { + this.client = new AppConfigDataClient(options.clientConfig || {}); + } + if (!options?.application && !process.env['POWERTOOLS_SERVICE_NAME']) { throw new Error( 'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set' diff --git a/packages/parameters/src/types/AppConfigProvider.ts b/packages/parameters/src/types/AppConfigProvider.ts index c699714639..007bca8a50 100644 --- a/packages/parameters/src/types/AppConfigProvider.ts +++ b/packages/parameters/src/types/AppConfigProvider.ts @@ -1,23 +1,59 @@ import type { + AppConfigDataClient, AppConfigDataClientConfig, StartConfigurationSessionCommandInput, } from '@aws-sdk/client-appconfigdata'; import type { GetOptionsInterface } from 'types/BaseProvider'; /** - * Options for the AppConfigProvider class constructor. + * Base interface for AppConfigProviderOptions. * - * @interface AppConfigProviderOptions + * @interface * @property {string} environment - The environment ID or the environment name. * @property {string} [application] - The application ID or the application name. - * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. */ -interface AppConfigProviderOptions { +interface AppConfigProviderOptionsBaseInterface { environment: string application?: string +} + +/** + * Interface for AppConfigProviderOptions with clientConfig property. + * + * @interface + * @extends AppConfigProviderOptionsBaseInterface + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @property {never} [awsSdkV3Client] - This property should never be passed. + */ +interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOptionsBaseInterface { clientConfig?: AppConfigDataClientConfig + awsSdkV3Client?: never +} + +/** + * Interface for AppConfigProviderOptions with awsSdkV3Client property. + * + * @interface + * @extends AppConfigProviderOptionsBaseInterface + * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation + * @property {never} [clientConfig] - This property should never be passed. + */ +interface AppConfigProviderOptionsWithClientInstance extends AppConfigProviderOptionsBaseInterface { + awsSdkV3Client?: AppConfigDataClient + clientConfig?: never } +/** + * Options for the AppConfigProvider class constructor. + * + * @type AppConfigProviderOptions + * @property {string} environment - The environment ID or the environment name. + * @property {string} [application] - The application ID or the application name. + * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation. Mutually exclusive with clientConfig. + */ +type AppConfigProviderOptions = AppConfigProviderOptionsWithClientConfig | AppConfigProviderOptionsWithClientInstance; + /** * Options for the AppConfigProvider get method. * @@ -40,7 +76,7 @@ interface AppConfigGetOptionsInterface extends Omit, + extends Omit, AppConfigGetOptionsInterface {} export type { diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index f0b23560df..56365d882c 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -21,6 +21,85 @@ describe('Class: AppConfigProvider', () => { jest.clearAllMocks(); }); + describe('Method: constructor', () => { + test('when the class instantiates without SDK client and client config it has default options', async () => { + // Prepare + const options: AppConfigProviderOptions = { + application: 'MyApp', + environment: 'MyAppProdEnv', + }; + + // Act + const provider = new AppConfigProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'AppConfigData', + }) + ); + }); + + test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + // Prepare + const options: AppConfigProviderOptions = { + application: 'MyApp', + environment: 'MyAppProdEnv', + clientConfig: { + serviceId: 'with-client-config', + }, + }; + + // Act + const provider = new AppConfigProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-client-config', + }) + ); + }); + + test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + // Prepare + const awsSdkV3Client = new AppConfigDataClient({ + serviceId: 'with-custom-sdk-client', + }); + + const options: AppConfigProviderOptions = { + application: 'MyApp', + environment: 'MyAppProdEnv', + awsSdkV3Client: awsSdkV3Client, + }; + + // Act + const provider = new AppConfigProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-custom-sdk-client', + }) + ); + }); + + test('when the user provides NOT an SDK client in the options, it throws an error', async () => { + // Prepare + const awsSdkV3Client = {}; + const options: AppConfigProviderOptions = { + application: 'MyApp', + environment: 'MyAppProdEnv', + awsSdkV3Client: awsSdkV3Client as AppConfigDataClient, + }; + + // Act & Assess + expect(() => { + new AppConfigProvider(options); + }).toThrow(); + }); + }); + describe('Method: _get', () => { test('when called with name and options, it returns binary configuration', async () => { // Prepare From 8391008b974bcc17f97a9b08561d76e1d9ffd7d6 Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Wed, 1 Feb 2023 16:37:26 +0400 Subject: [PATCH 2/4] feat(parameters): add support for custom sdk client for secrets manager provider --- .../parameters/src/secrets/SecretsProvider.ts | 13 +++- .../parameters/src/types/SecretsProvider.ts | 12 ++- .../tests/unit/SecretsProvider.test.ts | 74 +++++++++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 4d91009610..12a6703783 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -15,8 +15,17 @@ class SecretsProvider extends BaseProvider { public constructor (config?: SecretsProviderOptions) { super(); - const clientConfig = config?.clientConfig || {}; - this.client = new SecretsManagerClient(clientConfig); + if (config?.awsSdkV3Client) { + if (config?.awsSdkV3Client instanceof SecretsManagerClient) { + this.client = config.awsSdkV3Client; + } else { + throw Error('Not a valid SecretsManagerClient provided'); + } + } else { + const clientConfig = config?.clientConfig || {}; + this.client = new SecretsManagerClient(clientConfig); + } + } public async get( diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index b936309765..757f38b18b 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -1,10 +1,18 @@ import type { GetOptionsInterface } from './BaseProvider'; -import type { SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; +import type { SecretsManagerClient, SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; -interface SecretsProviderOptions { +interface SecretsProviderOptionsWithClientConfig { clientConfig?: SecretsManagerClientConfig + awsSdkV3Client?: never } +interface SecretsProviderOptionsWithClientInstance { + awsSdkV3Client?: SecretsManagerClient + clientConfig?: never +} + +type SecretsProviderOptions = SecretsProviderOptionsWithClientConfig | SecretsProviderOptionsWithClientInstance; + interface SecretsGetOptionsInterface extends GetOptionsInterface { sdkOptions?: Omit, 'SecretId'> } diff --git a/packages/parameters/tests/unit/SecretsProvider.test.ts b/packages/parameters/tests/unit/SecretsProvider.test.ts index 8b4fc3067a..e35923cb32 100644 --- a/packages/parameters/tests/unit/SecretsProvider.test.ts +++ b/packages/parameters/tests/unit/SecretsProvider.test.ts @@ -6,6 +6,7 @@ import { SecretsProvider } from '../../src/secrets'; import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; +import type { SecretsProviderOptions } from '../../src/types/SecretsProvider'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; @@ -19,6 +20,79 @@ describe('Class: SecretsProvider', () => { jest.clearAllMocks(); }); + describe('Method: constructor', () => { + test('when the class instantiates without SDK client and client config it has default options', async () => { + + // Prepare + const options: SecretsProviderOptions = {}; + + // Act + const provider = new SecretsProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'Secrets Manager', + }) + ); + }); + + test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + + // Prepare + const options: SecretsProviderOptions = { + clientConfig: { + serviceId: 'with-client-config', + }, + }; + + // Act + const provider = new SecretsProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-client-config', + }) + ); + }); + + test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + + // Prepare + const awsSdkV3Client = new SecretsManagerClient({ + serviceId: 'with-custom-sdk-client', + }); + + const options: SecretsProviderOptions = { + awsSdkV3Client: awsSdkV3Client, + }; + + // Act + const provider = new SecretsProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-custom-sdk-client', + }) + ); + }); + + test('when the user provides NOT an SDK client in the options, it throws an error', async () => { + // Prepare + const awsSdkV3Client = {}; + const options: SecretsProviderOptions = { + awsSdkV3Client: awsSdkV3Client as SecretsManagerClient, + }; + + // Act & Assess + expect(() => { + new SecretsProvider(options); + }).toThrow(); + }); + }); + describe('Method: _get', () => { test('when called with only a name, it gets the secret string', async () => { From c58879f88db2697e5b1ff35a72e67b6b45203ae3 Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Wed, 1 Feb 2023 17:01:27 +0400 Subject: [PATCH 3/4] feat(parameters): add support for custom sdk client for dynamoDB provider --- .../src/dynamodb/DynamoDBProvider.ts | 13 ++- .../parameters/src/types/DynamoDBProvider.ts | 15 +++- .../tests/unit/DynamoDBProvider.test.ts | 79 +++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index f99ab90651..9f0bab174d 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -19,8 +19,17 @@ class DynamoDBProvider extends BaseProvider { public constructor(config: DynamoDBProviderOptions) { super(); - const clientConfig = config.clientConfig || {}; - this.client = new DynamoDBClient(clientConfig); + if (config?.awsSdkV3Client) { + if (config?.awsSdkV3Client instanceof DynamoDBClient) { + this.client = config.awsSdkV3Client; + } else { + throw Error('Not a valid DynamoDBClient provided'); + } + } else { + const clientConfig = config?.clientConfig || {}; + this.client = new DynamoDBClient(clientConfig); + } + this.tableName = config.tableName; if (config.keyAttr) this.keyAttr = config.keyAttr; if (config.sortAttr) this.sortAttr = config.sortAttr; diff --git a/packages/parameters/src/types/DynamoDBProvider.ts b/packages/parameters/src/types/DynamoDBProvider.ts index a0327a9557..140bf50fd2 100644 --- a/packages/parameters/src/types/DynamoDBProvider.ts +++ b/packages/parameters/src/types/DynamoDBProvider.ts @@ -1,14 +1,25 @@ import type { GetOptionsInterface, GetMultipleOptionsInterface } from './BaseProvider'; -import type { GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'; +import type { DynamoDBClient, GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'; -interface DynamoDBProviderOptions { +interface DynamoDBProviderOptionsBaseInterface { tableName: string keyAttr?: string sortAttr?: string valueAttr?: string +} + +interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOptionsBaseInterface { clientConfig?: DynamoDBClientConfig + awsSdkV3Client?: never } +interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOptionsBaseInterface { + awsSdkV3Client?: DynamoDBClient + clientConfig?: never +} + +type DynamoDBProviderOptions = DynamoDBProviderOptionsWithClientConfig | DynamoDBProviderOptionsWithClientInstance; + /** * Options for the DynamoDBProvider get method. * diff --git a/packages/parameters/tests/unit/DynamoDBProvider.test.ts b/packages/parameters/tests/unit/DynamoDBProvider.test.ts index 2ecbd4dd44..f72a36c0d1 100644 --- a/packages/parameters/tests/unit/DynamoDBProvider.test.ts +++ b/packages/parameters/tests/unit/DynamoDBProvider.test.ts @@ -6,6 +6,7 @@ import { DynamoDBProvider } from '../../src/dynamodb'; import { DynamoDBClient, GetItemCommand, QueryCommand } from '@aws-sdk/client-dynamodb'; import type { GetItemCommandInput, QueryCommandInput } from '@aws-sdk/client-dynamodb'; +import type { DynamoDBProviderOptions } from '../../src/types/DynamoDBProvider'; import { marshall } from '@aws-sdk/util-dynamodb'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; @@ -16,6 +17,84 @@ describe('Class: DynamoDBProvider', () => { jest.clearAllMocks(); }); + describe('Method: constructor', () => { + test('when the class instantiates without SDK client and client config it has default options', async () => { + + // Prepare + const options: DynamoDBProviderOptions = { + tableName: 'test-table', + }; + + // Act + const provider = new DynamoDBProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'DynamoDB', + }) + ); + }); + + test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + + // Prepare + const options: DynamoDBProviderOptions = { + tableName: 'test-table', + clientConfig: { + serviceId: 'with-client-config', + }, + }; + + // Act + const provider = new DynamoDBProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-client-config', + }) + ); + }); + + test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + + // Prepare + const awsSdkV3Client = new DynamoDBClient({ + serviceId: 'with-custom-sdk-client', + }); + + const options: DynamoDBProviderOptions = { + tableName: 'test-table', + awsSdkV3Client: awsSdkV3Client, + }; + + // Act + const provider = new DynamoDBProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-custom-sdk-client', + }) + ); + }); + + test('when the user provides NOT an SDK client in the options, it throws an error', async () => { + // Prepare + const awsSdkV3Client = {}; + const options: DynamoDBProviderOptions = { + tableName: 'test-table', + awsSdkV3Client: awsSdkV3Client as DynamoDBClient, + }; + + // Act & Assess + expect(() => { + new DynamoDBProvider(options); + }).toThrow(); + }); + }); + describe('Method: _get', () => { test('when called and the sdk client returns no items, it returns undefined', async () => { From 564169ae2b457b1f1e9fc86a61789534862e3d4b Mon Sep 17 00:00:00 2001 From: Sergei Cherniaev Date: Wed, 1 Feb 2023 17:30:02 +0400 Subject: [PATCH 4/4] feat(parameters): add support for custom sdk client for SSM provider --- packages/parameters/src/ssm/SSMProvider.ts | 16 +++- packages/parameters/src/types/SSMProvider.ts | 15 +++- .../parameters/tests/unit/SSMProvider.test.ts | 74 +++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index 66d7e12367..39cc0ee569 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -14,7 +14,7 @@ import type { GetParametersCommandOutput, } from '@aws-sdk/client-ssm'; import type { - SSMProviderOptionsInterface, + SSMProviderOptions, SSMGetMultipleOptionsInterface, SSMGetOptionsInterface, SSMGetParametersByNameOutputInterface, @@ -29,9 +29,19 @@ class SSMProvider extends BaseProvider { protected errorsKey = '_errors'; protected maxGetParametersItems = 10; - public constructor(config?: SSMProviderOptionsInterface) { + public constructor(config?: SSMProviderOptions) { super(); - this.client = new SSMClient(config?.clientConfig || {}); + + if (config?.awsSdkV3Client) { + if (config?.awsSdkV3Client instanceof SSMClient) { + this.client = config.awsSdkV3Client; + } else { + throw Error('Not a valid SSMClient provided'); + } + } else { + const clientConfig = config?.clientConfig || {}; + this.client = new SSMClient(clientConfig); + } } public async get( diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index f1127b289e..9ce1f66e97 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -1,4 +1,5 @@ import type { + SSMClient, SSMClientConfig, GetParameterCommandInput, GetParametersByPathCommandInput @@ -9,10 +10,18 @@ import type { TransformOptions } from './BaseProvider'; -interface SSMProviderOptionsInterface { - clientConfig: SSMClientConfig +interface SSMProviderOptionsWithClientConfig { + clientConfig?: SSMClientConfig + awsSdkV3Client?: never } +interface SSMProviderOptionsWithClientInstance { + awsSdkV3Client?: SSMClient + clientConfig?: never +} + +type SSMProviderOptions = SSMProviderOptionsWithClientConfig | SSMProviderOptionsWithClientInstance; + /** * Options for the SSMProvider get method. * @@ -56,7 +65,7 @@ type SSMGetParametersByNameFromCacheOutputType = { }; export type { - SSMProviderOptionsInterface, + SSMProviderOptions, SSMGetOptionsInterface, SSMGetMultipleOptionsInterface, SSMGetParametersByNameOptionsInterface, diff --git a/packages/parameters/tests/unit/SSMProvider.test.ts b/packages/parameters/tests/unit/SSMProvider.test.ts index a48e6a4004..856f2debec 100644 --- a/packages/parameters/tests/unit/SSMProvider.test.ts +++ b/packages/parameters/tests/unit/SSMProvider.test.ts @@ -14,6 +14,7 @@ import type { GetParametersCommandOutput } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; import type { + SSMProviderOptions, SSMGetParametersByNameFromCacheOutputType, SSMGetParametersByNameOptionsInterface, SSMSplitBatchAndDecryptParametersOutputType, @@ -30,6 +31,79 @@ describe('Class: SSMProvider', () => { jest.clearAllMocks(); }); + describe('Method: constructor', () => { + test('when the class instantiates without SDK client and client config it has default options', async () => { + + // Prepare + const options: SSMProviderOptions = {}; + + // Act + const provider = new SSMProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'SSM', + }) + ); + }); + + test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + + // Prepare + const options: SSMProviderOptions = { + clientConfig: { + serviceId: 'with-client-config', + }, + }; + + // Act + const provider = new SSMProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-client-config', + }) + ); + }); + + test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + + // Prepare + const awsSdkV3Client = new SSMClient({ + serviceId: 'with-custom-sdk-client', + }); + + const options: SSMProviderOptions = { + awsSdkV3Client: awsSdkV3Client, + }; + + // Act + const provider = new SSMProvider(options); + + // Assess + expect(provider.client.config).toEqual( + expect.objectContaining({ + serviceId: 'with-custom-sdk-client', + }) + ); + }); + + test('when the user provides NOT an SDK client in the options, it throws an error', async () => { + // Prepare + const awsSdkV3Client = {}; + const options: SSMProviderOptions = { + awsSdkV3Client: awsSdkV3Client as SSMClient, + }; + + // Act & Assess + expect(() => { + new SSMProvider(options); + }).toThrow(); + }); + }); + describe('Method: getParametersByName', () => { class SSMProviderMock extends SSMProvider {