UNPKG

ssm-parameters

Version:

Lightweight AWS Parameter Store wrapper

218 lines (193 loc) 7.23 kB
/* * Copyright (c) 2021 Gustavo Salomão * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { SSM } from 'aws-sdk'; /** * Options for the *SSMParameters*. */ export interface SSMParametersOptions { /** Return decrypted values for secure string parameters */ withDecryption?: boolean; /** Maximum number of seconds the parameters will be considered fresh */ maxAge?: number; /** https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SSM.html */ ssmConfiguration?: SSM.ClientConfiguration; } /** * Options when loading or getting the parameters. */ export interface SSMParametersLoadOptions { /** Ignore any existing cache and load the parameters */ ignoreCache: boolean; } /** * Class responsible to load and cache parameters from the AWS SSM Parameters * Store. */ export class SSMParameters<Parameters extends Record<string, string>> { private readonly DEFAULT_WITH_DECRYPTION = true; private readonly DEFAULT_MAX_AGE_IN_SECONDS = 3600; private readonly MAX_PARAMETERS_PER_REQUEST = 10; private readonly withDecryption: boolean; private readonly maxAge: number; private readonly ssmClient: SSM; private parametersName: Parameters; private parametersValue: Record<string, string | undefined> = {}; private parameterLoaded: Record<string, boolean> = {}; private lastLoadTime: Date | undefined; /** * Create an instance of the *SSMParameters*. * * @example * new SSMParameters({ LogLevel: '/LogLevel' }); * new SSMParameters({ LogLevel: '/LogLevel' }, { maxAge: 60 }); * * @param parameters Map with parameter's keys and parameter's names on AWS. * @param options *SSMParametersOptions* */ constructor(parameters: Parameters, options?: SSMParametersOptions) { this.parametersName = parameters; this.maxAge = options?.maxAge ?? this.DEFAULT_MAX_AGE_IN_SECONDS; this.ssmClient = new SSM(options?.ssmConfiguration); this.withDecryption = options?.withDecryption ?? this.DEFAULT_WITH_DECRYPTION; Object.values(parameters).forEach((name: string) => { this.parametersValue[name] = undefined; this.parameterLoaded[name] = false; }); } /** * Load parameters from AWS SSM Parameter Store. * * @note * - If the parameters have not been loaded yet, they will be loaded. * - If the parameters have been loaded and cache didn't expired yet, * it uses the cache. * - If the parameters have been loaded but the cache has expired, it loads * the parameters again. * * @param options *SSMParametersLoadOptions* * @returns Promise to when parameters have been loaded or cache is used. */ async load( options: SSMParametersLoadOptions = { ignoreCache: false }, ): Promise<void> { const cacheAge = this.lastLoadTime ? Math.round((new Date().getTime() - this.lastLoadTime.getTime()) / 1000) : this.maxAge + 1; if (!options.ignoreCache && this.maxAge && cacheAge <= this.maxAge) { return Promise.resolve(); } Object.entries(this.parameterLoaded).forEach((param) => (param[1] = false)); const parametersToLoad = this.getParametersToLoad(); return this.loadParameters(parametersToLoad); } /** * Get parameter. * * @note * - If the parameters have not been loaded yet, they will be loaded. * - If the parameters have been loaded and cache didn't expired yet, * it uses the cache. * - If the parameters have been loaded but the cache has expired, it loads * the parameters again. * * @param key Key of the parameter. * @param options *SSMParametersLoadOptions* * @returns Promise to parameter's value or undefined if it does not exist. */ async get( key: keyof Parameters, options: SSMParametersLoadOptions = { ignoreCache: false }, ): Promise<string | undefined> { return this.load(options).then( () => this.parametersValue[this.parametersName[key]], ); } /** * Get parameters. * * @note * - If the parameters have not been loaded yet, they will be loaded. * - If the parameters have been loaded and cache didn't expired yet, * it uses the cache. * - If the parameters have been loaded but the cache has expired, it loads * the parameters again. * * @param options *SSMParametersLoadOptions* * @returns Promise to all parameters. */ async getAll( options: SSMParametersLoadOptions = { ignoreCache: false }, ): Promise<Record<keyof Parameters, string | undefined>> { return this.load(options).then(() => { const response = {} as Record<keyof Parameters, string | undefined>; Object.entries(this.parametersName).forEach(([name, value]) => { response[name as keyof Parameters] = this.parametersValue[value]; }); return response; }); } /** * Get parameters to load. * * @note The array returned by this function contains a maximum number of * parameters defined by `MAX_PARAMETERS_PER_REQUEST`. * * @returns Array of parameters to load. */ private getParametersToLoad(): string[] { return Object.entries(this.parameterLoaded) .filter((param) => !param[1]) .map((param) => param[0]) .slice(0, this.MAX_PARAMETERS_PER_REQUEST); } /** * Load parameters from AWS SSM. * * @param parameters Parameters to load from AWS SSM. * @returns Promise to when parameters have been loaded. */ private async loadParameters(parameters: string[]): Promise<void> { const ssmRequest: SSM.Types.GetParametersRequest = { Names: parameters, WithDecryption: this.withDecryption, }; return this.ssmClient .getParameters(ssmRequest) .promise() .then((response) => { response.Parameters!.forEach((param) => { this.parametersValue[param.Name!] = param.Value; }); parameters.forEach((param) => { this.parameterLoaded[param] = true; }); parameters = this.getParametersToLoad(); if (parameters.length) { return this.loadParameters(parameters); } this.lastLoadTime = new Date(); return Promise.resolve(); }); } }