Skip to content

chore(parameters): update AWS SDK client type #3768

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

Merged
merged 7 commits into from
Mar 25, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/utilities/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ 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"
```typescript hl_lines="12"
--8<-- "examples/snippets/parameters/customProviderVaultUsage.ts"
```

Expand Down
1 change: 0 additions & 1 deletion examples/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"@middy/core": "^4.7.0",
"aws-sdk": "^2.1692.0",
"aws-sdk-client-mock": "^4.1.0",
"hashi-vault-js": "^0.4.16",
"zod": "^3.24.2"
}
}
69 changes: 34 additions & 35 deletions examples/snippets/parameters/customProviderVault.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
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,
HashiCorpVaultProviderOptions,
} from './customProviderVaultTypes.js';

class HashiCorpVaultProvider extends BaseProvider {
public client: Vault;
readonly #baseUrl: string;
readonly #token: string;
readonly #rootPath?: string;
readonly #timeout: number;
readonly #abortController: AbortController;

/**
* It initializes the HashiCorpVaultProvider class.
*
* @param {HashiCorpVaultProviderOptions} config - The configuration object.
* @param config - The configuration object.
*/
public constructor(config: HashiCorpVaultProviderOptions) {
super({});

const { url, token, clientConfig, vaultClient } = config;
if (vaultClient) {
if (vaultClient instanceof Vault) {
this.client = vaultClient;
} else {
throw Error('Not a valid Vault client provided');
}
} else {
const config = {
baseUrl: url,
...(clientConfig ?? {
timeout: 10000,
rootPath: '',
}),
};
this.client = new Vault(config);
}
const { url, token, rootPath, timeout } = config;
this.#baseUrl = url;
this.#rootPath = rootPath ?? 'secret';
this.#timeout = timeout ?? 5000;
this.#token = token;
this.#abortController = new AbortController();
}

/**
Expand All @@ -46,8 +36,8 @@ class HashiCorpVaultProvider extends BaseProvider {
* * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache
* * `sdkOptions` - Extra options to pass to the HashiCorp Vault SDK, e.g. `mount` or `version`
*
* @param {string} name - The name of the secret
* @param {HashiCorpVaultGetOptions} options - Options to customize the retrieval of the secret
* @param name - The name of the secret
* @param options - Options to customize the retrieval of the secret
*/
public async get<T extends Record<string, unknown>>(
name: string,
Expand All @@ -68,27 +58,36 @@ class HashiCorpVaultProvider extends BaseProvider {
/**
* Retrieve a secret from HashiCorp Vault.
*
* @param {string} name - The name of the secret
* @param {HashiCorpVaultGetOptions} options - Options to customize the retrieval of the secret
* @param name - The name of the secret
* @param options - Options to customize the retrieval of the secret
*/
protected async _get(
name: string,
options?: HashiCorpVaultGetOptions
): Promise<Record<string, unknown>> {
const mount = options?.sdkOptions?.mount ?? 'secret';
const version = options?.sdkOptions?.version;
const { sdkOptions } = options ?? {};
const mount = sdkOptions?.mount ?? this.#rootPath;
const version = sdkOptions?.version
? `?version=${sdkOptions?.version}`
: '';

const response = await this.client.readKVSecret(
this.#token,
name,
version,
mount
);
setTimeout(() => {
this.#abortController.abort();
}, this.#timeout);

if (response.isVaultError) {
throw response;
const res = await fetch(
`${this.#baseUrl}/${mount}/data/${name}${version}`,
{
headers: { 'X-Vault-Token': this.#token },
method: 'GET',
signal: this.#abortController.signal,
}
);
if (!res.ok) {
throw new GetParameterError(`Failed to fetch secret ${res.statusText}`);
}
return response.data;
const response = await res.json();
return response.data.data;
}

/**
Expand Down
57 changes: 12 additions & 45 deletions examples/snippets/parameters/customProviderVaultTypes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { GetOptionsInterface } from '@aws-lambda-powertools/parameters/base/types';
import type Vault from 'hashi-vault-js';

/**
* Base interface for HashiCorpVaultProviderOptions.
* @interface
* Options for the HashiCorpVaultProvider class constructor.
*
* @param {string} url - Indicate the server name/IP, port and API version for the Vault instance, all paths are relative to this one.
* @param {string} token - The Vault token to use for authentication.
*
*/
interface HashiCorpVaultProviderOptionsBase {
interface HashiCorpVaultProviderOptions {
/**
* Indicate the server name/IP, port and API version for the Vault instance, all paths are relative to this one.
* @example 'https://vault.example.com:8200/v1'
Expand All @@ -15,53 +17,18 @@ interface HashiCorpVaultProviderOptionsBase {
* The Vault token to use for authentication.
*/
token: string;
}

/**
* Interface for HashiCorpVaultProviderOptions with clientConfig property.
* @interface
*/
interface HashiCorpVaultProviderOptionsWithClientConfig
extends HashiCorpVaultProviderOptionsBase {
/**
* Optional configuration to pass during client initialization to customize the `hashi-vault-js` client.
* The root path to use for the secret engine. Defaults to `secret`.
*/
clientConfig?: unknown;
rootPath?: string;
/**
* This property should never be passed.
* The timeout in milliseconds for the HTTP requests. Defaults to `5000`.
* @example 10000
* @default 5000
*/
vaultClient?: never;
timeout?: number;
}

/**
* Interface for HashiCorpVaultProviderOptions with vaultClient property.
*
* @interface
*/
interface HashiCorpVaultProviderOptionsWithClientInstance
extends HashiCorpVaultProviderOptionsBase {
/**
* Optional `hashi-vault-js` client to pass during HashiCorpVaultProvider class instantiation. If not provided, a new client will be created.
*/
vaultClient?: Vault;
/**
* This property should never be passed.
*/
clientConfig: never;
}

/**
* Options for the HashiCorpVaultProvider class constructor.
*
* @param {string} url - Indicate the server name/IP, port and API version for the Vault instance, all paths are relative to this one.
* @param {string} token - The Vault token to use for authentication.
* @param {Vault.VaultConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. timeout. Mutually exclusive with vaultClient.
* @param {Vault} [vaultClient] - Optional `hashi-vault-js` client to pass during HashiCorpVaultProvider class instantiation. Mutually exclusive with clientConfig.
*/
type HashiCorpVaultProviderOptions =
| HashiCorpVaultProviderOptionsWithClientConfig
| HashiCorpVaultProviderOptionsWithClientInstance;

type HashiCorpVaultReadKVSecretOptions = {
/**
* The mount point of the secret engine to use. Defaults to `secret`.
Expand Down
31 changes: 14 additions & 17 deletions examples/snippets/parameters/customProviderVaultUsage.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { Logger } from '@aws-lambda-powertools/logger';
import { HashiCorpVaultProvider } from './customProviderVault.js';

const logger = new Logger({ logLevel: 'DEBUG' });
const logger = new Logger({ serviceName: 'serverless-airline' });
const secretsProvider = new HashiCorpVaultProvider({
url: 'https://vault.example.com:8200/v1',
token: process.env.ROOT_TOKEN ?? '',
rootPath: 'kv',
});

try {
// Retrieve a secret from HashiCorp Vault
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);
}
}
// Retrieve a secret from HashiCorp Vault
const secret = await secretsProvider.get<{ foo: 'string' }>('my-secret');

const res = await fetch('https://example.com/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${secret?.foo}`,
},
body: JSON.stringify({ data: 'example' }),
});
logger.debug('res status', { status: res.status });
13 changes: 0 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/parameters/src/types/BaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type BaseProviderConstructorOptions = {
*
* If the `awsSdkV3Client` is not provided, this will be used to create a new client.
*/
awsSdkV3ClientPrototype: new (
awsSdkV3ClientPrototype?: new (
config?: unknown
) => unknown;
};
Expand Down
Loading