Socket
Socket
Sign inDemoInstall

@opuscapita/config

Package Overview
Dependencies
9
Maintainers
9
Versions
48
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.1.3 to 3.1.4-next.1

types/index.d.ts

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,31 +19,31 @@ 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.
*
* @param {object} config
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
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: {name: '@opuscapita/config'}}),
};
return Promise.resolve(this._init(initConf)).catch(e => { this.logger.error(e.message); 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.

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

*/
dispose()
{
return Promise.resolve(this._dispose()).catch(e => { this.logger.error(e.message); 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}.

@@ -65,8 +65,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.

@@ -81,8 +80,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));
}
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.

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

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

@@ -108,9 +105,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.

@@ -120,9 +116,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.

@@ -140,33 +135,31 @@ * To get a list of values you can either use key-prefixing or an array of keys or key-prefixes.

* remote service capacity problems. For further details have a look at the [DefaultConfig]{@link DefaultConfig}.
* @param {string|array} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} recursive - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {string|string[]} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} [recursive] - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
getProperty(keyOrPrefix, recursive, silent)
{
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.error(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.

@@ -177,43 +170,41 @@ * @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.
* @param {string|array} keyOrPrefix - Key to remove. Will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} recursive - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {string|string[]} keyOrPrefix - Key to remove. Will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} [recursive] - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
deleteProperty(keyOrPrefix, recursive, silent)
{
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.error(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.

@@ -225,22 +216,24 @@ * Be aware, that this method only returns service endpoints that are marked *healthy* by consul.

* remote service capacity problems. For further details have a look at the [DefaultConfig]{@link DefaultConfig}.
* @param {string} serviceName - Name of service to request.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {string|string[]} serviceName - Name of service to request.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html} containing and [Endpoint]{@link module:@opuscapita/config~Endpoint} object.
*/
getEndPoint(serviceName, silent)
{
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.error(e));
else
return retry(task, { max_tries: this.config.retryCount, interval: this.config.retryTimeout })
.catch(e => { this.logger.error(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.

@@ -252,22 +245,26 @@ * Be aware, that this method only returns service endpoints that are marked *healthy* by consul.

* remote service capacity problems. For further details have a look at the [DefaultConfig]{@link DefaultConfig}.
* @param {string} serviceName - Name of service to request.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {string|string[]} serviceName - Name of service to request.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html} containing an array of [Endpoint]{@link module:@opuscapita/config~Endpoint} objects.
*/
getEndPoints(serviceName, silent)
{
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.error(e); return [ ]; });
else
return retry(task, { max_tries: this.config.retryCount, interval: this.config.retryTimeout })
.catch(e => { this.logger.error(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}.

@@ -285,14 +282,15 @@ * To get a list of values you can either use key-prefixing or and array of keys and key-prefixes.

* remote service capacity problems. For further details have a look at the [DefaultConfig]{@link DefaultConfig}.
* @param {mixed} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} recursive - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @deprecated since v3.1.5 - use getProperty instead
* @param {string|string[]} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} [recursive] - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
get(keyOrPrefix, recursive, silent)
{
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}.
* @deprecated since v3.1.5 - use setProperty instead
* @param {string} key - Key to set. Will automatically get prefixed with [serviceName]{@link serviceName}.

@@ -302,40 +300,40 @@ * @param {object} value - Value to set.

*/
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}.
* @deprecated since v3.1.5 - use deleteProperty instead
* @param {string} key - Key to remove. Will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} recursive - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {boolean} [recursive] - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
delete(keyOrPrefix, recursive, silent)
{
return this.deleteProperty(keyOrPrefix, recursive);
}
delete(key, recursive = false, silent = false) {
return this.deleteProperty(key, recursive, silent);
}
/**
/**
* Gets an encrypted value from consul's key-value store.
* This method uses retry in order to bypass problems with network connections, service latencies or
* remote service capacity problems. For further details have a look at the [DefaultConfig]{@link DefaultConfig}.
* @param {mixed} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} recursive - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} silent - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @param {string|string[]} keyOrPrefix - Key(s) to request. Can be either a single key string, a key-prefix, an array of keys or and array of key-prefixes. For using key-prefixes, the *recusrive* parameter has to be set to true. All keys an prefixes will automatically get prefixed with [serviceName]{@link serviceName}.
* @param {boolean} [recursive] - If set to true, the *keyOrPrefix* parameter is used as key-prefix to recursively list all keys below this prefix.
* @param {boolean} [silent] - If set to true, this method will not use retry to get something from consul so it will return faster and it will not pass any errors up to the calling code.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html}.
*/
getPassword(keyOrPrefix, recursive, silent)
{
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.

@@ -346,20 +344,22 @@ * @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
* resolving to *true*. Otherwise the returned promise gets rejected with an error.
*
* @param {string|array} serviceNames - A single service name or an array of service names to wait for.
* @param {number?} timeout - The maximum time in milliseconds to wait for the requested services to be available.
* @param {string|string[]} serviceNames - A single service name or an array of service names to wait for.
* @param {number} [timeout] - The maximum time in milliseconds to wait for the requested services to be available.
* @returns {Promise} Returns a bluebird [Promise]{@link http://bluebirdjs.com/docs/api-reference.html} that gets rejected with an error if the passed timeout is reached.
*/
waitForEndpoints(serviceNames, timeout = 5000)
{
return retry(() => this.getEndPoint(serviceNames).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}
);
}
}

