Skip to content

Commit

Permalink
feat(parameters): add support for custom AWS SDK v3 clients for provi…
Browse files Browse the repository at this point in the history
…ders (#1260)

* feat(parameters): add support for custom sdk client for appconfig provider

* feat(parameters): add support for custom sdk client for secrets manager provider

* feat(parameters): add support for custom sdk client for dynamoDB provider

* feat(parameters): add support for custom sdk client for SSM provider
  • Loading branch information
shdq committed Feb 6, 2023
1 parent fd5e72c commit 3a8cfa0
Show file tree
Hide file tree
Showing 12 changed files with 427 additions and 20 deletions.
11 changes: 10 additions & 1 deletion packages/parameters/src/appconfig/AppConfigProvider.ts
Expand Up @@ -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'
Expand Down
13 changes: 11 additions & 2 deletions packages/parameters/src/dynamodb/DynamoDBProvider.ts
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions packages/parameters/src/secrets/SecretsProvider.ts
Expand Up @@ -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(
Expand Down
16 changes: 13 additions & 3 deletions packages/parameters/src/ssm/SSMProvider.ts
Expand Up @@ -14,7 +14,7 @@ import type {
GetParametersCommandOutput,
} from '@aws-sdk/client-ssm';
import type {
SSMProviderOptionsInterface,
SSMProviderOptions,
SSMGetMultipleOptionsInterface,
SSMGetOptionsInterface,
SSMGetParametersByNameOutputInterface,
Expand All @@ -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(
Expand Down
46 changes: 41 additions & 5 deletions 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.
*
Expand All @@ -40,7 +76,7 @@ interface AppConfigGetOptionsInterface extends Omit<GetOptionsInterface, 'sdkOpt
* @extends {AppConfigProviderOptions, AppConfigGetOptionsInterface}
*/
interface GetAppConfigCombinedInterface
extends Omit<AppConfigProviderOptions, 'clientConfig'>,
extends Omit<AppConfigProviderOptions, 'clientConfig' | 'awsSdkV3Client'>,
AppConfigGetOptionsInterface {}

export type {
Expand Down
15 changes: 13 additions & 2 deletions 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.
*
Expand Down
15 changes: 12 additions & 3 deletions packages/parameters/src/types/SSMProvider.ts
@@ -1,4 +1,5 @@
import type {
SSMClient,
SSMClientConfig,
GetParameterCommandInput,
GetParametersByPathCommandInput
Expand All @@ -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.
*
Expand Down Expand Up @@ -56,7 +65,7 @@ type SSMGetParametersByNameFromCacheOutputType = {
};

export type {
SSMProviderOptionsInterface,
SSMProviderOptions,
SSMGetOptionsInterface,
SSMGetMultipleOptionsInterface,
SSMGetParametersByNameOptionsInterface,
Expand Down
12 changes: 10 additions & 2 deletions 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<Partial<GetSecretValueCommandInput>, 'SecretId'>
}
Expand Down
79 changes: 79 additions & 0 deletions packages/parameters/tests/unit/AppConfigProvider.test.ts
Expand Up @@ -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
Expand Down

0 comments on commit 3a8cfa0

Please sign in to comment.