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); + } +} 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 bbaeff91c1..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,27 +45,25 @@ 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)) { + 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 { - this.client = new proto(clientConfig ?? {}); + } else if (awsSdkV3ClientPrototype) { + this.client = new awsSdkV3ClientPrototype(clientConfig ?? {}); + } + if (isSdkClient(this.client)) { + addUserAgentMiddleware(this.client, 'parameters'); } - 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,