From ce104f4e67ff021554131100932a2e025340c763 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 6 Feb 2025 12:22:17 +0100 Subject: [PATCH 1/3] improv(parser): allow extending base provider with no AWS SDK client --- packages/parameters/src/base/BaseProvider.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/parameters/src/base/BaseProvider.ts b/packages/parameters/src/base/BaseProvider.ts index bbaeff91c1..c335628a28 100644 --- a/packages/parameters/src/base/BaseProvider.ts +++ b/packages/parameters/src/base/BaseProvider.ts @@ -48,12 +48,12 @@ abstract class BaseProvider implements BaseProviderInterface { }: { awsSdkV3Client?: unknown; clientConfig?: unknown; - proto: new (config?: unknown) => unknown; + proto?: new (config?: unknown) => unknown; }) { this.store = new Map(); this.envVarsService = new EnvironmentVariablesService(); if (awsSdkV3Client) { - if (!isSdkClient(awsSdkV3Client)) { + if (!isSdkClient(awsSdkV3Client) && proto) { console.warn( 'awsSdkV3Client is not an AWS SDK v3 client, using default client' ); @@ -61,10 +61,12 @@ abstract class BaseProvider implements BaseProviderInterface { } else { this.client = awsSdkV3Client; } - } else { + } else if (proto) { this.client = new proto(clientConfig ?? {}); } - addUserAgentMiddleware(this.client, 'parameters'); + if (isSdkClient(this.client)) { + addUserAgentMiddleware(this.client, 'parameters'); + } } /** From 82a64c7856a3fd7e8de4321c007d3b68d44ec99d Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 6 Feb 2025 12:50:14 +0100 Subject: [PATCH 2/3] chore: update docs & snippets --- docs/utilities/parameters.md | 10 ++++---- .../parameters/customProviderVault.ts | 23 ++++++------------- .../parameters/customProviderVaultUsage.ts | 23 +++++++++++++++---- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index 9c757e42e5..d35188c9ea 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -297,6 +297,11 @@ All caching logic is handled by the `BaseProvider`, and provided that the return Here's an example of implementing a custom parameter store using an external service like HashiCorp Vault, a widely popular key-value secret storage. +=== "Provider usage" + ```typescript hl_lines="5-8 12-16" + --8<-- "examples/snippets/parameters/customProviderVaultUsage.ts" + ``` + === "Provider implementation" ```typescript --8<-- "examples/snippets/parameters/customProviderVault.ts" @@ -307,11 +312,6 @@ Here's an example of implementing a custom parameter store using an external ser --8<-- "examples/snippets/parameters/customProviderVaultTypes.ts" ``` -=== "Provider usage" - ```typescript - --8<-- "examples/snippets/parameters/customProviderVaultUsage.ts" - ``` - ### Deserializing values with transform parameter For parameters stored in JSON or Base64 format, you can use the `transform` argument for deserialization. diff --git a/examples/snippets/parameters/customProviderVault.ts b/examples/snippets/parameters/customProviderVault.ts index 4b5c061b9c..838f1adc9d 100644 --- a/examples/snippets/parameters/customProviderVault.ts +++ b/examples/snippets/parameters/customProviderVault.ts @@ -1,5 +1,5 @@ -import { Logger } from '@aws-lambda-powertools/logger'; import { BaseProvider } from '@aws-lambda-powertools/parameters/base'; +import { GetParameterError } from '@aws-lambda-powertools/parameters/errors'; import Vault from 'hashi-vault-js'; import type { HashiCorpVaultGetOptions, @@ -9,7 +9,6 @@ import type { class HashiCorpVaultProvider extends BaseProvider { public client: Vault; readonly #token: string; - readonly #logger: Logger; /** * It initializes the HashiCorpVaultProvider class. @@ -17,9 +16,7 @@ class HashiCorpVaultProvider extends BaseProvider { * @param {HashiCorpVaultProviderOptions} config - The configuration object. */ public constructor(config: HashiCorpVaultProviderOptions) { - super({ - proto: Vault, - }); + super({}); const { url, token, clientConfig, vaultClient } = config; if (vaultClient) { @@ -39,9 +36,6 @@ class HashiCorpVaultProvider extends BaseProvider { this.client = new Vault(config); } this.#token = token; - this.#logger = new Logger({ - serviceName: 'HashiCorpVaultProvider', - }); } /** @@ -55,13 +49,13 @@ class HashiCorpVaultProvider extends BaseProvider { * @param {string} name - The name of the secret * @param {HashiCorpVaultGetOptions} options - Options to customize the retrieval of the secret */ - public async get( + public async get>( name: string, options?: HashiCorpVaultGetOptions - ): Promise | undefined> { + ): Promise { return super.get(name, options) as Promise< Record | undefined - >; + > as Promise; } /** @@ -92,9 +86,6 @@ class HashiCorpVaultProvider extends BaseProvider { ); if (response.isVaultError) { - this.#logger.error('An error occurred', { - error: response.vaultHelpMessage, - }); throw response; } return response.data; @@ -108,8 +99,8 @@ class HashiCorpVaultProvider extends BaseProvider { protected async _getMultiple( _path: string, _options?: unknown - ): Promise { - throw new Error('Method not implemented.'); + ): Promise | undefined> { + throw new GetParameterError('Method not implemented.'); } } diff --git a/examples/snippets/parameters/customProviderVaultUsage.ts b/examples/snippets/parameters/customProviderVaultUsage.ts index b86b51e2d8..5ead5b0a46 100644 --- a/examples/snippets/parameters/customProviderVaultUsage.ts +++ b/examples/snippets/parameters/customProviderVaultUsage.ts @@ -1,12 +1,25 @@ +import { Logger } from '@aws-lambda-powertools/logger'; import { HashiCorpVaultProvider } from './customProviderVault.js'; +const logger = new Logger({ logLevel: 'DEBUG' }); const secretsProvider = new HashiCorpVaultProvider({ url: 'https://vault.example.com:8200/v1', - token: 'my-token', + token: process.env.ROOT_TOKEN ?? '', }); -export const handler = async (): Promise => { +try { // Retrieve a secret from HashiCorp Vault - const secret = await secretsProvider.get('my-secret'); - console.log(secret); -}; + const secret = await secretsProvider.get<{ foo: 'string' }>('my-secret', { + sdkOptions: { + mount: 'secrets', + }, + }); + if (!secret) { + throw new Error('Secret not found'); + } + logger.debug('Secret retrieved!'); +} catch (error) { + if (error instanceof Error) { + logger.error(error.message, error); + } +} From a4f948743c8e041907a2e112f187ea4a708e3ecc Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 6 Feb 2025 13:03:14 +0100 Subject: [PATCH 3/3] improv: rename & document parameter for clarity --- .../src/appconfig/AppConfigProvider.ts | 2 +- packages/parameters/src/base/BaseProvider.ts | 17 ++++++-------- .../src/dynamodb/DynamoDBProvider.ts | 4 +++- .../parameters/src/secrets/SecretsProvider.ts | 2 +- packages/parameters/src/ssm/SSMProvider.ts | 2 +- packages/parameters/src/types/BaseProvider.ts | 23 +++++++++++++++++++ 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 74ee59aeab..900c5831f0 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -198,7 +198,7 @@ class AppConfigProvider extends BaseProvider { */ public constructor(options: AppConfigProviderOptions) { super({ - proto: AppConfigDataClient as new ( + awsSdkV3ClientPrototype: AppConfigDataClient as new ( config?: unknown ) => AppConfigDataClient, clientConfig: options.clientConfig, diff --git a/packages/parameters/src/base/BaseProvider.ts b/packages/parameters/src/base/BaseProvider.ts index c335628a28..afeabc69a6 100644 --- a/packages/parameters/src/base/BaseProvider.ts +++ b/packages/parameters/src/base/BaseProvider.ts @@ -8,6 +8,7 @@ import { import { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js'; import { GetParameterError, TransformParameterError } from '../errors.js'; import type { + BaseProviderConstructorOptions, BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, @@ -44,25 +45,21 @@ abstract class BaseProvider implements BaseProviderInterface { public constructor({ awsSdkV3Client, clientConfig, - proto, - }: { - awsSdkV3Client?: unknown; - clientConfig?: unknown; - proto?: new (config?: unknown) => unknown; - }) { + awsSdkV3ClientPrototype, + }: BaseProviderConstructorOptions) { this.store = new Map(); this.envVarsService = new EnvironmentVariablesService(); if (awsSdkV3Client) { - if (!isSdkClient(awsSdkV3Client) && proto) { + if (!isSdkClient(awsSdkV3Client) && awsSdkV3ClientPrototype) { console.warn( 'awsSdkV3Client is not an AWS SDK v3 client, using default client' ); - this.client = new proto(clientConfig ?? {}); + this.client = new awsSdkV3ClientPrototype(clientConfig ?? {}); } else { this.client = awsSdkV3Client; } - } else if (proto) { - this.client = new proto(clientConfig ?? {}); + } else if (awsSdkV3ClientPrototype) { + this.client = new awsSdkV3ClientPrototype(clientConfig ?? {}); } if (isSdkClient(this.client)) { addUserAgentMiddleware(this.client, 'parameters'); diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index 42f3c4c854..3690fa16d7 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -248,7 +248,9 @@ class DynamoDBProvider extends BaseProvider { */ public constructor(config: DynamoDBProviderOptions) { super({ - proto: DynamoDBClient as new (config?: unknown) => DynamoDBClient, + awsSdkV3ClientPrototype: DynamoDBClient as new ( + config?: unknown + ) => DynamoDBClient, ...config, }); diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 1f0e0cb996..ba4ecee562 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -153,7 +153,7 @@ class SecretsProvider extends BaseProvider { */ public constructor(config?: SecretsProviderOptions) { super({ - proto: SecretsManagerClient as new ( + awsSdkV3ClientPrototype: SecretsManagerClient as new ( config?: unknown ) => SecretsManagerClient, ...(config ?? {}), diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index 86c1e571c6..d94a17a101 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -278,7 +278,7 @@ class SSMProvider extends BaseProvider { */ public constructor(config?: SSMProviderOptions) { super({ - proto: SSMClient as new (config?: unknown) => SSMClient, + awsSdkV3ClientPrototype: SSMClient as new (config?: unknown) => SSMClient, ...(config ?? {}), }); } diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index 8ad41705b5..44c3ee417e 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -1,5 +1,27 @@ import type { Transform } from '../constants.js'; +/** + * Options for the BaseProvider class constructor. + */ +type BaseProviderConstructorOptions = { + /** + * AWS SDK v3 client instance to use for operations. + */ + awsSdkV3Client?: unknown; + /** + * Optional configuration to pass during client initialization to customize AWS SDK v3 clients. + */ + clientConfig?: unknown; + /** + * AWS SDK v3 client prototype. + * + * If the `awsSdkV3Client` is not provided, this will be used to create a new client. + */ + awsSdkV3ClientPrototype: new ( + config?: unknown + ) => unknown; +}; + /** * Type for the transform option. */ @@ -79,6 +101,7 @@ interface BaseProviderInterface { } export type { + BaseProviderConstructorOptions, GetOptionsInterface, GetMultipleOptionsInterface, BaseProviderInterface,