@multicloud-io/client
Version:
Customer-facing Multicloud API client with limited field exposure
278 lines • 11.9 kB
JavaScript
/**
* Configuration management for Multicloud connections
*
* This module provides the MulticloudConfig class for loading and managing
* configuration from files, environment variables, and direct parameters
* with proper precedence handling.
*
* Supports both Node.js and browser environments (server-side only for security).
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MulticloudConfig = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const exceptions_1 = require("./exceptions");
class MulticloudConfig {
/**
* Get connection parameters with file and environment variable overrides
*
* Applies configuration precedence:
* 1. Direct overrides (highest priority)
* 2. Environment variables
* 3. Configuration file
* 4. Default values (lowest priority)
*
* @param overrides Direct parameter overrides
* @param options Configuration options
* @returns Connection parameters
*/
static getConnectionParams(overrides = {}, options = {}) {
const envPrefix = options.envPrefix || 'MULTICLOUD_';
const configFile = options.configFile || this.DEFAULT_CONFIG_PATH;
const isProduction = options.isProduction ?? (process.env.NODE_ENV === 'production');
// Start with default values
const params = { ...this.DEFAULT_CONFIG };
// Apply configuration file overrides
this.applyFileConfig(params, configFile);
// Apply environment variable overrides
this.applyEnvOverrides(params, envPrefix);
// Apply direct overrides (highest priority)
Object.assign(params, overrides);
// Validate required parameters
this.validateParams(params, isProduction);
// Print the final serverUrl to the console
console.log('[multicloud-connection-js] Final serverUrl used:', params.serverUrl);
return params;
}
/**
* Load configuration from YAML file
*/
static applyFileConfig(params, configFile) {
const expandedPath = configFile.startsWith('~')
? path_1.default.join(os_1.default.homedir(), configFile.slice(1))
: configFile;
if (!fs_1.default.existsSync(expandedPath)) {
// Config file is optional, just return if it doesn't exist
return;
}
try {
const content = fs_1.default.readFileSync(expandedPath, 'utf8');
// Handle variable substitution (simple ${variable} replacement)
const processedContent = this.substituteVariables(content);
// Parse YAML
const fileConfig = js_yaml_1.default.load(processedContent);
if (fileConfig && typeof fileConfig === 'object') {
// Map file config to our parameter names
if (fileConfig.multicloud_url || fileConfig.server_url) {
params.serverUrl = fileConfig.multicloud_url || fileConfig.server_url;
}
// Handle certificate paths from config
if (fileConfig.certificates) {
if (fileConfig.certificates['client-cert'] || fileConfig.certificates.client_cert) {
params.clientCert = fileConfig.certificates['client-cert'] || fileConfig.certificates.client_cert;
}
if (fileConfig.certificates['client-key'] || fileConfig.certificates.client_key) {
params.clientKey = fileConfig.certificates['client-key'] || fileConfig.certificates.client_key;
}
}
// Handle other config options
if (fileConfig.access_token || fileConfig.accessToken) {
params.accessToken = fileConfig.access_token || fileConfig.accessToken;
}
if (fileConfig.verify_ssl !== undefined) {
params.verifySsl = fileConfig.verify_ssl;
}
if (fileConfig.timeout !== undefined) {
params.timeout = fileConfig.timeout * 1000; // Convert seconds to milliseconds
}
if (fileConfig.debug !== undefined) {
params.debug = fileConfig.debug;
}
}
}
catch (error) {
throw new exceptions_1.MulticloudConfigurationError(`Error loading config file ${expandedPath}: ${error}`);
}
}
/**
* Substitute variables in configuration content
* Supports ${variable} syntax with simple replacements
*/
static substituteVariables(content) {
try {
// First pass: load to extract variables
const rawConfig = js_yaml_1.default.load(content);
const variables = {};
// Extract certificate directory for substitution
if (rawConfig && typeof rawConfig === 'object' && rawConfig.certificates) {
const certConfig = rawConfig.certificates;
if (certConfig && typeof certConfig === 'object' && certConfig.dir) {
variables['dir'] = certConfig.dir;
}
}
// Second pass: substitute variables
let processedContent = content;
for (const [varName, varValue] of Object.entries(variables)) {
const regex = new RegExp(`\\$\\{${varName}\\}`, 'g');
processedContent = processedContent.replace(regex, String(varValue));
}
return processedContent;
}
catch (error) {
// If variable substitution fails, log warning but continue
console.warn('Warning: Variable substitution failed:', error);
return content;
}
}
/**
* Apply environment variable overrides to parameters
*/
static applyEnvOverrides(params, envPrefix) {
const envMappings = {
[`${envPrefix}URL`]: 'serverUrl',
[`${envPrefix}ACCESS_TOKEN`]: 'accessToken',
[`${envPrefix}CLIENT_CERT`]: 'clientCert',
[`${envPrefix}CLIENT_KEY`]: 'clientKey',
[`${envPrefix}VERIFY_SSL`]: 'verifySsl',
[`${envPrefix}TIMEOUT`]: 'timeout',
[`${envPrefix}DEBUG`]: 'debug',
};
for (const [envVar, configKey] of Object.entries(envMappings)) {
const envValue = process.env[envVar];
if (envValue !== undefined) {
switch (configKey) {
case 'verifySsl':
case 'debug':
params[configKey] = envValue.toLowerCase() === 'true';
break;
case 'timeout':
const timeoutValue = parseInt(envValue, 10);
if (isNaN(timeoutValue)) {
throw new exceptions_1.MulticloudConfigurationError(`Invalid timeout value in ${envVar}: ${envValue}`);
}
params.timeout = timeoutValue;
break;
default:
params[configKey] = envValue;
}
}
}
}
/**
* Validate that required parameters are present
*/
static validateParams(params, isProduction) {
if (!params.serverUrl) {
throw new exceptions_1.MulticloudConfigurationError('Server URL is required');
}
// For production, require certificates
if (isProduction) {
if (!params.clientCert) {
throw new exceptions_1.MulticloudConfigurationError('Client certificate is required in production');
}
if (!params.clientKey) {
throw new exceptions_1.MulticloudConfigurationError('Client key is required in production');
}
}
}
/**
* Get configuration information for debugging
*/
static getConfigInfo(options = {}) {
const envPrefix = options.envPrefix || 'MULTICLOUD_';
const configFile = options.configFile || this.DEFAULT_CONFIG_PATH;
const expandedPath = configFile.startsWith('~')
? path_1.default.join(os_1.default.homedir(), configFile.slice(1))
: configFile;
const envVars = [
`${envPrefix}URL`,
`${envPrefix}ACCESS_TOKEN`,
`${envPrefix}CLIENT_CERT`,
`${envPrefix}CLIENT_KEY`,
`${envPrefix}VERIFY_SSL`,
`${envPrefix}TIMEOUT`,
`${envPrefix}DEBUG`
];
// Actually try to parse the configuration to get accurate status
let hasServerUrl = false;
let hasCertificates = false;
let configParseError = null;
try {
// Start with defaults
const testParams = { ...this.DEFAULT_CONFIG };
// Apply file config if it exists
if (fs_1.default.existsSync(expandedPath)) {
this.applyFileConfig(testParams, configFile);
}
// Apply environment overrides
this.applyEnvOverrides(testParams, envPrefix);
// Check what we got
hasServerUrl = !!testParams.serverUrl && testParams.serverUrl !== this.DEFAULT_CONFIG.serverUrl;
hasCertificates = !!(testParams.clientCert && testParams.clientKey);
}
catch (error) {
configParseError = error instanceof Error ? error.message : String(error);
}
return {
envVarsSet: envVars.filter(envVar => process.env[envVar] !== undefined),
nodeEnv: process.env.NODE_ENV,
hasServerUrl,
hasCertificates,
configParseError,
configFile: {
path: configFile,
expandedPath: expandedPath,
exists: fs_1.default.existsSync(expandedPath)
}
};
}
/**
* Check if configuration file exists
*/
static hasConfigFile(configFile) {
const configPath = configFile || this.DEFAULT_CONFIG_PATH;
const expandedPath = configPath.startsWith('~')
? path_1.default.join(os_1.default.homedir(), configPath.slice(1))
: configPath;
return fs_1.default.existsSync(expandedPath);
}
/**
* Check if multicloud integration is enabled
*
* @param options Configuration options
* @returns True if multicloud is properly configured
*/
static isEnabled(options = {}) {
const envPrefix = options.envPrefix || 'MULTICLOUD_';
const isProduction = options.isProduction ?? (process.env.NODE_ENV === 'production');
// Check if we have a config file
const hasConfigFile = this.hasConfigFile(options.configFile);
// In development, check if URL is configured via env vars OR config file
if (!isProduction) {
return !!process.env[`${envPrefix}URL`] || hasConfigFile;
}
// In production, require full configuration (env vars take precedence)
const hasEnvConfig = !!(process.env[`${envPrefix}URL`] &&
process.env[`${envPrefix}CLIENT_CERT`] &&
process.env[`${envPrefix}CLIENT_KEY`]);
return hasEnvConfig || hasConfigFile;
}
}
exports.MulticloudConfig = MulticloudConfig;
MulticloudConfig.DEFAULT_CONFIG_PATH = "~/.multicloud/config.yaml";
/**
* Default configuration values
*/
MulticloudConfig.DEFAULT_CONFIG = {
serverUrl: 'https://localhost:8443',
verifySsl: true,
timeout: 5000, // 5 seconds
debug: false,
};
//# sourceMappingURL=config.js.map
;