@creditkarma/dynamic-config
Version:
Dynamic Config for Node.js backed by Consul and Vault
213 lines • 8.59 kB
JavaScript
;
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