@underpostnet/underpost
Version:
Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc
178 lines (169 loc) • 7.38 kB
JavaScript
/**
* Environment module for managing the environment variables of the underpost root
* @module src/cli/env.js
* @namespace UnderpostEnv
*/
import { getNpmRootPath, writeEnv } from '../server/conf.js';
import fs from 'fs-extra';
import { loggerFactory } from '../server/logger.js';
import dotenv from 'dotenv';
import { pbcopy } from '../server/process.js';
const logger = loggerFactory(import.meta);
/**
* Guards an env file path against stale directory artifacts.
* Removes the path if it exists as a directory (e.g. `.env/` created by a previous EISDIR bug).
* @param {string} envPath - The path to the environment file.
* @memberof UnderpostEnv
*/
const guardEnvPath = (envPath) => {
if (fs.existsSync(envPath) && !fs.statSync(envPath).isFile()) {
logger.warn(`Removing stale directory at env path: ${envPath}`);
fs.removeSync(envPath);
}
};
/**
* @class UnderpostRootEnv
* @description Manages the environment variables of the underpost root.
* @memberof UnderpostEnv
*/
class UnderpostRootEnv {
static API = {
/**
* @method set
* @description Sets an environment variable in the underpost root environment.
* @param {string} key - The key of the environment variable to set.
* @param {string} value - The value of the environment variable to set.
* @param {object} options - Options for setting the environment variable.
* @param {string} [options.deployId=''] - Deployment ID associated with the environment variable.
* @param {boolean} [options.build=false] - If true, sets the environment variable using custom --deploy-id or all dd.router deploy ids configured.
* @memberof UnderpostEnv
*/
set(key, value, options = { deployId: '', build: false }) {
const _set = (envPath, key, value) => {
guardEnvPath(envPath);
let env = {};
if (fs.existsSync(envPath)) env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
env[key] = value;
writeEnv(envPath, env);
};
if (options.build) {
const deployIdList = options.deployId
? [options.deployId]
: fs.existsSync(`./engine-private/deploy/dd.router`)
? fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')
: [DEFAULT_DEPLOY_ID];
for (const deployId of deployIdList)
for (const envFile of ['test', 'development', 'production'])
_set(`./engine-private/conf/${deployId}/.env.${envFile}`, key, value);
return;
}
const exeRootPath = `${getNpmRootPath()}/underpost`;
fs.ensureDirSync(exeRootPath);
const envPath = `${exeRootPath}/.env`;
_set(envPath, key, value);
},
/**
* @method delete
* @description Deletes an environment variable from the underpost root environment.
* @param {string} key - The key of the environment variable to delete.
* @memberof UnderpostEnv
*/
delete(key) {
const exeRootPath = `${getNpmRootPath()}/underpost`;
const envPath = `${exeRootPath}/.env`;
guardEnvPath(envPath);
let env = {};
if (fs.existsSync(envPath)) env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
delete env[key];
writeEnv(envPath, env);
},
/**
* @method get
* @description Gets an environment variable from the underpost root environment.
* @param {string} key - The key of the environment variable to get.
* @param {string} value - The value of the environment variable to get.
* @param {object} options - Options for getting the environment variable.
* @param {boolean} [options.plain=false] - If true, returns the environment variable value as a string.
* @param {boolean} [options.disableLog=false] - If true, disables logging of the environment variable value.
* @param {boolean} [options.copy=false] - If true, copies the environment variable value to the clipboard.
* @memberof UnderpostEnv
*/
get(key, value, options = { plain: false, disableLog: false, copy: false }) {
const exeRootPath = `${getNpmRootPath()}/underpost`;
const envPath = `${exeRootPath}/.env`;
if (!fs.existsSync(envPath) || !fs.statSync(envPath).isFile()) return undefined;
const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
if (!options.disableLog)
options?.plain === true ? console.log(env[key]) : logger.info(`${key}(${typeof env[key]})`, env[key]);
if (options.copy === true) pbcopy(env[key]);
return env[key];
},
/**
* @method list
* @description Lists all environment variables in the underpost root environment.
* @param {string} key - Not used for list operation.
* @param {string} value - Not used for list operation.
* @param {object} options - Options for listing environment variables.
* @param {string} [options.filter] - Filter keyword to match against keys or values.
* @memberof UnderpostEnv
*/
list(key, value, options = {}) {
const exeRootPath = `${getNpmRootPath()}/underpost`;
const envPath = `${exeRootPath}/.env`;
guardEnvPath(envPath);
if (!fs.existsSync(envPath)) {
logger.warn(`Empty environment variables`);
return {};
}
let env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
// Apply filter if provided
if (options.filter) {
const filterKeyword = options.filter.toLowerCase();
const filtered = {};
for (const [envKey, envValue] of Object.entries(env)) {
const keyMatch = envKey.toLowerCase().includes(filterKeyword);
const valueMatch = String(envValue).toLowerCase().includes(filterKeyword);
if (keyMatch || valueMatch) {
filtered[envKey] = envValue;
}
}
env = filtered;
logger.info(`underpost root (filtered by: ${options.filter})`, env);
} else {
logger.info('underpost root', env);
}
return env;
},
/**
* @method clean
* @description Cleans the underpost root environment by removing the environment file.
* @param {object} options - Options for cleaning the environment.
* @param {Array<string>} [options.keepKeys=[]] - List of keys to keep in the environment file. If provided, only these keys will be retained.
* @memberof UnderpostEnv
*/
clean(options = { keepKeys: [] }) {
const { keepKeys } = options;
const exeRootPath = `${getNpmRootPath()}/underpost`;
const envPath = `${exeRootPath}/.env`;
if (keepKeys && keepKeys.length > 0 && fs.existsSync(envPath)) {
const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
const filteredEnv = Object.fromEntries(Object.entries(env).filter(([key]) => keepKeys.includes(key)));
writeEnv(envPath, filteredEnv);
} else {
fs.removeSync(envPath);
}
},
/**
* @method isInsideContainer
* @description Detects whether the current process is running inside a container.
* Checks for Kubernetes service injection or Docker's .dockerenv marker.
* @returns {boolean} True if running inside a container.
* @memberof UnderpostEnv
*/
isInsideContainer() {
return !!process.env.KUBERNETES_SERVICE_HOST || fs.existsSync('/.dockerenv');
},
};
}
export default UnderpostRootEnv;
export { guardEnvPath };