Skip to content

Commit c85b90a

Browse files
authored
improv(parser): allow extending base provider with no AWS SDK client (#3564)
1 parent 674ba36 commit c85b90a

File tree

9 files changed

+69
-41
lines changed

9 files changed

+69
-41
lines changed

Diff for: docs/utilities/parameters.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ All caching logic is handled by the `BaseProvider`, and provided that the return
297297

298298
Here's an example of implementing a custom parameter store using an external service like HashiCorp Vault, a widely popular key-value secret storage.
299299

300+
=== "Provider usage"
301+
```typescript hl_lines="5-8 12-16"
302+
--8<-- "examples/snippets/parameters/customProviderVaultUsage.ts"
303+
```
304+
300305
=== "Provider implementation"
301306
```typescript
302307
--8<-- "examples/snippets/parameters/customProviderVault.ts"
@@ -307,11 +312,6 @@ Here's an example of implementing a custom parameter store using an external ser
307312
--8<-- "examples/snippets/parameters/customProviderVaultTypes.ts"
308313
```
309314

310-
=== "Provider usage"
311-
```typescript
312-
--8<-- "examples/snippets/parameters/customProviderVaultUsage.ts"
313-
```
314-
315315
### Deserializing values with transform parameter
316316

317317
For parameters stored in JSON or Base64 format, you can use the `transform` argument for deserialization.

Diff for: examples/snippets/parameters/customProviderVault.ts

+7-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Logger } from '@aws-lambda-powertools/logger';
21
import { BaseProvider } from '@aws-lambda-powertools/parameters/base';
2+
import { GetParameterError } from '@aws-lambda-powertools/parameters/errors';
33
import Vault from 'hashi-vault-js';
44
import type {
55
HashiCorpVaultGetOptions,
@@ -9,17 +9,14 @@ import type {
99
class HashiCorpVaultProvider extends BaseProvider {
1010
public client: Vault;
1111
readonly #token: string;
12-
readonly #logger: Logger;
1312

1413
/**
1514
* It initializes the HashiCorpVaultProvider class.
1615
*
1716
* @param {HashiCorpVaultProviderOptions} config - The configuration object.
1817
*/
1918
public constructor(config: HashiCorpVaultProviderOptions) {
20-
super({
21-
proto: Vault,
22-
});
19+
super({});
2320

2421
const { url, token, clientConfig, vaultClient } = config;
2522
if (vaultClient) {
@@ -39,9 +36,6 @@ class HashiCorpVaultProvider extends BaseProvider {
3936
this.client = new Vault(config);
4037
}
4138
this.#token = token;
42-
this.#logger = new Logger({
43-
serviceName: 'HashiCorpVaultProvider',
44-
});
4539
}
4640

4741
/**
@@ -55,13 +49,13 @@ class HashiCorpVaultProvider extends BaseProvider {
5549
* @param {string} name - The name of the secret
5650
* @param {HashiCorpVaultGetOptions} options - Options to customize the retrieval of the secret
5751
*/
58-
public async get(
52+
public async get<T extends Record<string, unknown>>(
5953
name: string,
6054
options?: HashiCorpVaultGetOptions
61-
): Promise<Record<string, unknown> | undefined> {
55+
): Promise<T | undefined> {
6256
return super.get(name, options) as Promise<
6357
Record<string, unknown> | undefined
64-
>;
58+
> as Promise<T | undefined>;
6559
}
6660

6761
/**
@@ -92,9 +86,6 @@ class HashiCorpVaultProvider extends BaseProvider {
9286
);
9387

9488
if (response.isVaultError) {
95-
this.#logger.error('An error occurred', {
96-
error: response.vaultHelpMessage,
97-
});
9889
throw response;
9990
}
10091
return response.data;
@@ -108,8 +99,8 @@ class HashiCorpVaultProvider extends BaseProvider {
10899
protected async _getMultiple(
109100
_path: string,
110101
_options?: unknown
111-
): Promise<void> {
112-
throw new Error('Method not implemented.');
102+
): Promise<Record<string, unknown> | undefined> {
103+
throw new GetParameterError('Method not implemented.');
113104
}
114105
}
115106

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
12
import { HashiCorpVaultProvider } from './customProviderVault.js';
23

4+
const logger = new Logger({ logLevel: 'DEBUG' });
35
const secretsProvider = new HashiCorpVaultProvider({
46
url: 'https://vault.example.com:8200/v1',
5-
token: 'my-token',
7+
token: process.env.ROOT_TOKEN ?? '',
68
});
79

8-
export const handler = async (): Promise<void> => {
10+
try {
911
// Retrieve a secret from HashiCorp Vault
10-
const secret = await secretsProvider.get('my-secret');
11-
console.log(secret);
12-
};
12+
const secret = await secretsProvider.get<{ foo: 'string' }>('my-secret', {
13+
sdkOptions: {
14+
mount: 'secrets',
15+
},
16+
});
17+
if (!secret) {
18+
throw new Error('Secret not found');
19+
}
20+
logger.debug('Secret retrieved!');
21+
} catch (error) {
22+
if (error instanceof Error) {
23+
logger.error(error.message, error);
24+
}
25+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class AppConfigProvider extends BaseProvider {
198198
*/
199199
public constructor(options: AppConfigProviderOptions) {
200200
super({
201-
proto: AppConfigDataClient as new (
201+
awsSdkV3ClientPrototype: AppConfigDataClient as new (
202202
config?: unknown
203203
) => AppConfigDataClient,
204204
clientConfig: options.clientConfig,

Diff for: packages/parameters/src/base/BaseProvider.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js';
99
import { GetParameterError, TransformParameterError } from '../errors.js';
1010
import type {
11+
BaseProviderConstructorOptions,
1112
BaseProviderInterface,
1213
GetMultipleOptionsInterface,
1314
GetOptionsInterface,
@@ -44,27 +45,25 @@ abstract class BaseProvider implements BaseProviderInterface {
4445
public constructor({
4546
awsSdkV3Client,
4647
clientConfig,
47-
proto,
48-
}: {
49-
awsSdkV3Client?: unknown;
50-
clientConfig?: unknown;
51-
proto: new (config?: unknown) => unknown;
52-
}) {
48+
awsSdkV3ClientPrototype,
49+
}: BaseProviderConstructorOptions) {
5350
this.store = new Map();
5451
this.envVarsService = new EnvironmentVariablesService();
5552
if (awsSdkV3Client) {
56-
if (!isSdkClient(awsSdkV3Client)) {
53+
if (!isSdkClient(awsSdkV3Client) && awsSdkV3ClientPrototype) {
5754
console.warn(
5855
'awsSdkV3Client is not an AWS SDK v3 client, using default client'
5956
);
60-
this.client = new proto(clientConfig ?? {});
57+
this.client = new awsSdkV3ClientPrototype(clientConfig ?? {});
6158
} else {
6259
this.client = awsSdkV3Client;
6360
}
64-
} else {
65-
this.client = new proto(clientConfig ?? {});
61+
} else if (awsSdkV3ClientPrototype) {
62+
this.client = new awsSdkV3ClientPrototype(clientConfig ?? {});
63+
}
64+
if (isSdkClient(this.client)) {
65+
addUserAgentMiddleware(this.client, 'parameters');
6666
}
67-
addUserAgentMiddleware(this.client, 'parameters');
6867
}
6968

7069
/**

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,9 @@ class DynamoDBProvider extends BaseProvider {
248248
*/
249249
public constructor(config: DynamoDBProviderOptions) {
250250
super({
251-
proto: DynamoDBClient as new (config?: unknown) => DynamoDBClient,
251+
awsSdkV3ClientPrototype: DynamoDBClient as new (
252+
config?: unknown
253+
) => DynamoDBClient,
252254
...config,
253255
});
254256

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class SecretsProvider extends BaseProvider {
153153
*/
154154
public constructor(config?: SecretsProviderOptions) {
155155
super({
156-
proto: SecretsManagerClient as new (
156+
awsSdkV3ClientPrototype: SecretsManagerClient as new (
157157
config?: unknown
158158
) => SecretsManagerClient,
159159
...(config ?? {}),

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class SSMProvider extends BaseProvider {
278278
*/
279279
public constructor(config?: SSMProviderOptions) {
280280
super({
281-
proto: SSMClient as new (config?: unknown) => SSMClient,
281+
awsSdkV3ClientPrototype: SSMClient as new (config?: unknown) => SSMClient,
282282
...(config ?? {}),
283283
});
284284
}

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

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
import type { Transform } from '../constants.js';
22

3+
/**
4+
* Options for the BaseProvider class constructor.
5+
*/
6+
type BaseProviderConstructorOptions = {
7+
/**
8+
* AWS SDK v3 client instance to use for operations.
9+
*/
10+
awsSdkV3Client?: unknown;
11+
/**
12+
* Optional configuration to pass during client initialization to customize AWS SDK v3 clients.
13+
*/
14+
clientConfig?: unknown;
15+
/**
16+
* AWS SDK v3 client prototype.
17+
*
18+
* If the `awsSdkV3Client` is not provided, this will be used to create a new client.
19+
*/
20+
awsSdkV3ClientPrototype: new (
21+
config?: unknown
22+
) => unknown;
23+
};
24+
325
/**
426
* Type for the transform option.
527
*/
@@ -79,6 +101,7 @@ interface BaseProviderInterface {
79101
}
80102

81103
export type {
104+
BaseProviderConstructorOptions,
82105
GetOptionsInterface,
83106
GetMultipleOptionsInterface,
84107
BaseProviderInterface,

0 commit comments

Comments
 (0)