UNPKG

@creditkarma/dynamic-config

Version:

Dynamic Config for Node.js backed by Consul and Vault

213 lines 8.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.consulResolver = exports.toRemoteOptionMap = void 0; const consul_client_1 = require("@creditkarma/consul-client"); const Maybe_1 = require("../Maybe"); const constants_1 = require("../constants"); const errors = require("../errors"); const utils_1 = require("../utils"); const logger_1 = require("../logger"); function toRemoteOptionMap(str) { const [key, ...tail] = str.split('?'); const result = { key }; if (tail.length > 0) { const params = tail[0]; const options = params.split('&'); for (const option of options) { const [name, value] = option.split('='); if (value !== undefined) { result[name] = value; } } } return result; } exports.toRemoteOptionMap = toRemoteOptionMap; function addTrailingSlash(str) { if (str.endsWith('/')) { return str; } else { return `${str}/`; } } function invokeKeyValueStoreWatchObserver(callback, client, pathForKey, remoteOptions, consulDc) { const observer = client.kvStore.watch({ path: pathForKey, dc: remoteOptions.dc || consulDc.getOrElse(''), }); observer.onValue((val) => { callback(undefined, val); }); observer.onError((err) => { callback(err, undefined); }); } function consulResolver() { let consulClient; let consulAddress = new Maybe_1.Nothing(); let consulDc = new Maybe_1.Nothing(); let consulKeys = new Maybe_1.Nothing(); let consulNamespace = new Maybe_1.Nothing(); function getConsulClient() { if (consulClient !== undefined) { return consulClient; } else { if (consulAddress.isNothing()) { logger_1.defaultLogger.warn('Could not create a Consul client: Consul Address (CONSUL_ADDRESS) is not defined'); consulClient = new Maybe_1.Nothing(); } else if (consulDc.isNothing()) { logger_1.defaultLogger.warn('Could not create a Consul client: Consul Data Center (CONSUL_DC) is not defined'); consulClient = new Maybe_1.Nothing(); } else { const addresses = consulAddress .get() .split(',') .map((next) => { return next.trim(); }) .filter((next) => { return next !== ''; }); consulClient = new Maybe_1.Just({ kvStore: new consul_client_1.KvStore(addresses), catalog: new consul_client_1.Catalog(addresses), }); } return consulClient; } } return { type: 'remote', name: 'consul', async init(configInstance, remoteOptions = {}) { consulAddress = Maybe_1.Maybe.fromNullable(remoteOptions.consulAddress || utils_1.Utils.readFirstMatch(constants_1.CONSUL_ADDRESS)); consulDc = Maybe_1.Maybe.fromNullable(remoteOptions.consulDc || utils_1.Utils.readFirstMatch(constants_1.CONSUL_DC)); consulKeys = Maybe_1.Maybe.fromNullable(remoteOptions.consulKeys || utils_1.Utils.readFirstMatch(constants_1.CONSUL_KEYS)); consulNamespace = Maybe_1.Maybe.fromNullable(remoteOptions.consulNamespace || utils_1.Utils.readFirstMatch(constants_1.CONSUL_NAMESPACE)); return Maybe_1.Maybe.all(getConsulClient(), consulDc).fork( // Some case ([client, dc]) => { const keys = consulKeys.getOrElse(''); if (keys !== '') { const rawConfigs = Promise.all(keys.split(',').map((key) => { return client.kvStore .get({ path: key, dc }) .then((val) => { if (val === null) { throw new Error(`Unable to find key[${key}] in Consul.`); } else { return val; } }); })).catch((err) => { logger_1.defaultLogger.error(`Unable to read keys[${keys}] from Consul. ${err.message}`); return []; }); const resolvedConfigs = rawConfigs.then((configs) => { return utils_1.ObjectUtils.overlayObjects(...configs); }); return resolvedConfigs; } else { logger_1.defaultLogger.log('No keys to load from Consul.'); return {}; } }, // Nothing case () => { logger_1.defaultLogger.log('Consul is not configured.'); return {}; }); }, async get(key) { return getConsulClient().fork( // Some case (client) => { const remoteOptions = toRemoteOptionMap(key); return client.kvStore .get({ path: consulNamespace.fork( // Some case (val) => { return `${addTrailingSlash(val)}${remoteOptions.key}`; }, // Nothing case () => { return `${remoteOptions.key}`; }), dc: remoteOptions.dc || consulDc.getOrElse(''), }) .then((val) => { if (val !== null) { return val; } else { return client.catalog .resolveAddress(key) .then((address) => { return address; }, (err) => { throw new errors.ConsulFailed(key, err.message); }); } }, (err) => { throw new errors.ConsulFailed(key, err.message); }); }, // Nothing case () => { throw new errors.ConsulNotConfigured(key); }); }, watch(key, callback, type) { getConsulClient().fork((client) => { const remoteOptions = toRemoteOptionMap(key); const pathForKey = consulNamespace.fork( // Some case (val) => { return `${addTrailingSlash(val)}${remoteOptions.key}`; }, // Nothing case () => { return `${remoteOptions.key}`; }); client.kvStore .get({ path: pathForKey, dc: remoteOptions.dc || consulDc.getOrElse(''), }) .then((_val) => { if (_val !== null) { invokeKeyValueStoreWatchObserver(callback, client, pathForKey, remoteOptions, consulDc); } else { client.catalog.resolveAddress(key).then((address) => { client.catalog .watchAddress(key) .onValue((val) => { callback(undefined, val); }); }, (err) => { logger_1.defaultLogger.warn(`Unable to resolve address for key[${key}]: ${err.message}. Watching key value store for updates.`); invokeKeyValueStoreWatchObserver(callback, client, pathForKey, remoteOptions, consulDc); }); } }, (err) => { callback(new Error(`Unable to watch key[${key}]. ${err.message}`), undefined); }); }, () => { logger_1.defaultLogger.warn(`Unable to watch changes for key[${key}]. Consul is not configured.`); }); }, }; } exports.consulResolver = consulResolver; //# sourceMappingURL=consul.js.map