@@ -378,10 +378,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 service: %s:%s', 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: %s', e.message);
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: %s', 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 from %s. 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,152 +264,135 @@ *

*/
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 serviceEnvPrefix = serviceName.toUpperCase().replaceAll(/[^A-Z0-9_]/g, '_');
const host = process.env[`${serviceEnvPrefix}_HOST`] || serviceName;
const port = process.env[`${serviceEnvPrefix}_PORT`] || 80;
return [{host: host, port}];
}
async _getConsulEndpoints(serviceName)
{
if (process.env.KUBERNETES_SERVICE_DISCOVERY)
{
return [{host: serviceName, port: 80}];
}
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].substring(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 %s could not be set: %s', keyName, e.message);
}
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 %s could not be deleted: %s', keyName, e.message);
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;
}
}

@@ -431,0 +399,0 @@

const ConfigClient = require('./ConfigClient');
module.exports = { ConfigClient };
module.exports = {ConfigClient};
{
"name": "@opuscapita/config",
"version": "3.1.3",
"description": "Configuration API connector module for OpusCapita Business Network Portal.",
"version": "3.1.4-next.1",
"description": "Configuration provider for OpusCapita Andariel platform",
"main": "index.js",
"scripts": {
"start": "npm run test",
"test": "npm run setupConsul ; npx nyc mocha --exit --timeout 30000 -R mocha-junit-reporter --reporter-options mochaFile=results/integration-test-results.xml,outputs=true",
"test-raw": "npm run setupConsul ; npx nyc mocha --exit --timeout 30000",
"setupConsul": "sh ./setup-consul.sh",
"test-coverage": "npm run setupConsul ; npx nyc --reporter=lcov mocha --exit --timeout 20000 && sed -i 's/\\/home\\/node/\\/Users\\/work\\/Documents\\/workspace/g' coverage/lcov.info",
"upload-coverage": "cat ./coverage/lcov.info | npx coveralls",
"api-doc": "npx jsdoc2md --files ./lib/* > wiki/Home.md",
"doc": "npm run api-doc",
"prepublishOnly": "npm version patch -m 'Version set to %s. [skip ci]'"
"test": "npm run setup-consul ; npx nyc --all -r lcov mocha --exit --timeout 30000 -R mocha-junit-reporter --reporter-options mochaFile=junit/integration-test-results.xml,outputs=true",
"setup-consul": "sh ./setup-consul.sh"
},

@@ -29,18 +23,31 @@ "repository": {

"lib",
"andariel_dependencies.json"
"types"
],
"engines": {
"node": ">=12.0.0"
},
"dependencies": {
"@opuscapita/cache": "^1.1.0",
"@opuscapita/logger": "^1.3.0",
"bluebird": "~3.5.1",
"@opuscapita/logger": "^2.1.2",
"bluebird": "^3.7.2",
"bluebird-retry": "~0.11.0",
"consul": "^0.34.0",
"extend": "~3.0.1"
"consul": "^0.40.0",
"extend": "~3.0.1",
"node-cache": "^5.1.2"
},
"devDependencies": {
"jsdoc-to-markdown": "^4.0.1",
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.23.3",
"nyc": "^13.0.0"
"@babel/eslint-parser": "^7.21.8",
"@babel/preset-env": "^7.22.4",
"@babel/preset-react": "^7.22.3",
"@opuscapita/eslint-config-opuscapita-bnapp": "^2.3.0",
"@types/mocha": "^10.0.1",
"@types/node": "^20.2.5",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"eslint": "^8.42.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"mocha": "^10.2.0",
"mocha-junit-reporter": "^2.2.0",
"nyc": "^15.1.0"
}
}
# @opuscapita/config
This library is automatically build and published to npmjs after commit to the `master` branch
This library was automatically build and published to npmjs after commit to the `master` branch but was re-configured for manual build instead.

@@ -4,0 +4,0 @@ This module provides easy access to instances of the **consul** service registry. It helps with accessing **key-value** stored configuration data, local data **encryption** and service **endpoint discovery** with health checking and change notifications.

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