import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm' // Explicity setting VM level variables in order to persist // client across different important and instances. // eslint-disable-next-line no-var /** * ${1:Description placeholder} * * @type {*} */ let ssmClient: SSMClient | null = null, secretsClient: SecretsManagerClient | null = null /** * An enumeration representing different types of environments. * @enum {number} * @readonly * @property {number} PlainRemote - Represents a plain remote environment. * @property {number} SecureRemote - Represents a secure remote environment. * @property {number} Local - Represents a local environment. */ export enum EnvironmentType { PlainRemote, SecureRemote, Local, } /** * Represents an environment variable with methods to resolve its value based on the environment type. * @template T - The type of the environment variable value. */ export default class EnvironmentVar { /** * The name of the parameter. */ private readonly paramName: string /** * The type of environment. * @readonly */ private readonly type: EnvironmentType /** * A boolean flag indicating whether the field is optional or not. */ private readonly optional: boolean /** * The prefix used for parameters in the class. * @type {string} */ private readonly paramPrefix: string /** * Constructs a new instance of the EnvironmentVar class. * @param {string} paramName - The name of the environment variable. * @param {EnvironmentType} type - The type of the environment. * @param {boolean} [optional] - Indicates whether the environment variable is optional. * @param {string} [paramPrefix] - The prefix to be added to the environment variable name. */ constructor( paramName: string, type: EnvironmentType, optional?: boolean, paramPrefix = `/creatorco-api-${process.env.STAGE}/env/` /* only allowed process.env */ ) { this.paramName = paramName this.type = type this.optional = !!optional this.paramPrefix = paramPrefix } /** * Resolves the value of the environment variable synchronously. * @returns {T} - The resolved value of the environment variable. * @throws {Error} - If the environment type is not 'Local'. */ public syncResolve(): T { if (this.type == EnvironmentType.Local) return this.getLocalValue() else { throw new Error( "EnvironmentVar syncResolve method can only be called for environments of type 'Local'" ) } } /** * Resolves the value based on the environment type. * @returns {Promise} - The resolved value. */ public async resolve() { if (this.type == EnvironmentType.Local) return this.getLocalValue() else if (this.type == EnvironmentType.SecureRemote) return this.getSecureValue() else return this.getPlainValue() } /** * Retrieves the value of a local environment variable. * @returns {T} - The value of the local environment variable. * @throws {Error} - If the local environment variable is not defined and is not optional. */ private getLocalValue(): T { const value = process.env[this.paramName] if (!value && !this.optional) { throw new Error( `Local environment ${this.paramName} is not defined, please reconfigure the application and redeploy!` ) } return value as T } /** * Retrieves the plain value of a remote environment parameter. * @returns {Promise} - A promise that resolves to the plain value of the parameter. * @throws {Error} - If the parameter cannot be retrieved and is not optional. */ private async getPlainValue(): Promise { if (!ssmClient) ssmClient = new SSMClient() const pName = `${this.paramPrefix}${this.paramName}` try { const data = await ssmClient.send(new GetParameterCommand({ Name: pName })) if (data.Parameter && data.Parameter.Value) return data.Parameter.Value as T } catch (error) { console.error(error) } if (!this.optional) { throw new Error(`Unable to retrieve plain remote env value: ${pName}`) } } /** * Retrieves a secure value from the secrets manager. * @returns {Promise} - A promise that resolves to the secure value. * @throws {Error} - Throws an error if the secure value cannot be retrieved and is not optional. */ private async getSecureValue(): Promise { if (!secretsClient) secretsClient = new SecretsManagerClient() const pName = `${this.paramPrefix}${this.paramName}` try { const data = await secretsClient.send(new GetSecretValueCommand({ SecretId: pName })) if (data.SecretString) return data.SecretString as T } catch (error) { console.error(error) } if (!this.optional) { throw new Error(`Unable to retrieve secure remote env: ${pName}`) } } }