UNPKG

envilder

Version:

A CLI and GitHub Action that securely centralizes your environment variables from AWS SSM or Azure Key Vault as a single source of truth

195 lines 9.99 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { inject, injectable } from 'inversify'; import { EnvironmentVariable } from '../../domain/EnvironmentVariable.js'; import { TYPES } from '../../types.js'; let PushEnvToSecretsCommandHandler = class PushEnvToSecretsCommandHandler { constructor(secretProvider, variableStore, logger) { this.secretProvider = secretProvider; this.variableStore = variableStore; this.logger = logger; } /** * Handles the PushEnvToSecretsCommand which imports environment variables * from a local file and pushes them to the secret store. * Uses a map file to determine the secret path for each environment variable. * * @param command - The PushEnvToSecretsCommand containing mapPath and envFilePath */ handle(command) { return __awaiter(this, void 0, void 0, function* () { try { this.logger.info(`Starting push operation from '${command.envFilePath}' using map '${command.mapPath}'`); const config = yield this.loadConfiguration(command); const validatedPaths = this.validateAndGroupByPath(config); yield this.pushParametersToStore(validatedPaths); this.logger.info(`Successfully pushed environment variables from '${command.envFilePath}' to secret store.`); } catch (error) { const errorMessage = this.getErrorMessage(error); this.logger.error(`Failed to push environment file: ${errorMessage}`); throw error; } }); } loadConfiguration(command) { return __awaiter(this, void 0, void 0, function* () { this.logger.info(`Loading parameter map from '${command.mapPath}'`); const paramMap = yield this.variableStore.getMapping(command.mapPath); this.logger.info(`Loading environment variables from '${command.envFilePath}'`); const envVariables = yield this.variableStore.getEnvironment(command.envFilePath); this.logger.info(`Found ${Object.keys(paramMap).length} parameter mappings in map file`); this.logger.info(`Found ${Object.keys(envVariables).length} environment variables in env file`); return { paramMap, envVariables }; }); } /** * Validates and groups environment variables by secret path. * Ensures that all variables pointing to the same secret path have the same value. * Returns a map of secret path to value. */ validateAndGroupByPath(config) { const { paramMap, envVariables } = config; const pathToValueMap = new Map(); for (const [envKey, secretPath] of Object.entries(paramMap)) { const envValue = envVariables[envKey]; if (envValue === undefined) { this.logger.warn(`Warning: Environment variable ${envKey} not found in environment file`); continue; } const existing = pathToValueMap.get(secretPath); if (existing) { if (existing.value !== envValue) { const existingMasked = new EnvironmentVariable(existing.sourceKeys[0], existing.value, true).maskedValue; const newMasked = new EnvironmentVariable(envKey, envValue, true) .maskedValue; throw new Error(`Conflicting values for secret path '${EnvironmentVariable.maskSecretPath(secretPath)}': ` + `'${existing.sourceKeys[0]}' has value '${existingMasked}' ` + `but '${envKey}' has value '${newMasked}'`); } existing.sourceKeys.push(envKey); } else { pathToValueMap.set(secretPath, { value: envValue, sourceKeys: [envKey], }); } } const uniquePaths = pathToValueMap.size; const totalVariables = Object.keys(paramMap).length; this.logger.info(`Validated ${totalVariables} environment variables mapping to ${uniquePaths} unique secrets`); return pathToValueMap; } pushParametersToStore(pathToValueMap) { return __awaiter(this, void 0, void 0, function* () { const pathsToProcess = Array.from(pathToValueMap.keys()); this.logger.info(`Processing ${pathsToProcess.length} unique secrets`); // Process secrets in parallel with retry logic for throttling errors const parameterProcessingPromises = Array.from(pathToValueMap.entries()).map(([secretPath, { value, sourceKeys }]) => { return this.retryWithBackoff(() => this.pushParameter(secretPath, value, sourceKeys)); }); yield Promise.all(parameterProcessingPromises); }); } pushParameter(secretPath, value, sourceKeys) { return __awaiter(this, void 0, void 0, function* () { const envVariable = new EnvironmentVariable(sourceKeys[0], value, true); yield this.secretProvider.setSecret(secretPath, value); const keysDescription = sourceKeys.length > 1 ? sourceKeys.join(', ') : sourceKeys[0]; this.logger.info(`Pushed ${keysDescription}=${envVariable.maskedValue} to secret store at path ${EnvironmentVariable.maskSecretPath(secretPath)}`); }); } /** * Retries an async operation with exponential backoff and jitter. * Handles throttling errors from cloud providers. */ retryWithBackoff(operation_1) { return __awaiter(this, arguments, void 0, function* (operation, maxRetries = 5, baseDelayMs = 100) { let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return yield operation(); } catch (error) { lastError = error; const isThrottlingError = typeof error === 'object' && error !== null && (('name' in error && (error.name === 'TooManyUpdates' || error.name === 'ThrottlingException' || error.name === 'TooManyRequestsException')) || ('statusCode' in error && error.statusCode === 429)); if (!isThrottlingError || attempt === maxRetries) { throw error; } const exponentialDelay = baseDelayMs * 2 ** attempt; const jitter = Math.random() * exponentialDelay * 0.5; // 0-50% jitter const delayMs = exponentialDelay + jitter; yield new Promise((resolve) => setTimeout(resolve, delayMs)); } } throw lastError; }); } getErrorMessage(error) { if (error instanceof Error) { return error.message; } if (typeof error === 'string') { return error; } if (error === null) { return 'Unknown error (null)'; } if (error === undefined) { return 'Unknown error (undefined)'; } if (typeof error === 'object') { const awsError = error; if (awsError.name) { return awsError.message ? `${awsError.name}: ${awsError.message}` : awsError.name; } const safeFields = []; if (awsError.code) safeFields.push(`code: ${awsError.code}`); if (awsError.message) safeFields.push(`message: ${awsError.message}`); if (safeFields.length > 0) { return `Object error (${safeFields.join(', ')})`; } return `Object error: ${Object.keys(error).join(', ')}`; } return `Unknown error: ${String(error)}`; } }; PushEnvToSecretsCommandHandler = __decorate([ injectable(), __param(0, inject(TYPES.ISecretProvider)), __param(1, inject(TYPES.IVariableStore)), __param(2, inject(TYPES.ILogger)), __metadata("design:paramtypes", [Object, Object, Object]) ], PushEnvToSecretsCommandHandler); export { PushEnvToSecretsCommandHandler }; //# sourceMappingURL=PushEnvToSecretsCommandHandler.js.map