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
JavaScript
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