@aws-lambda-powertools/parameters
Version:
The parameters package for the Powertools for AWS Lambda (TypeScript) library
738 lines (737 loc) • 34.5 kB
JavaScript
import { GetParameterCommand, GetParametersCommand, PutParameterCommand, SSMClient, paginateGetParametersByPath, } from '@aws-sdk/client-ssm';
import { BaseProvider } from '../base/BaseProvider.js';
import { transformValue } from '../base/transformValue.js';
import { DEFAULT_MAX_AGE_SECS } from '../constants.js';
import { GetParameterError, SetParameterError } from '../errors.js';
/**
* ## Intro
* The Parameters utility provides a SSMProvider that allows to retrieve parameters from AWS Systems Manager.
*
* ## Getting started
*
* This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-ssm`). This allows the utility to be modular, and you to install only
* the SDK packages you need and keep your bundle size small.
*
* ## Basic usage
*
* Retrieve a parameter from SSM:
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter from SSM
* const parameter = await parametersProvider.get('/my-parameter');
* };
* ```
*
* If you want to retrieve a parameter without customizing the provider, you can use the {@link getParameter} function instead.
*
* You can also retrieve parameters at once. If you want to get multiple parameters under the same path, you can use the `getMultiple` method.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve multiple parameters by path from SSM
* const parameters = await parametersProvider.getMultiple('/my-parameters-path');
* };
* ```
*
* If you don't need to customize the provider, you can also use the {@link getParameters} function instead.
*
* If instead you want to retrieve multiple parameters by name, you can use the `getParametersByName` method.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve multiple parameters by name from SSM
* const parameters = await parametersProvider.getParametersByName({
* '/my-parameter-1': {}, // Use default options
* '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON
* });
* };
* ```
*
* If you don't need to customize the provider, you can also use the {@link getParametersByName} function instead.
*
* ## Advanced usage
*
* ### Caching
*
* By default, the provider will cache parameters retrieved in-memory for 5 seconds.
* You can adjust how long values should be kept in cache by using the `maxAge` parameter.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter and cache it for 10 seconds
* const parameter = await parametersProvider.get('/my-parameter', { maxAge: 10 });
* // Retrieve multiple parameters by path and cache them for 20 seconds
* const parameters = await parametersProvider.getMultiple('/my-parameters-path', { maxAge: 20 });
* };
* ```
*
* When using the `getParametersByName` method, you can set a different `maxAge` for each parameter or set a default `maxAge` for all parameters.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve multiple parameters by name and cache them individually
* const parameters = await parametersProvider.getParametersByName({
* '/my-parameter-1': { maxAge: 10 }, // Cache for 10 seconds
* '/my-parameter-2': { maxAge: 20 }, // Cache for 20 seconds
* });
* // Retrieve multiple parameters by name and cache them all for 20 seconds
* const parameters = await parametersProvider.getParametersByName({
* '/my-parameter-1': {},
* '/my-parameter-2': {},
* }, { maxAge: 20 });
* };
* ```
*
* If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter and skip cache
* const parameter = await parametersProvider.get('/my-parameter', { forceFetch: true });
* // Retrieve multiple parameters and skip cache
* const parameters = await parametersProvider.getMultiple('/my-parameters-path', { forceFetch: true });
* };
* ```
*
* Likewise, you can use the `forceFetch` parameter with the `getParametersByName` method both for individual parameters and for all parameters.
*
* ### Decryption
*
* If you want to retrieve a parameter that is encrypted, you can use the `decrypt` parameter. This parameter is compatible with `get`, `getMultiple` and `getParametersByName`.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter and decrypt it
* const parameter = await parametersProvider.get('/my-parameter', { decrypt: true });
* // Retrieve multiple parameters and decrypt them
* const parameters = await parametersProvider.getMultiple('/my-parameters-path', { decrypt: true });
* };
* ```
*
* ### Transformations
*
* For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter and parse it as JSON
* const parameter = await parametersProvider.get('/my-parameter', { transform: 'json' });
* // Retrieve multiple parameters and parse them as JSON
* const parameters = await parametersProvider.getMultiple('/my-parameters-path', { transform: 'json' });
* };
* ```
*
* For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a base64-encoded string and decode it
* const parameter = await parametersProvider.get('/my-parameter', { transform: 'binary' });
* // Retrieve multiple base64-encoded strings and decode them
* const parameters = await parametersProvider.getMultiple('/my-parameters-path', { transform: 'binary' });
* };
* ```
*
* Both type of transformations are compatible also with the `getParametersByName` method.
*
* ### Extra SDK options
*
* When retrieving parameters, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter and pass extra options to the AWS SDK v3 for JavaScript client
* const parameter = await parametersProvider.get('/my-parameter', {
* sdkOptions: {
* WithDecryption: true,
* },
* });
* };
* ```
*
* The objects accept the same options as respectively the [AWS SDK v3 for JavaScript GetParameter command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametercommand.html) and the [AWS SDK v3 for JavaScript GetParametersByPath command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametersbypathcommand.html).
*
* ### Customize AWS SDK v3 for JavaScript client
*
* By default, the provider will create a new SSM client using the default configuration.
*
* You can customize the client by passing a custom configuration object to the provider.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider({
* clientConfig: { region: 'eu-west-1' },
* });
* ```
*
* This object accepts the same options as the [AWS SDK v3 for JavaScript SSM client constructor](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/ssmclient.html#constructor).
*
* Otherwise, if you want to use a custom client altogether, you can pass it to the provider.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
* import { SSMClient } from '@aws-sdk/client-ssm';
*
* const client = new SSMClient({ region: 'eu-west-1' });
* const parametersProvider = new SSMProvider({
* awsSdkV3Client: client,
* });
* ```
*
* This object must be an instance of the [AWS SDK v3 for JavaScript SSM client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/ssmclient.html).
*
* For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/parameters/).
*/
class SSMProvider extends BaseProvider {
errorsKey = '_errors';
maxGetParametersItems = 10;
/**
* It initializes the SSMProvider class.
*
* @param {SSMProviderOptions} config - The configuration object.
*/
constructor(config) {
super({
awsSdkV3ClientPrototype: SSMClient,
...(config ?? {}),
});
}
/**
* Retrieve a value from AWS Systems Manager.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve a parameter from SSM
* const parameter = await parametersProvider.get('/my-parameter');
* };
* ```
*
* You can customize the retrieval of the value by passing options to the function:
* * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5)
* * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache
* * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary`
* * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client
* * `decrypt` - Whether to decrypt the value before returning it.
*
* For usage examples check {@link SSMProvider}.
*
* @param {string} name - The name of the value to retrieve (i.e. the partition key)
* @param {SSMGetOptions} options - Options to configure the provider
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/parameters/
*/
async get(name, options) {
return super.get(name, options);
}
/**
* Sets a parameter in AWS Systems Manager (SSM).
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Set a parameter in SSM
* const version = await parametersProvider.set('/my-parameter', { value: 'my-value' });
* console.log(`Parameter version: ${version}`);
* };
* ```
*
* You can customize the storage of the value by passing options to the function:
* * `value` - The value of the parameter, which is a mandatory option.
* * `overwrite` - Whether to overwrite the value if it already exists (default: `false`)
* * `description` - The description of the parameter
* * `parameterType` - The type of the parameter, can be one of `String`, `StringList`, or `SecureString` (default: `String`)
* * `tier` - The parameter tier to use, can be one of `Standard`, `Advanced`, and `Intelligent-Tiering` (default: `Standard`)
* * `kmsKeyId` - The KMS key id to use to encrypt the parameter
* * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client
*
* @param {string} name - The name of the parameter
* @param {SSMSetOptions} options - Options to configure the parameter
* @returns {Promise<number>} The version of the parameter
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/parameters/
*/
async set(name, options) {
const sdkOptions = {
Tier: options.tier ?? 'Standard',
Type: options.parameterType ?? 'String',
Overwrite: options.overwrite ?? false,
...(options.kmsKeyId ? { KeyId: options.kmsKeyId } : {}),
...(options.description ? { Description: options.description } : {}),
...(options?.sdkOptions ?? {}),
Name: name,
Value: options.value,
};
let result;
try {
result = await this.client.send(new PutParameterCommand(sdkOptions));
}
catch (error) {
throw new SetParameterError(`Unable to set parameter with name ${name}`, {
cause: error,
});
}
return result.Version;
}
/**
* Retrieve multiple values from AWS Systems Manager.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve multiple parameters from SSM
* const parameters = await parametersProvider.getMultiple('/my-parameters-path');
* };
* ```
*
* You can customize the retrieval of the values by passing options to the function:
* * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5)
* * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache
* * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary`
* * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client
* * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`)
* * `decrypt` - Whether to decrypt the value before returning it.
* * `recursive` - Whether to recursively retrieve all parameters under the given path (default: `false`)
*
* For usage examples check {@link SSMProvider}.
*
* @param {string} path - The path of the parameters to retrieve
* @param {SSMGetMultipleOptions} options - Options to configure the retrieval
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/parameters/
*/
async getMultiple(path, options) {
return super.getMultiple(path, options);
}
/**
* Retrieve multiple parameters by name from AWS Systems Manager.
*
* @example
* ```typescript
* import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
*
* const parametersProvider = new SSMProvider();
*
* export const handler = async (): Promise<void> => {
* // Retrieve multiple parameters by name from SSM
* const parameters = await parametersProvider.getParametersByName({
* '/my-parameter-1': {}, // Use default options
* '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON
* });
* };
* ```
* You can customize the retrieval of the values by passing options to **both the function and the parameter**:
* * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5)
* * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache
* * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary`
* * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client
* * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`)
* * `decrypt` - Whether to decrypt the value before returning it
*
* `throwOnError` decides whether to throw an error if a parameter is not found:
* - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure.
* - B) Gracefully aggregate all parameters that failed under "_errors" key.
*
* It transparently uses GetParameter and/or GetParameters depending on decryption requirements.
*
* ```sh
* ┌────────────────────────┐
* ┌───▶ Decrypt entire batch │─────┐
* │ └────────────────────────┘ │ ┌────────────────────┐
* │ ├─────▶ GetParameters API │
* ┌──────────────────┐ │ ┌────────────────────────┐ │ └────────────────────┘
* │ Split batch │─── ┼──▶│ No decryption required │─────┘
* └──────────────────┘ │ └────────────────────────┘
* │ ┌────────────────────┐
* │ ┌────────────────────────┐ │ GetParameter API │
* └──▶│Decrypt some but not all│───────────▶────────────────────┤
* └────────────────────────┘ │ GetParameters API │
* └────────────────────┘
* ```
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - Object containing parameter names and any optional overrides
* @param {SSMGetParametersByNameOptions} options - Options to configure the retrieval
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/parameters/
*/
async getParametersByName(parameters, options) {
const configs = {
...{
decrypt: this.resolveDecryptionConfigValue({}) || false,
maxAge: DEFAULT_MAX_AGE_SECS,
throwOnError: true,
},
...options,
};
let response = {};
// NOTE: We fail early to avoid unintended graceful errors being replaced with their '_errors' param values
SSMProvider.throwIfErrorsKeyIsPresent(parameters, this.errorsKey, configs.throwOnError);
const { parametersToFetchInBatch, parametersToDecrypt } = SSMProvider.splitBatchAndDecryptParameters(parameters, configs);
// NOTE: We need to find out whether all parameters must be decrypted or not to know which API to use
// Logic:
// GetParameters API -> When decrypt is used for all parameters in the the batch
// GetParameter API -> When decrypt is used for one or more in the batch
if (Object.keys(parametersToDecrypt).length !== Object.keys(parameters).length) {
const { response: decryptResponse, errors: decryptErrors } = await this.getParametersByNameWithDecryptOption(parametersToDecrypt, configs.throwOnError);
const { response: batchResponse, errors: batchErrors } = await this.getParametersBatchByName(parametersToFetchInBatch, configs.throwOnError, false);
response = { ...decryptResponse, ...batchResponse };
// Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully
if (!configs.throwOnError) {
response[this.errorsKey] = [...decryptErrors, ...batchErrors];
}
}
else {
const { response: batchResponse, errors: batchErrors } = await this.getParametersBatchByName(parametersToDecrypt, configs.throwOnError, true);
response = batchResponse;
// Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully
if (!configs.throwOnError) {
response[this.errorsKey] = [...batchErrors];
}
}
return response;
}
/**
* Retrieve a parameter from AWS Systems Manager.
*
* @param {string} name - Name of the parameter to retrieve
* @param {SSMGetOptions} options - Options to customize the retrieval
*/
async _get(name, options) {
const sdkOptions = {
...(options?.sdkOptions || {}),
Name: name,
};
sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions);
const result = await this.client.send(new GetParameterCommand(sdkOptions));
return result.Parameter?.Value;
}
/**
* Retrieve multiple items from AWS Systems Manager.
*
* @param {string} path - The path of the parameters to retrieve
* @param {SSMGetMultipleOptions} options - Options to configure the provider
*/
async _getMultiple(path, options) {
const sdkOptions = {
...(options?.sdkOptions || {}),
Path: path,
};
const paginationOptions = {
client: this.client,
};
sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions);
sdkOptions.Recursive =
options?.recursive !== undefined
? options.recursive
: sdkOptions.Recursive;
paginationOptions.pageSize =
sdkOptions.MaxResults !== undefined ? sdkOptions.MaxResults : undefined;
const parameters = {};
for await (const page of paginateGetParametersByPath(paginationOptions, sdkOptions)) {
for (const parameter of page.Parameters || []) {
/**
* Standardize the parameter name
*
* The parameter name returned by SSM will contain the full path.
* However, for readability, we should return only the part after the path.
**/
// biome-ignore lint/style/noNonNullAssertion: If the parameter is present in the response, then it has a Name
let name = parameter.Name;
name = name.replace(path, '');
if (name.startsWith('/')) {
name = name.replace('/', '');
}
parameters[name] = parameter.Value;
}
}
return parameters;
}
/**
* Retrieve multiple items by name from AWS Systems Manager.
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
* @param {boolean} decrypt - Whether to decrypt the parameters or not
*/
async _getParametersByName(parameters, throwOnError, decrypt) {
const sdkOptions = {
Names: Object.keys(parameters),
};
if (decrypt) {
sdkOptions.WithDecryption = true;
}
const result = await this.client.send(new GetParametersCommand(sdkOptions));
const errors = SSMProvider.handleAnyInvalidGetParameterErrors(result, throwOnError);
const response = this.transformAndCacheGetParametersResponse(result, parameters, throwOnError);
return {
response,
errors,
};
}
/**
* Slice batch and fetch parameters using GetPrameters API by max permissible batch size
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
* @param {boolean} decrypt - Whether to decrypt the parameters or not
*/
async getParametersBatchByName(parameters, throwOnError, decrypt) {
let response = {};
let errors = [];
// Fetch each possible batch param from cache and return if entire batch is cached
const { cached, toFetch } = await this.getParametersByNameFromCache(parameters);
if (Object.keys(cached).length >= Object.keys(parameters).length) {
response = cached;
return {
response,
errors,
};
}
// Slice batch by max permitted GetParameters call and retrieve the ones that are not cached
const { response: batchResponse, errors: batchErrors } = await this.getParametersByNameInChunks(toFetch, throwOnError, decrypt);
response = { ...cached, ...batchResponse };
errors = batchErrors;
return {
response,
errors,
};
}
/**
* Fetch each parameter from batch that hasn't expired from cache
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
*/
async getParametersByNameFromCache(parameters) {
const cached = {};
const toFetch = {};
for (const [parameterName, parameterOptions] of Object.entries(parameters)) {
const cacheKey = [parameterName, parameterOptions.transform].toString();
if (!this.hasKeyExpiredInCache(cacheKey)) {
// biome-ignore lint/style/noNonNullAssertion: Since we know the key exists in the cache, we can safely use the non-null assertion operator
cached[parameterName] = this.store.get(cacheKey).value;
}
else {
toFetch[parameterName] = parameterOptions;
}
}
return {
cached,
toFetch,
};
}
/**
* Slice object into chunks of max permissible batch size and fetch parameters
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
* @param {boolean} decrypt - Whether to decrypt the parameters or not
*/
async getParametersByNameInChunks(parameters, throwOnError, decrypt) {
let response = {};
let errors = [];
// Slice object into chunks of max permissible batch size
const chunks = Object.entries(parameters).reduce((acc, [parameterName, parameterOptions], index) => {
const chunkIndex = Math.floor(index / this.maxGetParametersItems);
if (!acc[chunkIndex]) {
acc[chunkIndex] = {};
}
acc[chunkIndex][parameterName] = parameterOptions;
return acc;
}, []);
// Fetch each chunk and merge results
for (const chunk of chunks) {
const { response: chunkResponse, errors: chunkErrors } = await this._getParametersByName(chunk, throwOnError, decrypt);
response = { ...response, ...chunkResponse };
errors = [...errors, ...chunkErrors];
}
return {
response,
errors,
};
}
/**
* Fetch parameters by name while also decrypting them
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
*/
async getParametersByNameWithDecryptOption(parameters, throwOnError) {
const response = {};
const errors = [];
for (const [parameterName, parameterOptions] of Object.entries(parameters)) {
try {
response[parameterName] = await this._get(parameterName, parameterOptions);
}
catch (error) {
if (throwOnError) {
throw error;
}
errors.push(parameterName);
}
}
return {
response,
errors,
};
}
/**
* Handle any invalid parameters returned by GetParameters API
* GetParameters is non-atomic. Failures don't always reflect in exceptions so we need to collect.
*
* @param {GetParametersCommandOutput} result - The result of the GetParameters API call
* @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
*/
static handleAnyInvalidGetParameterErrors(result, throwOnError) {
const errors = [];
if (result.InvalidParameters && result.InvalidParameters.length > 0) {
if (throwOnError) {
throw new GetParameterError(`Failed to fetch parameters: ${result.InvalidParameters.join(', ')}`);
}
errors.push(...result.InvalidParameters);
}
return errors;
}
resolveDecryptionConfigValue(options = {}, sdkOptions) {
if (options?.decrypt !== undefined)
return options.decrypt;
if (sdkOptions?.WithDecryption !== undefined)
return sdkOptions.WithDecryption;
if (this.envVarsService.getSSMDecrypt() !== '') {
return this.envVarsService.isValueTrue(this.envVarsService.getSSMDecrypt());
}
return undefined;
}
/**
* Split parameters that can be fetched by GetParameters vs GetParameter.
*
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {SSMGetParametersByNameOptions} configs - The configs passed down
*/
static splitBatchAndDecryptParameters(parameters, configs) {
const parametersToFetchInBatch = {};
const parametersToDecrypt = {};
for (const [parameterName, parameterOptions] of Object.entries(parameters)) {
const overrides = parameterOptions;
overrides.transform = overrides.transform || configs.transform;
overrides.decrypt =
overrides.decrypt !== undefined ? overrides.decrypt : configs.decrypt;
overrides.maxAge =
overrides.maxAge !== undefined ? overrides.maxAge : configs.maxAge;
if (overrides.decrypt) {
parametersToDecrypt[parameterName] = overrides;
}
else {
parametersToFetchInBatch[parameterName] = overrides;
}
}
return {
parametersToFetchInBatch,
parametersToDecrypt,
};
}
/**
* Throw a GetParameterError if fail-fast is disabled and `_errors` key is in parameters list.
*
* @param {Record<string, unknown>} parameters
* @param {string} reservedParameter
* @param {boolean} throwOnError
*/
static throwIfErrorsKeyIsPresent(parameters, reservedParameter, throwOnError) {
if (!throwOnError && Object.hasOwn(parameters, reservedParameter)) {
throw new GetParameterError(`You cannot fetch a parameter named ${reservedParameter} in graceful error mode.`);
}
}
/**
* Transform and cache the response from GetParameters API call
*
* @param {GetParametersCommandOutput} response - The response from the GetParameters API call
* @param {Record<string, SSMGetParametersByNameOptions>} parameters - An object of parameter names and their options
* @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully
*/
transformAndCacheGetParametersResponse(response, parameters, throwOnError) {
const processedParameters = {};
for (const parameter of response.Parameters || []) {
// biome-ignore lint/style/noNonNullAssertion: If the parameter is present in the response, then it has a Name
const parameterName = parameter.Name;
const parameterValue = parameter.Value;
const parameterOptions = parameters[parameterName];
let value;
// NOTE: if transform is set, we do it before caching to reduce number of operations
if (parameterValue && parameterOptions.transform) {
value = transformValue(parameterValue, parameterOptions.transform, throwOnError, parameterName);
}
else if (parameterValue) {
value = parameterValue;
}
if (value) {
const cacheKey = [parameterName, parameterOptions.transform].toString();
this.addToCache(cacheKey, value, parameterOptions.maxAge || DEFAULT_MAX_AGE_SECS);
}
processedParameters[parameterName] = value;
}
return processedParameters;
}
}
export { SSMProvider };