Skip to content
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

feat(parameters): add support for custom AWS SDK v3 clients for providers #1260

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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