Socket
Socket
Sign inDemoInstall

@opuscapita/config

Package Overview
Dependencies
9
Maintainers
13
Versions
48
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.1.7 to 3.1.11

2

index.js

@@ -1,3 +0,3 @@

const { ConfigClient } = require('./lib');
const {ConfigClient} = require('./lib');
module.exports = new ConfigClient();
const ConfigClientBase = require('./ConfigClientBase');
const crypto = require('crypto');
const Promise = require('bluebird');
const Cache = require('@opuscapita/cache');
const extend = require('extend');

@@ -20,10 +19,8 @@ const Logger = require('@opuscapita/logger');

*/
class ConfigClient extends ConfigClientBase
{
constructor()
{
super();
}
class ConfigClient extends ConfigClientBase {
constructor() {
super();
}
/**
/**
* Prepares all required data, connects to consul and starts all event processing.

@@ -34,19 +31,20 @@ *

*/
init(config)
{
if(this.initialized)
return Promise.resolve(this);
init(config) {
if (this.initialized) {
return Promise.resolve(this);
}
config = extend(true, { }, ConfigClient.DefaultConfig, config);
config = extend(true, { }, ConfigClient.DefaultConfig, config);
const initConf = {
config : config,
logger : config.logger || new Logger({ context : { serviceName : this.serviceName, name: '@opuscapita/config' } }),
cache : new Cache({ defaultExpire : Number.MAX_SAFE_INTEGER })
};
const initConf = {
config: config,
logger: config.logger || new Logger({context: {serviceName: this.serviceName, name: '@opuscapita/config'}}),
};
return Promise.resolve(this._init(initConf)).catch(e => { this.logger.error(e.message, e); throw e; });
}
return Promise.resolve(this._init(initConf)).catch(e => {
this.logger.error(e.message, e); throw e;
});
}
/**
/**
* Closes all connections and cleans up the whole internal state.

@@ -56,8 +54,9 @@ *

*/
dispose()
{
return Promise.resolve(this._dispose()).catch(e => { this.logger.error(e.message, e); throw e; });
}
dispose() {
return Promise.resolve(this._dispose()).catch(e => {
this.logger.error(e.message, e); throw e;
});
}
/**
/**
* Gets the full qualified name for a key including the prefixed [serviceName]{@link serviceName}.

@@ -67,8 +66,7 @@ * @param {string} key - Key to check.

*/
getFullKey(key)
{
return `${this.serviceName}/${key}`;
}
getFullKey(key) {
return `${this.serviceName}/${key}`;
}
/**
/**
* Checks whenever the passed key follows the rules for key naming and is therefor a valid key.

@@ -83,8 +81,7 @@ * Requesting an invalid key from the system will throw an error.

*/
checkKey(key)
{
return key ? /^([0-9a-zA-Z-]+\/?)+[0-9a-zA-Z-]+$/.test(this.getFullKey(key)) : false;
}
checkKey(key) {
return key ? /^([0-9a-zA-Z-]+\/?)+[0-9a-zA-Z-]+$/.test(this.getFullKey(key)) : false;
}
/**
/**
* Checks whenever the passed prefix follows the rules for key prefixing.

@@ -99,8 +96,7 @@ * Requesting an invalid prefix from the system will throw an error.

*/
checkKeyPrefix(prefix)
{
return prefix ? /^([0-9a-zA-Z-]+\/)+$/.test(prefix) : false;
}
checkKeyPrefix(prefix) {
return prefix ? /^([0-9a-zA-Z-]+\/)+$/.test(prefix) : false;
}
/**
/**
* Encrypts a given object using an internal secret.

@@ -110,9 +106,8 @@ * @param {object} data Object to be encrypted.

*/
encryptData(data)
{
const cipher = crypto.createCipheriv('aes-256-cbc', this.secret, '9f3663cc2e8a47dd');
return cipher.update(JSON.stringify(data), 'utf8', 'base64') + cipher.final('base64');
}
encryptData(data) {
const cipher = crypto.createCipheriv('aes-256-cbc', this.secret, '9f3663cc2e8a47dd');
return cipher.update(JSON.stringify(data), 'utf8', 'base64') + cipher.final('base64');
}
/**
/**
* Decrypts a given string using an internal secret.

@@ -122,9 +117,8 @@ * @param {string} encrypted Encrypted data.

*/
decryptData(encrypted)
{
const cipher = crypto.createDecipheriv('aes-256-cbc', this.secret, '9f3663cc2e8a47dd');
return JSON.parse(cipher.update(encrypted, 'base64', 'utf8') + cipher.final('utf8'));
}
decryptData(encrypted) {
const cipher = crypto.createDecipheriv('aes-256-cbc', this.secret, '9f3663cc2e8a47dd');
return JSON.parse(cipher.update(encrypted, 'base64', 'utf8') + cipher.final('utf8'));
}
/**
/**
* Gets a single or a list of values from consul's key-value store.

@@ -147,28 +141,26 @@ * To get a list of values you can either use key-prefixing or an array of keys or key-prefixes.

*/
getProperty(keyOrPrefix, recursive = false, silent = false)
{
if(Array.isArray(keyOrPrefix))
return Promise.all(keyOrPrefix.map(k => this.getProperty(k, recursive, silent)));
getProperty(keyOrPrefix, recursive = false, silent = false) {
if (Array.isArray(keyOrPrefix)) {
return Promise.all(keyOrPrefix.map(k => this.getProperty(k, recursive, silent)));
}
const keyName = this.getFullKey(keyOrPrefix);
const keyName = this.getFullKey(keyOrPrefix);
const isValidKey = !recursive && this.checkKey(keyOrPrefix);
const isValidPrefix = recursive && this.checkKeyPrefix(keyOrPrefix);
const isValidKey = !recursive && this.checkKey(keyOrPrefix);
const isValidPrefix = recursive && this.checkKeyPrefix(keyOrPrefix);
if(isValidKey || isValidPrefix)
{
const task = () => retry(() => this._getConsulValues(keyName, recursive), { max_tries : 5, interval : 500 });
if (isValidKey || isValidPrefix) {
const task = () => retry(() => this._getConsulValues(keyName, recursive), {max_tries: 5, interval: 500});
if(silent)
return task().catch(e => this.logger.warn('Cannot get property', e));
else
return task();
}
else
{
return Promise.reject(new Error('The passed key or prefix is invalid: ' + keyOrPrefix));
}
if (silent) {
return task().catch(e => this.logger.warn('Cannot get property', e));
} else {
return task();
}
} else {
return Promise.reject(new Error(`The passed key or prefix is invalid: ${keyOrPrefix}`));
}
}
/**
/**
* Sets a value to consul's key-value store.

@@ -179,11 +171,11 @@ * @param {string} key - Key to set. Will automatically get prefixed with [serviceName]{@link serviceName}.

*/
setProperty(key, value)
{
if(this.checkKey(key))
return Promise.resolve(this._setConsulValue(this.getFullKey(key), value));
else
return Promise.reject(new Error('The passed key is invalid: ' + key));
setProperty(key, value) {
if (this.checkKey(key)) {
return Promise.resolve(this._setConsulValue(this.getFullKey(key), value));
} else {
return Promise.reject(new Error(`The passed key is invalid: ${key}`));
}
}
/**
/**
* Removes a single value or a list of values from consul's key-value store.

@@ -195,28 +187,26 @@ * @param {string|string[]} keyOrPrefix - Key to remove. Will automatically get prefixed with [serviceName]{@link serviceName}.

*/
deleteProperty(keyOrPrefix, recursive = false, silent = false)
{
if(Array.isArray(keyOrPrefix))
return Promise.all(keyOrPrefix.map(k => this.deleteProperty(k, recursive)));
deleteProperty(keyOrPrefix, recursive = false, silent = false) {
if (Array.isArray(keyOrPrefix)) {
return Promise.all(keyOrPrefix.map(k => this.deleteProperty(k, recursive)));
}
const keyName = this.getFullKey(keyOrPrefix);
const keyName = this.getFullKey(keyOrPrefix);
const isValidKey = !recursive && this.checkKey(keyOrPrefix);
const isValidPrefix = recursive && this.checkKeyPrefix(keyOrPrefix);
const isValidKey = !recursive && this.checkKey(keyOrPrefix);
const isValidPrefix = recursive && this.checkKeyPrefix(keyOrPrefix);
if(isValidKey || isValidPrefix)
{
const task = () => retry(() => this._deleteConsulValue(keyName, recursive), { max_tries : 5, interval : 500 });
if (isValidKey || isValidPrefix) {
const task = () => retry(() => this._deleteConsulValue(keyName, recursive), {max_tries: 5, interval: 500});
if(silent)
return task().catch(e => this.logger.warn('Cannot delete property', e));
else
return task();
}
else
{
return Promise.reject(new Error('The passed key or prefix is invalid: ' + keyOrPrefix));
}
if (silent) {
return task().catch(e => this.logger.warn('Cannot delete property', e));
} else {
return task();
}
} else {
return Promise.reject(new Error(`The passed key or prefix is invalid: ${keyOrPrefix}`));
}
}
/**
/**
* Gets an endpoint from consul's service registry.

@@ -232,18 +222,20 @@ * Be aware, that this method only returns service endpoints that are marked *healthy* by consul.

*/
getEndPoint(serviceName, silent = false)
{
getEndPoint(serviceName, silent = false) {
if (Array.isArray(serviceName)) {
return Promise.all(serviceName.map(k => this.getEndPoint(k, silent)));
}
if(Array.isArray(serviceName))
return Promise.all(serviceName.map(k => this.getEndPoint(k, silent)));
const task = () => this._getConsulEndpoint(serviceName);
const task = () => this._getConsulEndpoint(serviceName);
if(silent)
return task().catch(e => this.logger.warn('Cannot get endpoint', e));
else
return retry(task, { max_tries: this.config.retryCount, interval: this.config.retryTimeout })
.catch(e => { this.logger.warn('Cannot get endpoint', e); throw e; });
if (silent) {
return task().catch(e => this.logger.warn('Cannot get endpoint', e));
} else {
return retry(task, {max_tries: this.config.retryCount, interval: this.config.retryTimeout}).
catch(e => {
this.logger.warn('Cannot get endpoint', e); throw e;
});
}
}
/**
/**
* Gets a list of endpoints from consul's service registry.

@@ -259,18 +251,22 @@ * Be aware, that this method only returns service endpoints that are marked *healthy* by consul.

*/
getEndPoints(serviceName, silent = false)
{
getEndPoints(serviceName, silent = false) {
if (Array.isArray(serviceName)) {
return Promise.all(serviceName.map(k => this.getEndPoints(k, silent)));
}
if(Array.isArray(serviceName))
return Promise.all(serviceName.map(k => this.getEndPoints(k, silent)));
const task = () => this._getConsulEndpoints(serviceName);
const task = () => this._getConsulEndpoints(serviceName);
if(silent)
return task().catch(e => { this.logger.warn('Cannot get endpoints', e); return [ ]; });
else
return retry(task, { max_tries: this.config.retryCount, interval: this.config.retryTimeout })
.catch(e => { this.logger.warn('Cannot get endpoint', e); throw e; });
if (silent) {
return task().catch(e => {
this.logger.warn('Cannot get endpoints', e); return [];
});
} else {
return retry(task, {max_tries: this.config.retryCount, interval: this.config.retryTimeout}).
catch(e => {
this.logger.warn('Cannot get endpoint', e); throw e;
});
}
}
/**
/**
* Gets a single or a list of values from consul's key-value store. Alias for [getProperty()]{@link getProperty}.

@@ -294,8 +290,7 @@ * To get a list of values you can either use key-prefixing or and array of keys and key-prefixes.

*/
get(keyOrPrefix, recursive = false, silent = false)
{
return this.getProperty(keyOrPrefix, recursive, silent);
}
get(keyOrPrefix, recursive = false, silent = false) {
return this.getProperty(keyOrPrefix, recursive, silent);
}
/**
/**
* Sets a value to consul's key-value store. Alias for [setProperty()]{@link setProperty}.

@@ -307,8 +302,7 @@ * @deprecated since v3.1.5 - use setProperty instead

*/
set(key, value)
{
return this.setProperty(key, value);
}
set(key, value) {
return this.setProperty(key, value);
}
/**
/**
* Removes a value from consul's key-value store. Alias for [deleteProperty()]{@link deleteProperty}.

@@ -321,8 +315,7 @@ * @deprecated since v3.1.5 - use deleteProperty instead

*/
delete(key, recursive = false, silent = false)
{
return this.deleteProperty(key, recursive, silent);
}
delete(key, recursive = false, silent = false) {
return this.deleteProperty(key, recursive, silent);
}
/**
/**
* Gets an encrypted value from consul's key-value store.

@@ -336,14 +329,15 @@ * This method uses retry in order to bypass problems with network connections, service latencies or

*/
getPassword(keyOrPrefix, recursive = false, silent = false)
{
return this.get(keyOrPrefix, recursive, silent).then(values =>
{
if(Array.isArray(keyOrPrefix) || recursive)
return Object.keys(values).reduce((all, key) => { all[key] = this.decryptData(values[key]); return all }, { });
getPassword(keyOrPrefix, recursive = false, silent = false) {
return this.get(keyOrPrefix, recursive, silent).then(values => {
if (Array.isArray(keyOrPrefix) || recursive) {
return Object.keys(values).reduce((all, key) => {
all[key] = this.decryptData(values[key]); return all;
}, { });
}
return this.decryptData(values);
});
}
return this.decryptData(values);
});
}
/**
/**
* Sets an encrypted value to consul's key-value store.

@@ -354,9 +348,8 @@ * @param {string} key - Key to set. Will automatically get prefixed with [serviceName]{@link serviceName}.

*/
setPassword(key, value)
{
const encrypted = this.encryptData(value);
return this.set(key, encrypted);
};
setPassword(key, value) {
const encrypted = this.encryptData(value);
return this.set(key, encrypted);
}
/**
/**
* Waits a certain time for a list of endpoints to be available. If all endpoints are available before the passed timeout is reached, this method will return a promise

@@ -369,6 +362,9 @@ * resolving to *true*. Otherwise the returned promise gets rejected with an error.

*/
waitForEndpoints(serviceNames, timeout = 5000)
{
return retry(() => this.getEndPoint(serviceNames, false).then(() => true), { interval : 500, backOff : 1.2, max_interval : timeout, max_tries : Number.MAX_SAFE_INTEGER, timeout : timeout });
}
waitForEndpoints(serviceNames, timeout = 5000) {
return retry(
() => this.getEndPoint(serviceNames, false).
then(() => true),
{interval: 500, backOff: 1.2, max_interval: timeout, max_tries: Number.MAX_SAFE_INTEGER, timeout: timeout}
);
}
}

@@ -387,10 +383,10 @@

ConfigClient.DefaultConfig = {
host : 'consul',
port : 8500,
retryCount : 50,
retryTimeout : 1000,
logger : null,
serviceSecretPath : `/run/secrets/${ConfigClientBase.serviceName}-consul-key`
}
host: 'consul',
port: 8500,
retryCount: 50,
retryTimeout: 1000,
logger: null,
serviceSecretPath: `/run/secrets/${ConfigClientBase.serviceName}-consul-key`
};
module.exports = ConfigClient;

@@ -7,3 +7,4 @@ const EventEmitter = require('events');

const cwd = process.cwd();
const serviceName = cwd.slice(cwd.lastIndexOf('/') + 1);
const serviceName = cwd.slice(cwd.lastIndexOf('/') + 1);
const NodeCache = require('node-cache');

@@ -17,22 +18,19 @@ /**

*/
class ConfigClientBase extends EventEmitter
{
constructor()
{
super();
class ConfigClientBase extends EventEmitter {
constructor() {
super();
this.initialized = false;
this.setMaxListeners(100);
}
this.initialized = false;
this.setMaxListeners(100);
}
/**
/**
* Gets the name of the current service. It simply takes the name of the inner least directory the process is running in.
* @returns {string} Service name.
*/
get serviceName()
{
return serviceName;
}
get serviceName() {
return serviceName;
}
/**
/**
* Gets a promise containing a list of all services available in consul.

@@ -42,164 +40,153 @@ *

*/
get allServices()
{
return Promise.resolve((async () => this.consul && Object.keys(await this.consul.catalog.service.list()))());
get allServices() {
return Promise.resolve((async () => this.consul && Object.keys(await this.consul.catalog.service.list()))());
}
async _init({config, logger}) {
if (this.initialized) {
if (config && logger) {
logger.warn('ConfigClientBase already initialized. Ignoring new config.');
}
return this;
}
async _init({ config, logger, cache })
{
if(this.initialized)
return this;
this.config = config;
this.logger = logger;
this.cache = new NodeCache({
stdTTL: 0 // 0 = unlimited
});
this.epWatches = {};
this.keyWatches = {};
this.knownKeys = {};
this.config = config;
this.logger = logger;
this.cache = cache;
this.epWatches = {};
this.keyWatches = {};
this.knownKeys = {};
const [secret, consul] = await Promise.all([
this._loadEncryptionKey(this.config.serviceSecretPath),
this._connect(this.config)
]);
const [ secret, consul ] = await Promise.all([
this._loadEncryptionKey(this.config.serviceSecretPath),
this._connect(this.config)
]);
await Promise.all([
// this._watchServices(consul, (allServices) => Promise.all(allServices.map((serviceName) => this._watchServiceHealth(consul, serviceName))).catch(e => this.logger.error(e))),
this._watchKey(consul, `${this.serviceName}/`)
]);
await Promise.all([
//this._watchServices(consul, (allServices) => Promise.all(allServices.map((serviceName) => this._watchServiceHealth(consul, serviceName))).catch(e => this.logger.error(e))),
this._watchKey(consul, this.serviceName + '/')
]);
this.secret = secret;
this.consul = consul;
this.initialized = true;
this.secret = secret;
this.consul = consul;
this.initialized = true;
return this;
}
return this;
async _dispose() {
this.config = null;
this.logger = null;
this.cache = null;
for (const k in this.epWatches) {
this.epWatches[k].removeAllListeners('change').removeAllListeners('error');
this.epWatches[k].end();
}
async _dispose()
{
this.config = null;
this.logger = null;
this.cache = null;
for (const k in this.keyWatches) {
this.keyWatches[k].removeAllListeners('change').removeAllListeners('error');
this.keyWatches[k].end();
}
for(const k in this.epWatches)
{
this.epWatches[k].removeAllListeners('change').removeAllListeners('error');
this.epWatches[k].end();
}
this.epWatches = {};
this.keyWatches = {};
this.knownKeys = {};
for(const k in this.keyWatches)
{
this.keyWatches[k].removeAllListeners('change').removeAllListeners('error');
this.keyWatches[k].end();
}
this.secret = null;
this.consul = null;
this.initialized = false;
}
this.epWatches = {};
this.keyWatches = {};
this.knownKeys = {};
async _connect({host, port, retryCount, retryTimeout}) {
this.logger.info('Connecting to consul', {host, port});
this.secret = null;
this.consul = null;
this.initialized = false;
const connection = consul({
host: host,
port: port,
promisify: true
});
try {
await retry(() => connection.status.leader(), {max_tries: retryCount, interval: retryTimeout});
this.logger.info('Connected to consul.');
} catch (e) {
this.logger.error('Could not connect to consul', {e});
throw e;
}
async _connect({ host, port, retryCount, retryTimeout })
{
this.logger.info('Connecting to consul', {host, port});
return connection;
}
const connection = consul({
host : host,
port : port,
promisify : true
});
async _loadEncryptionKey(serviceSecretPath) {
this.logger.info('Reading encryption key', {serviceSecretPath});
try
{
await retry(() => connection.status.leader(), { max_tries : retryCount, interval : retryTimeout });
this.logger.info('Connected to consul.');
}
catch(e)
{
this.logger.error('Could not connect to consul', {e});
throw e;
}
let secret = 'b2eb88a9cd0e45cf890a1505df17a718';
return connection;
try {
secret = fs.readFileSync(serviceSecretPath, 'utf8').trim();
} catch (e) {
this.logger.warn('Cannot read encryption key - falling back to default key.', {serviceSecretPath});
}
async _loadEncryptionKey(serviceSecretPath)
{
this.logger.info('Reading encryption key', {serviceSecretPath});
return secret;
}
let secret = 'b2eb88a9cd0e45cf890a1505df17a718';
// async _watchServices(consul, callback)
// {
// return new Promise((resolve, reject) =>
// {
// const watch = consul.watch({ method : consul.catalog.service.list, maxAttempts : 3 });
try
{
secret = fs.readFileSync(serviceSecretPath, 'utf8').trim();
}
catch(e)
{
this.logger.warn('Cannot read encryption key - falling back to default key.', {serviceSecretPath});
}
// this.epWatches['all-services'] = watch;
return secret;
}
// watch.on('change', (data, res) => callback(Object.keys(data)));
// watch.on('error', (err) => { console.error('Error watching services.', err); reject(err) });
// async _watchServices(consul, callback)
// {
// return new Promise((resolve, reject) =>
// {
// const watch = consul.watch({ method : consul.catalog.service.list, maxAttempts : 3 });
// resolve(watch);
// });
// }
// this.epWatches['all-services'] = watch;
async _watchServiceHealth(consul, serviceName) {
return new Promise((resolve, reject) => {
const cacheKey = `${this.serviceName}.ep.${serviceName}`;
// watch.on('change', (data, res) => callback(Object.keys(data)));
// watch.on('error', (err) => { console.error('Error watching services.', err); reject(err) });
if (this.epWatches[cacheKey]) {
return resolve(this.epWatches[cacheKey]);
}
// resolve(watch);
// });
// }
const watch = consul.watch({method: consul.health.service, options: {service: serviceName}});
async _watchServiceHealth(consul, serviceName)
{
return new Promise((resolve, reject) =>
{
const cacheKey = this.serviceName + '.ep.' + serviceName;
this.epWatches[cacheKey] = watch;
if(this.epWatches[cacheKey])
return resolve(this.epWatches[cacheKey]);
const mapService = (s) => ({
host: s.Service.ServiceAddress || s.Service.Address || s.Node.Address,
port: s.Service.Port,
modifyIndex: s.Service.ModifyIndex
});
const watch = consul.watch({ method : consul.health.service, options : { service : serviceName } });
watch.on('error', (err) => {
// This is not crititcal as the watch will be automatically restarted/retried.
console.log(`Error watching service "${serviceName}". Retrying...`, err);
});
this.epWatches[cacheKey] = watch;
watch.on('end', () => {
console.warn(`The watch for service "${serviceName}" has been stopped.`); this._disposeServiceHealthWatch(serviceName);
});
watch.on('change', async (items) => {
if (items.length === 0) {
await this.cache.del(serviceName);
this.emit('endpointChanged', serviceName, null);
} else {
const newEndpoints = items.map((service) => mapService(service));
const oldEndpoints = await this.cache.get(cacheKey);
const hasChanged = oldEndpoints &&
oldEndpoints.filter(oe => newEndpoints.filter(ne => ne.modifyIndex > oe.modifyIndex).length);
const mapService = (s) => ({
host : s.Service.ServiceAddress || s.Service.Address || s.Node.Address,
port : s.Service.Port,
modifyIndex : s.Service.ModifyIndex
});
if (!hasChanged || (Array.isArray(hasChanged) && hasChanged.length > 0)) {
await this.cache.set(cacheKey, newEndpoints);
watch.on('error', (err) =>
{
// This is not crititcal as the watch will be automatically restarted/retried.
console.log(`Error watching service "${serviceName}". Retrying...`, err);
});
watch.on('end', () => { console.warn(`The watch for service "${serviceName}" has been stopped.`); this._disposeServiceHealthWatch(serviceName); });
watch.on('change', async (items) =>
{
if(items.length === 0)
{
await this.cache.delete(serviceName);
this.emit('endpointChanged', serviceName, null);
}
else
{
const newEndpoints = items.map((service) => mapService(service));
const oldEndpoints = await this.cache.get(cacheKey);
const hasChanged = oldEndpoints && oldEndpoints.filter(oe => newEndpoints.filter(ne => ne.modifyIndex > oe.modifyIndex).length);
if(!hasChanged || (Array.isArray(hasChanged) && hasChanged.length > 0))
{
await this.cache.put(cacheKey, newEndpoints);
/**
/**
* Change event for endpoint data.

@@ -212,61 +199,59 @@ *

*/
this.emit('endpointChanged', serviceName, await this.cache.get(cacheKey));
}
}
});
this.emit('endpointChanged', serviceName, await this.cache.get(cacheKey));
}
}
});
resolve(watch);
});
}
resolve(watch);
});
}
async _disposeServiceHealthWatch(serviceName)
{
const cacheKey = this.serviceName + '.ep.' + serviceName;
async _disposeServiceHealthWatch(serviceName) {
const cacheKey = `${this.serviceName}.ep.${serviceName}`;
if(this.epWatches[cacheKey])
{
this.epWatches[cacheKey].end();
this.epWatches[cacheKey].removeAllListeners('error').removeAllListeners('end').removeAllListeners('change');
if (this.epWatches[cacheKey]) {
this.epWatches[cacheKey].end();
this.epWatches[cacheKey].removeAllListeners('error').removeAllListeners('end').removeAllListeners('change');
delete this.epWatches[cacheKey];
this.cache && await this.cache.delete(serviceName);
delete this.epWatches[cacheKey];
this.cache && await this.cache.del(serviceName);
this.emit('endpointChanged', serviceName, null);
}
this.emit('endpointChanged', serviceName, null);
}
}
async _watchKey(consul, key)
{
return new Promise((resolve, reject) =>
{
if(this.keyWatches[key])
return resolve(this.keyWatches[key]);
async _watchKey(consul, key) {
return new Promise((resolve, reject) => {
if (this.keyWatches[key]) {
return resolve(this.keyWatches[key]);
}
const watch = consul.watch({ method : consul.kv.keys, options : { key : key } });
const watch = consul.watch({method: consul.kv.keys, options: {key: key}});
this.keyWatches[key] = watch;
this.keyWatches[key] = watch;
watch.on('error', (err) => { console.error(`Error watching key "${key}".`, err) ; reject(err); });
watch.on('end', () => { console.warn(`The watch for key "${key}" has been stopped.`); this._disposeKeyWatch(key); });
watch.on('change', async (keys) =>
{
const oldKeys = Object.keys(this.knownKeys);
const missingKeys = oldKeys.filter(key => !keys.includes(key));
watch.on('error', (err) => {
console.error(`Error watching key "${key}".`, err); reject(err);
});
watch.on('end', () => {
console.warn(`The watch for key "${key}" has been stopped.`); this._disposeKeyWatch(key);
});
watch.on('change', async (keys) => {
const oldKeys = Object.keys(this.knownKeys);
const missingKeys = oldKeys.filter(key => !keys.includes(key));
await Promise.all(missingKeys.map(async key => this._disposeKeyWatch(key))).catch(e => null);
await Promise.all(keys.map(async (key) =>
{
this.knownKeys[key] = true;
await Promise.all(missingKeys.map(async key => this._disposeKeyWatch(key))).catch(e => null);
await Promise.all(keys.map(async (key) => {
this.knownKeys[key] = true;
const result = await consul.kv.get({ key });
const value = result && result.Value;
const oldValue = await this.cache.get(key);
const result = await consul.kv.get({key});
const value = result && result.Value;
const oldValue = await this.cache.get(key);
if(value != oldValue)
{
await this.cache.put(key, value);
if (value != oldValue) {
await this.cache.set(key, value);
const shortKey = key.substr(key.indexOf(this.serviceName) + this.serviceName.length + 1);
const shortKey = key.substr(key.indexOf(this.serviceName) + this.serviceName.length + 1);
/**
/**
* Change event for properties (kv).

@@ -279,153 +264,133 @@ *

*/
this.emit('propertyChanged', shortKey, value);
}
})).catch(e => null);
});
this.emit('propertyChanged', shortKey, value);
}
})).catch(e => null);
});
resolve(watch);
});
}
resolve(watch);
});
}
async _disposeKeyWatch(key)
{
if(this.keyWatches[key])
{
this.keyWatches[key].end();
this.keyWatches[key].removeAllListeners('error').removeAllListeners('end').removeAllListeners('change');
async _disposeKeyWatch(key) {
if (this.keyWatches[key]) {
this.keyWatches[key].end();
this.keyWatches[key].removeAllListeners('error').removeAllListeners('end').removeAllListeners('change');
delete this.keyWatches[key];
delete this.knownKeys[key];
delete this.keyWatches[key];
delete this.knownKeys[key];
this.cache && await this.cache.delete(key);
this.cache && await this.cache.del(key);
const shortKey = key.substr(key.indexOf(this.serviceName) + this.serviceName.length + 1);
const shortKey = key.substr(key.indexOf(this.serviceName) + this.serviceName.length + 1);
this.emit('propertyChanged', shortKey);
}
this.emit('propertyChanged', shortKey);
}
}
async _getConsulEndpoint(serviceName)
{
return this._getRandomValueThrow(await this._getConsulEndpoints(serviceName));
async _getConsulEndpoint(serviceName) {
return this._getRandomValueThrow(await this._getConsulEndpoints(serviceName));
}
async _getConsulEndpoints(serviceName) {
if (process.env.KUBERNETES_SERVICE_DISCOVERY) {
const port = process.env[`${serviceName.toUpperCase()}_PORT`] || 80;
return [{host: serviceName, port}];
}
async _getConsulEndpoints(serviceName)
{
if (process.env.KUBERNETES_SERVICE_DISCOVERY)
{
const port = process.env[`${serviceName.toUpperCase()}_PORT`] || 80;
return [{host: serviceName, port}];
}
const cacheName = `${this.serviceName}.ep.${serviceName}`;
const results = await this.cache.get(cacheName);
const cacheName = this.serviceName + '.ep.' + serviceName;
const results = await this.cache.get(cacheName);
if (results === undefined) {
await this._watchServiceHealth(this.consul, serviceName);
if(results === undefined)
{
await this._watchServiceHealth(this.consul, serviceName);
const getValuesOrThrow = async (values) => {
if (!values || (Array.isArray(values) && values.length === 0)) {
throw new Error('Could not get values.');
}
const getValuesOrThrow = async (values) =>
{
if(!values || (Array.isArray(values) && values.length === 0))
throw new Error('Could not get values.');
return values;
};
return values;
}
return retry(async () => getValuesOrThrow(await this.cache.get(cacheName)), { max_tries : 5, interval : 500 })
.catch(e => { throw new Error('The requested endpoint could not be found or did not pass health checks: ' + serviceName); });
}
else
{
return results;
}
return retry(async () => getValuesOrThrow(await this.cache.get(cacheName)), {max_tries: 5, interval: 500}).
catch(e => {
throw new Error(`The requested endpoint could not be found or did not pass health checks: ${serviceName}`);
});
} else {
return results;
}
}
async _getConsulValues(keyName, recursive)
{
if(recursive)
{
const keys = (await this.cache.keys()).filter(k => k.startsWith(keyName));
async _getConsulValues(keyName, recursive) {
if (recursive) {
const keys = (await this.cache.keys()).filter(k => k.startsWith(keyName));
if(keys.length === 0)
throw new Error('Key was not found: ' + keyName);
if (keys.length === 0) {
throw new Error(`Key was not found: ${keyName}`);
}
const values = await Promise.all(keys.map(k => this.cache.get(k)));
const results = { };
const values = await Promise.all(keys.map(k => this.cache.get(k)));
const results = { };
for(const i in keys)
{
const key = keys[i].substr(keys[i].indexOf(this.serviceName) + this.serviceName.length + 1);
results[key] = values[i];
}
for (const i in keys) {
const key = keys[i].substr(keys[i].indexOf(this.serviceName) + this.serviceName.length + 1);
results[key] = values[i];
}
return results;
}
else
{
const value = await this.cache.get(keyName);
return results;
} else {
const value = await this.cache.get(keyName);
if(value)
return value;
else
throw new Error('Key was not found: ' + keyName);
}
if (value) {
return value;
} else {
throw new Error(`Key was not found: ${keyName}`);
}
}
}
async _setConsulValue(keyName, value)
{
try
{
await this.cache.delete(keyName);
await this.consul.kv.set(keyName, value);
}
catch(e)
{
this.logger.warn('Config key could not be set', {keyName, ...e});
}
async _setConsulValue(keyName, value) {
try {
await this.cache.del(keyName);
await this.consul.kv.set(keyName, value);
} catch (e) {
this.logger.warn('Config key could not be set', {keyName, ...e});
}
}
async _deleteConsulValue(keyName, recursive)
{
try
{
if(recursive)
{
const keys = (await this.cache.keys()).filter(k => k.startsWith(keyName));
await Promise.all(keys.map(k => this.cache.delete(k)));
}
else
{
await this.cache.delete(keyName);
}
await this.consul.kv.del({ key : keyName, recurse : recursive });
}
catch(e)
{
this.logger.warn('Config key could not be deleted', {keyName, ...e});
return false;
}
async _deleteConsulValue(keyName, recursive) {
try {
if (recursive) {
const keys = (await this.cache.keys()).filter(k => k.startsWith(keyName));
await Promise.all(keys.map(k => this.cache.del(k)));
} else {
await this.cache.del(keyName);
}
return true;
await this.consul.kv.del({key: keyName, recurse: recursive});
} catch (e) {
this.logger.warn('Config key could not be deleted', {keyName, ...e});
return false;
}
_getRandomValue(array)
{
if(Array.isArray(array) && array.length)
return array[Math.floor(Math.random() * 1e10 % array.length)];
else
return null;
return true;
}
_getRandomValue(array) {
if (Array.isArray(array) && array.length) {
return array[Math.floor(Math.random() * 1e10 % array.length)];
} else {
return null;
}
}
_getRandomValueThrow(array)
{
const result = this._getRandomValue(array);
_getRandomValueThrow(array) {
const result = this._getRandomValue(array);
if(!result)
throw new Error('Cannot select random value from empty array.');
if (!result) {
throw new Error('Cannot select random value from empty array.');
}
return result;
}
return result;
}
}

@@ -432,0 +397,0 @@

const ConfigClient = require('./ConfigClient');
module.exports = { ConfigClient };
module.exports = {ConfigClient};
{
"name": "@opuscapita/config",
"version": "3.1.7",
"version": "3.1.11",
"description": "Configuration API connector module for OpusCapita Business Network Portal.",

@@ -28,18 +28,19 @@ "main": "index.js",

"dependencies": {
"@opuscapita/cache": "^1.2.0",
"@opuscapita/logger": "^2.0.0",
"@opuscapita/logger": "^2.0.2",
"bluebird": "^3.7.2",
"bluebird-retry": "~0.11.0",
"consul": "^0.40.0",
"extend": "~3.0.1"
"extend": "~3.0.1",
"node-cache": "^5.1.2"
},
"devDependencies": {
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.6",
"eslint": "^8.20.0",
"jsdoc-to-markdown": "^7.1.1",
"mocha": "^10.0.0",
"mocha-junit-reporter": "^2.0.2",
"@opuscapita/eslint-config-opuscapita-bnapp": "^1.3.5",
"@types/mocha": "^10.0.1",
"@types/node": "^18.11.15",
"eslint": "^8.29.0",
"jsdoc-to-markdown": "^8.0.0",
"mocha": "^10.2.0",
"mocha-junit-reporter": "^2.2.0",
"nyc": "^15.1.0"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc