Skip to content

Commit 3a8cfa0

Browse files
authored
feat(parameters): add support for custom AWS SDK v3 clients for providers (#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
1 parent fd5e72c commit 3a8cfa0

12 files changed

+427
-20
lines changed

Diff for: packages/parameters/src/appconfig/AppConfigProvider.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ class AppConfigProvider extends BaseProvider {
2323
*/
2424
public constructor(options: AppConfigProviderOptions) {
2525
super();
26-
this.client = new AppConfigDataClient(options.clientConfig || {});
26+
if (options?.awsSdkV3Client) {
27+
if (options?.awsSdkV3Client instanceof AppConfigDataClient) {
28+
this.client = options.awsSdkV3Client;
29+
} else {
30+
throw Error('Not a valid AppConfigDataClient provided');
31+
}
32+
} else {
33+
this.client = new AppConfigDataClient(options.clientConfig || {});
34+
}
35+
2736
if (!options?.application && !process.env['POWERTOOLS_SERVICE_NAME']) {
2837
throw new Error(
2938
'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set'

Diff for: packages/parameters/src/dynamodb/DynamoDBProvider.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,17 @@ class DynamoDBProvider extends BaseProvider {
1919
public constructor(config: DynamoDBProviderOptions) {
2020
super();
2121

22-
const clientConfig = config.clientConfig || {};
23-
this.client = new DynamoDBClient(clientConfig);
22+
if (config?.awsSdkV3Client) {
23+
if (config?.awsSdkV3Client instanceof DynamoDBClient) {
24+
this.client = config.awsSdkV3Client;
25+
} else {
26+
throw Error('Not a valid DynamoDBClient provided');
27+
}
28+
} else {
29+
const clientConfig = config?.clientConfig || {};
30+
this.client = new DynamoDBClient(clientConfig);
31+
}
32+
2433
this.tableName = config.tableName;
2534
if (config.keyAttr) this.keyAttr = config.keyAttr;
2635
if (config.sortAttr) this.sortAttr = config.sortAttr;

Diff for: packages/parameters/src/secrets/SecretsProvider.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@ class SecretsProvider extends BaseProvider {
1515
public constructor (config?: SecretsProviderOptions) {
1616
super();
1717

18-
const clientConfig = config?.clientConfig || {};
19-
this.client = new SecretsManagerClient(clientConfig);
18+
if (config?.awsSdkV3Client) {
19+
if (config?.awsSdkV3Client instanceof SecretsManagerClient) {
20+
this.client = config.awsSdkV3Client;
21+
} else {
22+
throw Error('Not a valid SecretsManagerClient provided');
23+
}
24+
} else {
25+
const clientConfig = config?.clientConfig || {};
26+
this.client = new SecretsManagerClient(clientConfig);
27+
}
28+
2029
}
2130

2231
public async get(

Diff for: packages/parameters/src/ssm/SSMProvider.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
GetParametersCommandOutput,
1515
} from '@aws-sdk/client-ssm';
1616
import type {
17-
SSMProviderOptionsInterface,
17+
SSMProviderOptions,
1818
SSMGetMultipleOptionsInterface,
1919
SSMGetOptionsInterface,
2020
SSMGetParametersByNameOutputInterface,
@@ -29,9 +29,19 @@ class SSMProvider extends BaseProvider {
2929
protected errorsKey = '_errors';
3030
protected maxGetParametersItems = 10;
3131

32-
public constructor(config?: SSMProviderOptionsInterface) {
32+
public constructor(config?: SSMProviderOptions) {
3333
super();
34-
this.client = new SSMClient(config?.clientConfig || {});
34+
35+
if (config?.awsSdkV3Client) {
36+
if (config?.awsSdkV3Client instanceof SSMClient) {
37+
this.client = config.awsSdkV3Client;
38+
} else {
39+
throw Error('Not a valid SSMClient provided');
40+
}
41+
} else {
42+
const clientConfig = config?.clientConfig || {};
43+
this.client = new SSMClient(clientConfig);
44+
}
3545
}
3646

3747
public async get(

Diff for: packages/parameters/src/types/AppConfigProvider.ts

+41-5
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,59 @@
11
import type {
2+
AppConfigDataClient,
23
AppConfigDataClientConfig,
34
StartConfigurationSessionCommandInput,
45
} from '@aws-sdk/client-appconfigdata';
56
import type { GetOptionsInterface } from 'types/BaseProvider';
67

78
/**
8-
* Options for the AppConfigProvider class constructor.
9+
* Base interface for AppConfigProviderOptions.
910
*
10-
* @interface AppConfigProviderOptions
11+
* @interface
1112
* @property {string} environment - The environment ID or the environment name.
1213
* @property {string} [application] - The application ID or the application name.
13-
* @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region.
1414
*/
15-
interface AppConfigProviderOptions {
15+
interface AppConfigProviderOptionsBaseInterface {
1616
environment: string
1717
application?: string
18+
}
19+
20+
/**
21+
* Interface for AppConfigProviderOptions with clientConfig property.
22+
*
23+
* @interface
24+
* @extends AppConfigProviderOptionsBaseInterface
25+
* @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region.
26+
* @property {never} [awsSdkV3Client] - This property should never be passed.
27+
*/
28+
interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOptionsBaseInterface {
1829
clientConfig?: AppConfigDataClientConfig
30+
awsSdkV3Client?: never
31+
}
32+
33+
/**
34+
* Interface for AppConfigProviderOptions with awsSdkV3Client property.
35+
*
36+
* @interface
37+
* @extends AppConfigProviderOptionsBaseInterface
38+
* @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation
39+
* @property {never} [clientConfig] - This property should never be passed.
40+
*/
41+
interface AppConfigProviderOptionsWithClientInstance extends AppConfigProviderOptionsBaseInterface {
42+
awsSdkV3Client?: AppConfigDataClient
43+
clientConfig?: never
1944
}
2045

46+
/**
47+
* Options for the AppConfigProvider class constructor.
48+
*
49+
* @type AppConfigProviderOptions
50+
* @property {string} environment - The environment ID or the environment name.
51+
* @property {string} [application] - The application ID or the application name.
52+
* @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client.
53+
* @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation. Mutually exclusive with clientConfig.
54+
*/
55+
type AppConfigProviderOptions = AppConfigProviderOptionsWithClientConfig | AppConfigProviderOptionsWithClientInstance;
56+
2157
/**
2258
* Options for the AppConfigProvider get method.
2359
*
@@ -40,7 +76,7 @@ interface AppConfigGetOptionsInterface extends Omit<GetOptionsInterface, 'sdkOpt
4076
* @extends {AppConfigProviderOptions, AppConfigGetOptionsInterface}
4177
*/
4278
interface GetAppConfigCombinedInterface
43-
extends Omit<AppConfigProviderOptions, 'clientConfig'>,
79+
extends Omit<AppConfigProviderOptions, 'clientConfig' | 'awsSdkV3Client'>,
4480
AppConfigGetOptionsInterface {}
4581

4682
export type {

Diff for: packages/parameters/src/types/DynamoDBProvider.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import type { GetOptionsInterface, GetMultipleOptionsInterface } from './BaseProvider';
2-
import type { GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
2+
import type { DynamoDBClient, GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
33

4-
interface DynamoDBProviderOptions {
4+
interface DynamoDBProviderOptionsBaseInterface {
55
tableName: string
66
keyAttr?: string
77
sortAttr?: string
88
valueAttr?: string
9+
}
10+
11+
interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOptionsBaseInterface {
912
clientConfig?: DynamoDBClientConfig
13+
awsSdkV3Client?: never
1014
}
1115

16+
interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOptionsBaseInterface {
17+
awsSdkV3Client?: DynamoDBClient
18+
clientConfig?: never
19+
}
20+
21+
type DynamoDBProviderOptions = DynamoDBProviderOptionsWithClientConfig | DynamoDBProviderOptionsWithClientInstance;
22+
1223
/**
1324
* Options for the DynamoDBProvider get method.
1425
*

Diff for: packages/parameters/src/types/SSMProvider.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
SSMClient,
23
SSMClientConfig,
34
GetParameterCommandInput,
45
GetParametersByPathCommandInput
@@ -9,10 +10,18 @@ import type {
910
TransformOptions
1011
} from './BaseProvider';
1112

12-
interface SSMProviderOptionsInterface {
13-
clientConfig: SSMClientConfig
13+
interface SSMProviderOptionsWithClientConfig {
14+
clientConfig?: SSMClientConfig
15+
awsSdkV3Client?: never
1416
}
1517

18+
interface SSMProviderOptionsWithClientInstance {
19+
awsSdkV3Client?: SSMClient
20+
clientConfig?: never
21+
}
22+
23+
type SSMProviderOptions = SSMProviderOptionsWithClientConfig | SSMProviderOptionsWithClientInstance;
24+
1625
/**
1726
* Options for the SSMProvider get method.
1827
*
@@ -56,7 +65,7 @@ type SSMGetParametersByNameFromCacheOutputType = {
5665
};
5766

5867
export type {
59-
SSMProviderOptionsInterface,
68+
SSMProviderOptions,
6069
SSMGetOptionsInterface,
6170
SSMGetMultipleOptionsInterface,
6271
SSMGetParametersByNameOptionsInterface,

Diff for: packages/parameters/src/types/SecretsProvider.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import type { GetOptionsInterface } from './BaseProvider';
2-
import type { SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager';
2+
import type { SecretsManagerClient, SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager';
33

4-
interface SecretsProviderOptions {
4+
interface SecretsProviderOptionsWithClientConfig {
55
clientConfig?: SecretsManagerClientConfig
6+
awsSdkV3Client?: never
67
}
78

9+
interface SecretsProviderOptionsWithClientInstance {
10+
awsSdkV3Client?: SecretsManagerClient
11+
clientConfig?: never
12+
}
13+
14+
type SecretsProviderOptions = SecretsProviderOptionsWithClientConfig | SecretsProviderOptionsWithClientInstance;
15+
816
interface SecretsGetOptionsInterface extends GetOptionsInterface {
917
sdkOptions?: Omit<Partial<GetSecretValueCommandInput>, 'SecretId'>
1018
}

Diff for: packages/parameters/tests/unit/AppConfigProvider.test.ts

+79
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,85 @@ describe('Class: AppConfigProvider', () => {
2121
jest.clearAllMocks();
2222
});
2323

24+
describe('Method: constructor', () => {
25+
test('when the class instantiates without SDK client and client config it has default options', async () => {
26+
// Prepare
27+
const options: AppConfigProviderOptions = {
28+
application: 'MyApp',
29+
environment: 'MyAppProdEnv',
30+
};
31+
32+
// Act
33+
const provider = new AppConfigProvider(options);
34+
35+
// Assess
36+
expect(provider.client.config).toEqual(
37+
expect.objectContaining({
38+
serviceId: 'AppConfigData',
39+
})
40+
);
41+
});
42+
43+
test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => {
44+
// Prepare
45+
const options: AppConfigProviderOptions = {
46+
application: 'MyApp',
47+
environment: 'MyAppProdEnv',
48+
clientConfig: {
49+
serviceId: 'with-client-config',
50+
},
51+
};
52+
53+
// Act
54+
const provider = new AppConfigProvider(options);
55+
56+
// Assess
57+
expect(provider.client.config).toEqual(
58+
expect.objectContaining({
59+
serviceId: 'with-client-config',
60+
})
61+
);
62+
});
63+
64+
test('when the user provides an SDK client in the options, the class instantiates with it', async () => {
65+
// Prepare
66+
const awsSdkV3Client = new AppConfigDataClient({
67+
serviceId: 'with-custom-sdk-client',
68+
});
69+
70+
const options: AppConfigProviderOptions = {
71+
application: 'MyApp',
72+
environment: 'MyAppProdEnv',
73+
awsSdkV3Client: awsSdkV3Client,
74+
};
75+
76+
// Act
77+
const provider = new AppConfigProvider(options);
78+
79+
// Assess
80+
expect(provider.client.config).toEqual(
81+
expect.objectContaining({
82+
serviceId: 'with-custom-sdk-client',
83+
})
84+
);
85+
});
86+
87+
test('when the user provides NOT an SDK client in the options, it throws an error', async () => {
88+
// Prepare
89+
const awsSdkV3Client = {};
90+
const options: AppConfigProviderOptions = {
91+
application: 'MyApp',
92+
environment: 'MyAppProdEnv',
93+
awsSdkV3Client: awsSdkV3Client as AppConfigDataClient,
94+
};
95+
96+
// Act & Assess
97+
expect(() => {
98+
new AppConfigProvider(options);
99+
}).toThrow();
100+
});
101+
});
102+
24103
describe('Method: _get', () => {
25104
test('when called with name and options, it returns binary configuration', async () => {
26105
// Prepare

0 commit comments

Comments
 (0)