UNPKG

@middy/ssm

Version:

SSM (EC2 Systems Manager) parameters middleware for the middy framework

199 lines (178 loc) 5.22 kB
import { GetParametersByPathCommand, GetParametersCommand, SSMClient, } from "@aws-sdk/client-ssm"; import { canPrefetch, catchInvalidSignatureException, createClient, createPrefetchClient, getCache, getInternal, jsonSafeParse, modifyCache, processCache, sanitizeKey, } from "../util/index.js"; const awsRequestLimit = 10; const defaults = { AwsClient: SSMClient, // Allow for XRay awsClientOptions: {}, awsClientAssumeRole: undefined, awsClientCapture: undefined, fetchData: {}, // { contextKey: fetchKey, contextPrefix: fetchPath/ } disablePrefetch: false, cacheKey: "ssm", cacheKeyExpiry: {}, cacheExpiry: -1, setToContext: false, }; const ssmMiddleware = (opts = {}) => { const options = { ...defaults, ...opts }; const fetchRequest = (request, cachedValues) => { return { ...fetchSingleRequest(request, cachedValues), ...fetchByPathRequest(request, cachedValues), }; }; const fetchSingleRequest = (request, cachedValues = {}) => { const values = {}; let batchReq = null; let batchInternalKeys = []; let batchFetchKeys = []; const namedKeys = []; const internalKeys = Object.keys(options.fetchData); const fetchKeys = Object.values(options.fetchData); for (const internalKey of internalKeys) { if (cachedValues[internalKey]) continue; if (options.fetchData[internalKey].substr(-1) === "/") continue; // Skip path passed in namedKeys.push(internalKey); } for (const [idx, internalKey] of namedKeys.entries()) { const fetchKey = options.fetchData[internalKey]; batchInternalKeys.push(internalKey); batchFetchKeys.push(fetchKey); // from the first to the batch size skip, unless it's the last entry if ( (!idx || (idx + 1) % awsRequestLimit !== 0) && !(idx + 1 === namedKeys.length) ) { continue; } const command = new GetParametersCommand({ Names: batchFetchKeys, WithDecryption: true, }); batchReq = client .send(command) .catch((e) => catchInvalidSignatureException(e, client, command)) .then((resp) => { // Don't sanitize key, mapped to set value in options return Object.assign( ...(resp.InvalidParameters ?? []).map((fetchKey) => { return { [fetchKey]: new Promise(() => { const internalKey = internalKeys[fetchKeys.indexOf(fetchKey)]; const value = getCache(options.cacheKey).value ?? {}; value[internalKey] = undefined; modifyCache(options.cacheKey, value); throw new Error(`InvalidParameter ${fetchKey}`, { cause: { package: "@middy/ssm" }, }); }), }; }), ...(resp.Parameters ?? []).map((param) => { return { [param.Name]: parseValue(param) }; }), ); }) .catch((e) => { const value = getCache(options.cacheKey).value ?? {}; value[internalKey] = undefined; modifyCache(options.cacheKey, value); throw e; }); for (const internalKey of batchInternalKeys) { values[internalKey] = batchReq.then((params) => { return params[options.fetchData[internalKey]]; }); } batchInternalKeys = []; batchFetchKeys = []; batchReq = null; } return values; }; const fetchByPathRequest = (request, cachedValues = {}) => { const values = {}; for (const internalKey in options.fetchData) { if (cachedValues[internalKey]) continue; const fetchKey = options.fetchData[internalKey]; if (fetchKey.substr(-1) !== "/") continue; // Skip not path passed in values[internalKey] = fetchPathRequest(fetchKey).catch((e) => { const value = getCache(options.cacheKey).value ?? {}; value[internalKey] = undefined; modifyCache(options.cacheKey, value); throw e; }); } return values; }; const fetchPathRequest = (path, nextToken, values = {}) => { const command = new GetParametersByPathCommand({ Path: path, NextToken: nextToken, Recursive: true, WithDecryption: true, }); return client .send(command) .catch((e) => catchInvalidSignatureException(e, client, command)) .then((resp) => { Object.assign( values, ...resp.Parameters.map((param) => { return { [sanitizeKey(param.Name.replace(path, ""))]: parseValue(param), }; }), ); if (resp.NextToken) { return fetchPathRequest(path, resp.NextToken, values); } return values; }); }; const parseValue = (param) => { if (param.Type === "StringList") { return param.Value.split(","); } return jsonSafeParse(param.Value); }; let client; if (canPrefetch(options)) { client = createPrefetchClient(options); processCache(options, fetchRequest); } const ssmMiddlewareBefore = async (request) => { if (!client) { client = await createClient(options, request); } const { value } = processCache(options, fetchRequest, request); Object.assign(request.internal, value); if (options.setToContext) { const data = await getInternal(Object.keys(options.fetchData), request); Object.assign(request.context, data); } }; return { before: ssmMiddlewareBefore, }; }; export default ssmMiddleware; // used for TS type inference (see index.d.ts) export function ssmParam(name) { return name; }