Comparing version 1.0.2 to 2.0.0
{ | ||
"name": "serviced", | ||
"version": "1.0.2", | ||
"version": "2.0.0", | ||
"description": "Microservice dependency readiness probe", | ||
@@ -10,3 +10,3 @@ "main": "serviced.js", | ||
"scripts": { | ||
"start": "DEBUG=$npm_package_name nodemon -q --exec 'node test.js | tap -R silent -'", | ||
"start": "DEBUG=$npm_package_name* nodemon -q --exec 'node test.js | tap -R spec -'", | ||
"test": "tap -R spec test.js", | ||
@@ -34,4 +34,4 @@ "linter": "eslint .", | ||
"dependencies": { | ||
"backoff": "^2.5.0", | ||
"request": "^2.72.0" | ||
"request": "^2.72.0", | ||
"retry": "^0.9.0" | ||
}, | ||
@@ -38,0 +38,0 @@ "devDependencies": { |
118
serviced.js
const debug = require('debug')('serviced'); | ||
const backoff = require('backoff'); | ||
const retry = require('retry'); | ||
const request = require('request'); | ||
const defaults = { | ||
method: 'GET', | ||
json: true, | ||
randomisationFactor: 0, // must be between 0 and 1 | ||
initialDelay: 100, | ||
maxDelay: 10000, | ||
numberOfBackoffs: null, | ||
error: null, | ||
pings: [], | ||
test() { | ||
return true; | ||
url: null, | ||
request: { | ||
json: true, | ||
}, | ||
retry: { | ||
retries: 2, | ||
maxTimeout: 2 * 1000, | ||
}, | ||
test: null, | ||
failure: null, | ||
}; | ||
function Services(...services) { | ||
debug('Setting up probes for %o services.', services.length); | ||
function Service(svc) { | ||
debug('Instantiating service with: %o', svc); | ||
const promises = services.map((options, i) => { | ||
const promise = new Promise((resolve, reject) => { | ||
debug('Setting up probe for %o', (options.name || i)); | ||
const service = Object.assign({}, defaults, options); | ||
const service = (typeof svc === 'string') ? { url: svc } : svc; | ||
const options = Object.assign({}, defaults, service); | ||
const ping = backoff.exponential(service); | ||
debug('Setting url in request options.'); | ||
options.request.uri = options.url; | ||
ping.on('backoff', (count, delay) => { | ||
service.pings.push({ count, delay }); | ||
}); | ||
return new Promise((resolve, reject) => { | ||
debug('Instantiating retry with: %o', options.retry); | ||
const operation = retry.operation(options.retry); | ||
operation.attempt(attempt => { | ||
let error = null; | ||
debug('Attempt: %o', attempt); | ||
debug('Creating request with: %o', options.request); | ||
request(options.request, (err, res, body) => { | ||
debug('Operation error: %o', operation.mainError()); | ||
debug('Operation errors: %o', operation.errors()); | ||
// operation.retry(true); | ||
if (err) { | ||
debug('Retrying - HTTP error: %o', err); | ||
error = err; | ||
} else { | ||
if (typeof options.test !== 'function') { | ||
debug('No test supplied.'); | ||
debug('Checking status code: %o', res.statusCode); | ||
if (res.statusCode !== 200) { | ||
debug('Status code check failed. Code: %o', res.statusCode); | ||
error = new Error('Status code is not 200.'); | ||
} | ||
} else { | ||
debug('Test supplied.'); | ||
debug('Tesing response body: %o', body); | ||
if (options.test(body) !== true) { | ||
debug('Supplied test failed.'); | ||
error = new Error('Serviced test failed.'); | ||
} | ||
} | ||
} | ||
if (service.numberOfBackoffs !== null) { | ||
ping.failAfter(service.numberOfBackoffs); | ||
} | ||
ping.on('ready', (count, delay) => { | ||
debug('Probing: %o', options.url); | ||
service.delay = delay; | ||
request(options, (err, res, body) => { | ||
service.error = err; | ||
if (err !== null) { | ||
debug('Probe HTTP error: %o', err); | ||
ping.backoff(); | ||
} else if (service.test(body) !== true) { | ||
debug('Probe test failed.'); | ||
ping.backoff(); | ||
if (error !== null) { | ||
debug('Error found.'); | ||
if (attempt < options.retry.retries) { | ||
debug('Additional retries available.'); | ||
operation.retry(error); | ||
} else { | ||
resolve(service); | ||
debug('No additional retries available.'); | ||
debug('Rejecting promise.'); | ||
debug('Main Error: %o', operation.mainError()); | ||
debug('All Errors: %o', operation.errors()); | ||
reject(operation.mainError()); | ||
} | ||
}); | ||
}); | ||
ping.on('fail', () => { | ||
if (!service.error) { | ||
service.error = 'PINGTIMEOUT'; | ||
} else { | ||
debug('Resolving promise.'); | ||
resolve(); | ||
} | ||
const info = { | ||
error: service.error, | ||
pings: service.pings, | ||
}; | ||
debug('Failed to reach service: %o', info); | ||
reject(info); | ||
}); | ||
ping.backoff(); | ||
}); | ||
return promise; | ||
}); | ||
} | ||
return Promise.all(promises); | ||
function Services(...services) { | ||
debug('Instantiating services'); | ||
return Promise.all(services.map(service => new Service(service))); | ||
} | ||
module.exports = Services; |
140
test.js
@@ -0,70 +1,98 @@ | ||
const debug = require('debug')('serviced:test'); | ||
const http = require('http'); | ||
const tap = require('tap'); | ||
const Serviced = require('.'); | ||
const passingService1 = { | ||
name: 'db', | ||
url: 'http://localhost:3000', | ||
method: 'GET', | ||
json: true, | ||
maxDelay: 3000, | ||
numberOfBackoffs: 3, | ||
test(body) { | ||
return body.here === true; | ||
}, | ||
}; | ||
function Server(output = {}, cb = () => {}) { | ||
debug('Creating test server'); | ||
debug('Set output: %o', output); | ||
const server = http.createServer((request, response) => { | ||
response.writeHead(200, { 'Content-Type': 'application/json' }); | ||
response.end(JSON.stringify(output)); | ||
}); | ||
server.listen(0, 'localhost', () => { | ||
const endpoint = `http://localhost:${server.address().port}`; | ||
debug('Server ready at: %o', endpoint); | ||
cb(endpoint); | ||
}); | ||
return server; | ||
} | ||
const faillingService1 = { | ||
name: 'db', | ||
url: 'http://localhost:3000', | ||
method: 'GET', | ||
json: true, | ||
maxDelay: 3000, | ||
numberOfBackoffs: 3, | ||
test(body) { | ||
return body.heres === true; | ||
}, | ||
}; | ||
tap.test('Single working service with string option.', t => { | ||
const server = new Server({ foo: 'bar' }, (endpoint) => { | ||
new Serviced(endpoint).then(() => { | ||
t.end(); | ||
server.close(); | ||
}); | ||
}); | ||
}); | ||
const passingService2 = { | ||
name: 'kube', | ||
url: 'http://localhost:3000', | ||
method: 'GET', | ||
json: true, | ||
maxDelay: 300, | ||
numberOfBackoffs: 10, | ||
test(body) { | ||
return body.here === true; | ||
}, | ||
}; | ||
tap.test('Single working service with object options.', t => { | ||
const server = new Server({ foo: 'bar' }, (endpoint) => { | ||
new Serviced({ url: endpoint }).then(() => { | ||
t.end(); | ||
server.close(); | ||
}); | ||
}); | ||
}); | ||
const faillingService2 = { | ||
name: 'kube', | ||
url: 'http://localhost:3000', | ||
method: 'GET', | ||
json: true, | ||
maxDelay: 300, | ||
numberOfBackoffs: 10, | ||
test(body) { | ||
return body.heres === true; | ||
}, | ||
}; | ||
tap.test('Single working service with passing test.', t => { | ||
const server = new Server({ foo: 'bar' }, (endpoint) => { | ||
new Serviced({ | ||
url: endpoint, | ||
test(body) { | ||
return body.foo === 'bar'; | ||
}, | ||
}).then(() => { | ||
t.end(); | ||
server.close(); | ||
}); | ||
}); | ||
}); | ||
tap.test('Single working service probe.', t => { | ||
const services = new Serviced(passingService1); | ||
services.then(() => t.end()); | ||
tap.test('Single working service with FAILING test.', t => { | ||
const server = new Server({ foo: 'bar' }, (endpoint) => { | ||
new Serviced({ | ||
url: endpoint, | ||
test(body) { | ||
return body.foo === 'foo'; | ||
}, | ||
}).catch(() => { | ||
t.end(); | ||
server.close(); | ||
}); | ||
}); | ||
}); | ||
tap.test('Single FAILLING service probe.', t => { | ||
const services = new Serviced(faillingService1); | ||
services.catch(() => t.end()); | ||
tap.test('Single unreachable service.', t => { | ||
new Serviced('http://fakehost').catch(() => { | ||
t.end(); | ||
}); | ||
}); | ||
tap.test('Multiple working services probes.', t => { | ||
const services = new Serviced(passingService1, passingService2); | ||
services.then(() => t.end()); | ||
tap.test('Multiple working services.', t => { | ||
const server1 = new Server({ foo: 'bar' }, (endpoint1) => { | ||
const server2 = new Server({ bar: 'foo' }, (endpoint2) => { | ||
new Serviced(endpoint1, endpoint2).then(() => { | ||
t.end(); | ||
server1.close(); | ||
server2.close(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
tap.test('Multiple FAILLING services probes.', t => { | ||
const services = new Serviced(faillingService1, faillingService2); | ||
services.catch(() => t.end()); | ||
tap.test('Multiple unreachable services.', t => { | ||
new Serviced('http://fakehost', 'http://fakehost2').catch(() => { | ||
t.end(); | ||
}); | ||
}); | ||
tap.test('Multiple mixed availability services.', t => { | ||
const server = new Server({ foo: 'bar' }, (endpoint) => { | ||
new Serviced(endpoint, 'http://fakehost2').catch(() => { | ||
t.end(); | ||
server.close(); | ||
}); | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
8069
166
2
+ Addedretry@^0.9.0
+ Addedretry@0.9.0(transitive)
- Removedbackoff@^2.5.0
- Removedbackoff@2.5.0(transitive)
- Removedprecond@0.2.3(transitive)