Proof of concept
This commit is contained in:
parent
e12143dc28
commit
2520824fe6
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
config/*
|
||||||
|
!config/*.example.js
|
@ -1,2 +1,5 @@
|
|||||||
# ConsulTemplate-NodeJS
|
# ConsulWatcher
|
||||||
Like Consul-template, but different. And written in Node.js.
|
|
||||||
|
This is a much simplified version of HashiCorp's Consul-Template, because what it accomplishes is awesome but I really dislike the Go template syntax. All configuration in this version are plain JavaScript.
|
||||||
|
|
||||||
|
It's main purpose is creating output files based on a Consul catalog, much like Consul-Template, but since the update handlers are just JavaScript functions you are free to do whatever you want, like calling a webservice or using a template library.
|
4
config/included.example.js
Normal file
4
config/included.example.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = async (catalog, logger) =>
|
||||||
|
{
|
||||||
|
// Use catalog parameter to generate output
|
||||||
|
};
|
105
config/index.example.js
Normal file
105
config/index.example.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
const config = {
|
||||||
|
onUpdate: [],
|
||||||
|
afterUpdate: null
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Consul agent configuration
|
||||||
|
Determines the agent or server which will be queried and
|
||||||
|
monitored for the service catalog.
|
||||||
|
|
||||||
|
Recommended to be a local agent connected to the cluster.
|
||||||
|
|
||||||
|
Passed to the initialization of the Node Consul client.
|
||||||
|
For all options, see: https://github.com/silas/node-consul#init
|
||||||
|
|
||||||
|
*/
|
||||||
|
config.consul = {
|
||||||
|
host: 'localhost'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Logging
|
||||||
|
See: https://github.com/winstonjs/winston#transports
|
||||||
|
|
||||||
|
*/
|
||||||
|
const winston = require('winston');
|
||||||
|
|
||||||
|
config.logging = {
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
level: 'debug',
|
||||||
|
timestamp: true,
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
onUpdate handlers
|
||||||
|
When a change occurs in the Consul catalog, each handler is called in order.
|
||||||
|
Callbacks may return a Promise. Note that when using multiple handlers,
|
||||||
|
they are not awaited immediately but at the end using Promise.all(), which
|
||||||
|
means the handlers effectively run in parallel.
|
||||||
|
|
||||||
|
The catalog parameter has a services property to enumerate the registered
|
||||||
|
services. See the Readme for the documentation of the ConsulCatalog class.
|
||||||
|
|
||||||
|
For more information about a service, including it's address and health,
|
||||||
|
an additional call to Consul is required. Calling these methods will result
|
||||||
|
in the service being watched for changes as well.
|
||||||
|
|
||||||
|
The second parameter is a reference to the Winston logger instance.
|
||||||
|
|
||||||
|
*/
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
|
|
||||||
|
config.onUpdate.push((catalog, logger) =>
|
||||||
|
{
|
||||||
|
// Use catalog parameter to generate output
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
for (const service of catalog.services)
|
||||||
|
{
|
||||||
|
output +=
|
||||||
|
`Service: ${service.name}
|
||||||
|
Tags: ${JSON.stringify(service.tags)}
|
||||||
|
Address: ${await service.getAddress()}
|
||||||
|
Port: ${await service.getPort()}
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeFile('example-output.txt', output);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
afterUpdate handler
|
||||||
|
This is a single handler which is called after all the onUpdate handlers
|
||||||
|
have finished, including any Promises returned.
|
||||||
|
|
||||||
|
It can be used for example to reload a proxy server after the configuration
|
||||||
|
changes have been written in onUpdate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
config.afterUpdate = (catalog, logger) =>
|
||||||
|
{
|
||||||
|
// Call a reload script
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = config;
|
12
consulwatcher.service
Normal file
12
consulwatcher.service
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ConsulWatcher
|
||||||
|
Requires=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
ExecStart=/usr/local/bin/node /srv/consulwatcher/index.js
|
||||||
|
KillSignal=SIGINT
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
9
index.js
Normal file
9
index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const ConsulCatalog = require('./lib/consulcatalog');
|
||||||
|
const logger = require('./lib/logger');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const catalog = new ConsulCatalog(logger, config);
|
||||||
|
|
||||||
|
// TODO detect if the connection is down for too long, allow a custom notification to be sent
|
||||||
|
|
||||||
|
// TODO provide a way to easily switch between configs, for multiple environments
|
332
lib/consulcatalog.js
Normal file
332
lib/consulcatalog.js
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
const debounce = require('debounce');
|
||||||
|
|
||||||
|
|
||||||
|
// TODO support multiple instances of the same service name
|
||||||
|
|
||||||
|
|
||||||
|
function sameTags(a, b)
|
||||||
|
{
|
||||||
|
if (a.length != b.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (let i = 0, l = a.length; i < l; i++)
|
||||||
|
{
|
||||||
|
if (!b.includes(a[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulCatalog
|
||||||
|
{
|
||||||
|
constructor(logger, config)
|
||||||
|
{
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.rawData = null;
|
||||||
|
|
||||||
|
// Always use promises, the code relies on it
|
||||||
|
config.consul.promisify = true;
|
||||||
|
|
||||||
|
self._config = config;
|
||||||
|
self._logger = logger;
|
||||||
|
self._consul = require('consul')(config.consul);
|
||||||
|
self._services = [];
|
||||||
|
self._updating = false;
|
||||||
|
self._requireUpdate = false;
|
||||||
|
self._debouncedUpdate = null;
|
||||||
|
|
||||||
|
this._logger.info('Starting watch for catalog service list');
|
||||||
|
|
||||||
|
self._watch = self._consul.watch({
|
||||||
|
method: self._consul.catalog.service.list,
|
||||||
|
options: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
self._watch.on('change', (data, res) =>
|
||||||
|
{
|
||||||
|
if (self._applyCatalogData(data))
|
||||||
|
self._doUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
self._watch.on('error', err =>
|
||||||
|
{
|
||||||
|
// TODO better exception handling
|
||||||
|
self._logger.error('Error while watching catalog service list', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get services()
|
||||||
|
{
|
||||||
|
return this._services;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
serviceByName(name)
|
||||||
|
{
|
||||||
|
return this._services.find(service => service.name == name) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
servicesByTag(tag)
|
||||||
|
{
|
||||||
|
return this._services.filter(service => service.tags.includes(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
servicesByTags(tags)
|
||||||
|
{
|
||||||
|
return this._services.filter(service => tags.every(tag => service.tags.includes(tag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_applyCatalogData(data)
|
||||||
|
{
|
||||||
|
const self = this;
|
||||||
|
self.rawData = data;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
// Remove services that no longer exist
|
||||||
|
const serviceNames = Object.keys(data);
|
||||||
|
|
||||||
|
self._services = self._services.filter(service =>
|
||||||
|
{
|
||||||
|
const serviceIndex = serviceNames.indexOf(service.name);
|
||||||
|
if (serviceIndex == -1)
|
||||||
|
{
|
||||||
|
// Previously detected service no longer appears in Consul, remove
|
||||||
|
// any watches that may be present and remove it from the list
|
||||||
|
service._delete();
|
||||||
|
changed = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service._applyCatalogData(data[service.name]))
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
// Remove from serviceNames to indicate it has already been applied
|
||||||
|
serviceNames.splice(serviceIndex, 1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// All remaining entries in serviceNames are new
|
||||||
|
serviceNames.forEach(name =>
|
||||||
|
{
|
||||||
|
self._logger.debug(`Found new service: ${name}`)
|
||||||
|
self._services.push(new ConsulService(self, name, data[name]));
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_doUpdate()
|
||||||
|
{
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
if (self._updating)
|
||||||
|
{
|
||||||
|
self._logger.debug('Update already running, will re-run after it is finished');
|
||||||
|
self._requireUpdate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (self._debouncedUpdate == null)
|
||||||
|
{
|
||||||
|
self._debouncedUpdate = debounce(() =>
|
||||||
|
{
|
||||||
|
self._updating = true;
|
||||||
|
self._requireUpdate = false;
|
||||||
|
|
||||||
|
self._logger.info('Running update handlers');
|
||||||
|
const handlerPromises = [];
|
||||||
|
|
||||||
|
self._config.onUpdate.forEach(handler =>
|
||||||
|
{
|
||||||
|
const handlerPromise = Promise.resolve(handler(self, self._logger));
|
||||||
|
handlerPromises.push(handlerPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(handlerPromises)
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
self._logger.info('Running after-update handler');
|
||||||
|
Promise.resolve(self._config.afterUpdate(self, self._logger))
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
self._logger.info('Update completed');
|
||||||
|
|
||||||
|
self._updating = false;
|
||||||
|
if (self._requireUpdate)
|
||||||
|
{
|
||||||
|
self._logger.debug('Update re-run requested');
|
||||||
|
self._doUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
self._debouncedUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConsulService
|
||||||
|
{
|
||||||
|
constructor(catalog, name, tags)
|
||||||
|
{
|
||||||
|
this._catalog = catalog;
|
||||||
|
this._consul = catalog._consul;
|
||||||
|
this._logger = catalog._logger;
|
||||||
|
|
||||||
|
this._watch = null;
|
||||||
|
this._rawData = null;
|
||||||
|
this._rawDataPromise = null;
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getAddress()
|
||||||
|
{
|
||||||
|
var rawData;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rawData = await this._getRawData();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
// TODO better exception handling
|
||||||
|
this._logger.error('Error while retrieving service status', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawData.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (rawData[0].Service.Address != '')
|
||||||
|
return rawData[0].Service.Address;
|
||||||
|
|
||||||
|
return rawData[0].Node.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getPort()
|
||||||
|
{
|
||||||
|
var rawData;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rawData = await this._getRawData();
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
// TODO better exception handling
|
||||||
|
this._logger.error('Error while retrieving service status', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawData.length > 0 ? rawData[0].Service.Port : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO getHealth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_getRawData()
|
||||||
|
{
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
if (self._rawDataPromise !== null)
|
||||||
|
return self._rawDataPromise;
|
||||||
|
|
||||||
|
// Get status information for the service and start watching it
|
||||||
|
self._rawDataPromise = new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
let firstResponse = true;
|
||||||
|
|
||||||
|
self._logger.debug(`Starting watch for service: ${this.name}`);
|
||||||
|
self._watch = self._consul.watch({
|
||||||
|
method: self._consul.health.service,
|
||||||
|
options: { service: self.name }
|
||||||
|
});
|
||||||
|
|
||||||
|
self._watch.on('change', (data, res) =>
|
||||||
|
{
|
||||||
|
if (self._applyHealthData(data))
|
||||||
|
self._catalog._doUpdate();
|
||||||
|
|
||||||
|
if (firstResponse)
|
||||||
|
{
|
||||||
|
firstResponse = false;
|
||||||
|
resolve(self._rawData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
self._watch.on('error', err =>
|
||||||
|
{
|
||||||
|
// TODO better error handling
|
||||||
|
self._logger.error(`Error while watching status for service: ${this.name}`, err);
|
||||||
|
|
||||||
|
if (firstResponse)
|
||||||
|
{
|
||||||
|
firstResponse = false;
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again the next time
|
||||||
|
self._rawDataPromise = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return self._rawDataPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_delete()
|
||||||
|
{
|
||||||
|
if (this._watch !== null)
|
||||||
|
{
|
||||||
|
this._logger.debug(`Stopping watch for service: ${this.name}`);
|
||||||
|
this._watch.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_applyCatalogData(data)
|
||||||
|
{
|
||||||
|
if (sameTags(this.tags, data))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this._logger.info(`${this.name}: tags changed from ${JSON.stringify(this.tags)} to ${JSON.stringify(data)}`);
|
||||||
|
this.tags = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_applyHealthData(data)
|
||||||
|
{
|
||||||
|
if (data == this._rawData)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const isUpdate = this._rawData != null;
|
||||||
|
this._rawData = data;
|
||||||
|
|
||||||
|
// If this is the first time we've received data, it is guaranteed to be the result of
|
||||||
|
// an update handler requesting this data and the handlers do not need to be called again.
|
||||||
|
return isUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConsulCatalog;
|
9
lib/logger.js
Normal file
9
lib/logger.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const winston = require('winston');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
// TODO make configurable
|
||||||
|
let logger = winston.createLogger({
|
||||||
|
transports: config.logging.transports,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = logger;
|
282
package-lock.json
generated
Normal file
282
package-lock.json
generated
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
{
|
||||||
|
"name": "consulwatcher",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@dabh/diagnostics": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==",
|
||||||
|
"requires": {
|
||||||
|
"colorspace": "1.1.x",
|
||||||
|
"enabled": "2.0.x",
|
||||||
|
"kuler": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"async": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^1.9.1",
|
||||||
|
"color-string": "^1.5.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
|
},
|
||||||
|
"color-string": {
|
||||||
|
"version": "1.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
|
||||||
|
"integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "^1.0.0",
|
||||||
|
"simple-swizzle": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||||
|
},
|
||||||
|
"colorspace": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
|
||||||
|
"requires": {
|
||||||
|
"color": "3.0.x",
|
||||||
|
"text-hex": "1.0.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"consul": {
|
||||||
|
"version": "0.37.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/consul/-/consul-0.37.0.tgz",
|
||||||
|
"integrity": "sha512-8V5hUcKK0osd8lShVFEgcYAGaJZmJIYq+sBy/5i4isyJ3Ud159V1PGT2eEvfib7Tu0c9kJ7Uesicc7KEnqj0yA==",
|
||||||
|
"requires": {
|
||||||
|
"papi": "^0.29.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
|
},
|
||||||
|
"debounce": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
|
||||||
|
},
|
||||||
|
"dedent": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||||
|
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
|
||||||
|
},
|
||||||
|
"fast-safe-stringify": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
|
||||||
|
},
|
||||||
|
"fecha": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg=="
|
||||||
|
},
|
||||||
|
"fn.name": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"is-arrayish": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||||
|
},
|
||||||
|
"is-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
|
||||||
|
},
|
||||||
|
"isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
|
},
|
||||||
|
"kuler": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
|
||||||
|
},
|
||||||
|
"logform": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==",
|
||||||
|
"requires": {
|
||||||
|
"colors": "^1.2.1",
|
||||||
|
"fast-safe-stringify": "^2.0.4",
|
||||||
|
"fecha": "^4.2.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"triple-beam": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"one-time": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
|
||||||
|
"requires": {
|
||||||
|
"fn.name": "1.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"papi": {
|
||||||
|
"version": "0.29.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/papi/-/papi-0.29.1.tgz",
|
||||||
|
"integrity": "sha512-Y9ipSMfWuuVFO3zY9PlxOmEg+bQ7CeJ28sa9/a0veYNynLf9fwjR3+3fld5otEy7okUaEOUuCHVH62MyTmACXQ=="
|
||||||
|
},
|
||||||
|
"process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||||
|
},
|
||||||
|
"simple-swizzle": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||||
|
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
|
||||||
|
"requires": {
|
||||||
|
"is-arrayish": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stack-trace": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||||
|
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supervisor": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"text-hex": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
|
||||||
|
},
|
||||||
|
"triple-beam": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
||||||
|
},
|
||||||
|
"util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||||
|
},
|
||||||
|
"winston": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==",
|
||||||
|
"requires": {
|
||||||
|
"@dabh/diagnostics": "^2.0.2",
|
||||||
|
"async": "^3.1.0",
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"logform": "^2.2.0",
|
||||||
|
"one-time": "^1.0.0",
|
||||||
|
"readable-stream": "^3.4.0",
|
||||||
|
"stack-trace": "0.0.x",
|
||||||
|
"triple-beam": "^1.3.0",
|
||||||
|
"winston-transport": "^4.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"winston-transport": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==",
|
||||||
|
"requires": {
|
||||||
|
"readable-stream": "^2.3.7",
|
||||||
|
"triple-beam": "^1.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "2.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||||
|
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||||
|
"requires": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "consulwatcher",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Like Consul-template, but different. And written in Node.js.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "supervisor --watch .,lib index.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/MvRens/ConsulWatcher.git"
|
||||||
|
},
|
||||||
|
"author": "Mark van Renswoude",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/MvRens/ConsulWatcher/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/MvRens/ConsulWatcher#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"consul": "^0.37.0",
|
||||||
|
"debounce": "^1.2.0",
|
||||||
|
"dedent": "^0.7.0",
|
||||||
|
"winston": "^3.3.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"supervisor": "^0.12.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user