koa-actuator
Advanced tools
Comparing version 0.3.0 to 0.4.0
106
lib/index.js
const compose = require('koa-compose'); | ||
const os = require('os'); | ||
const assert = require('assert'); | ||
@@ -8,6 +7,3 @@ const {loadPackageJson} = require('./utils'); | ||
const HEALTH_PATH = '/health'; | ||
const ENV_PATH = '/env'; | ||
const INFO_PATH = '/info'; | ||
const METRICS_PATH = '/metrics'; | ||
const SECURE_PROP_NAMES = ['admin', 'user', 'password', 'pass', 'pwd', 'login', 'username']; | ||
@@ -21,9 +17,12 @@ /** | ||
const timeout = options.checkTimeout || 5000; | ||
const HEALTH_ENDPOINT = options.actuatorPath ? options.actuatorPath + HEALTH_PATH : '/actuator' + HEALTH_PATH; | ||
return async function healthMiddleware(ctx, next) { | ||
if (HEALTH_PATH === ctx.path) { | ||
if (HEALTH_ENDPOINT === ctx.path) { | ||
const health = {status: 'UP'}; | ||
if (Object.keys(checks).length > 0) health.details = {}; | ||
for (const checkName of Object.keys(checks)) { | ||
const checkResult = await runCheck(checks[checkName], timeout); | ||
health[checkName] = checkResult; | ||
const checkResult = wrapDetails(await runCheck(checks[checkName], timeout)); | ||
health.details[checkName] = checkResult; | ||
if (checkResult && checkResult.status === 'DOWN') { | ||
@@ -38,2 +37,15 @@ health.status = checkResult.status; | ||
} | ||
}; | ||
function wrapDetails(checkResult) { | ||
const wrappedResult = {}; | ||
for(const field of Object.keys(checkResult)) { | ||
if (field === 'status') { | ||
wrappedResult[field] = checkResult[field]; | ||
} else { | ||
wrappedResult.details = wrappedResult.details || {}; | ||
wrappedResult.details[field] = checkResult[field]; | ||
} | ||
} | ||
return wrappedResult; | ||
} | ||
@@ -47,3 +59,3 @@ } | ||
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Check timed out')), timeout)) | ||
]); | ||
]); | ||
} catch (e) { | ||
@@ -57,62 +69,24 @@ return {status: 'DOWN', error: e && (e.message || e.toString())}; | ||
*/ | ||
async function info(ctx, next) { | ||
if (INFO_PATH === ctx.path) { | ||
if (package_json) { | ||
ctx.body = { | ||
build: | ||
{ | ||
version: package_json.version, | ||
name: package_json.name, | ||
main: package_json.main, | ||
description: package_json.description | ||
} | ||
}; | ||
} else { | ||
ctx.body = {}; | ||
} | ||
} else { | ||
await next(); | ||
} | ||
} | ||
function info(options) { | ||
/** | ||
* Exposes environment properties. Secure variables (such as 'user', 'password', 'pass' etc are) values will be hidden. | ||
*/ | ||
async function env(ctx, next) { | ||
if (ENV_PATH === ctx.path) { | ||
const envCopy = Object.assign({}, process.env); | ||
Object | ||
.keys(envCopy) | ||
.filter(property => { | ||
const propLowerCase = property.toLowerCase(); | ||
return SECURE_PROP_NAMES.some(p => propLowerCase.includes(p)); | ||
}) | ||
.forEach(property => envCopy[property] = '*******'); //hide secure details | ||
ctx.body = {systemEnvironment: envCopy, arguments: process.argv}; | ||
} else { | ||
await next(); | ||
} | ||
} | ||
const INFO_ENDPOINT = options.actuatorPath ? options.actuatorPath + INFO_PATH : '/actuator' + INFO_PATH; | ||
/** | ||
* Exposes application and resources information. E.g. name, version, memory and CPU usage | ||
*/ | ||
async function metrics(ctx, next) { | ||
if (METRICS_PATH === ctx.path) { | ||
const memory = process.memoryUsage(); | ||
ctx.body = { | ||
timestamp: Date.now(), | ||
uptime: process.uptime(), | ||
processors: os.cpus().length, | ||
heap: memory.heapTotal, | ||
'heap.used': memory.heapUsed, | ||
resources: { | ||
memory: memory, | ||
loadavg: os.loadavg(), | ||
cpu: JSON.stringify(os.cpus()), | ||
nics: JSON.stringify(os.networkInterfaces()) | ||
return async function infoMiddleware(ctx, next) { | ||
if (INFO_ENDPOINT === ctx.path) { | ||
if (package_json) { | ||
ctx.body = { | ||
build: | ||
{ | ||
version: package_json.version, | ||
name: package_json.name, | ||
main: package_json.main, | ||
description: package_json.description | ||
} | ||
}; | ||
} else { | ||
ctx.body = {}; | ||
} | ||
}; | ||
} else { | ||
await next(); | ||
} else { | ||
await next(); | ||
} | ||
} | ||
@@ -122,3 +96,3 @@ } | ||
module.exports = (healthChecks = {}, options = {}) => { | ||
return compose([health(healthChecks, options), env, info, metrics]); | ||
return compose([health(healthChecks, options), info(options)]); | ||
}; |
{ | ||
"name": "koa-actuator", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Healthcheck and monitoring middleware for koa inspired by java spring's actuators", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
125
test/test.js
@@ -18,3 +18,3 @@ const actuator = require('../lib/index'); | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(200) | ||
@@ -24,2 +24,15 @@ .expect({status: 'UP'}, done); | ||
it('should use parameter actuatorPath if it is defined', (done) => { | ||
const options = {actuatorPath: '/custom-actuator-path'}; | ||
const app = new Koa(); | ||
app.use(actuator({}, options)); | ||
request(app.callback()) | ||
.get('/custom-actuator-path/health') | ||
.expect(200) | ||
.expect({status: 'UP'}, done); | ||
}); | ||
it('should return 200 and status UP if sync and async checks pass', (done) => { | ||
@@ -35,8 +48,10 @@ //arrange | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(200) | ||
.expect({ | ||
status: 'UP', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'UP', usedMemory: '52m', uptimeDays: 16} | ||
details: { | ||
db: {status: 'UP', details: {freeConnections: 10}}, | ||
redis: {status: 'UP', details: {usedMemory: '52m', uptimeDays: 16}} | ||
} | ||
}, done); | ||
@@ -55,8 +70,10 @@ }); | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'DOWN', usedMemory: '52m', uptimeDays: 16} | ||
details: { | ||
db: {status: 'UP', details: {freeConnections: 10}}, | ||
redis: {status: 'DOWN', details:{usedMemory: '52m', uptimeDays: 16}} | ||
} | ||
}, done); | ||
@@ -69,3 +86,5 @@ }); | ||
app.use(actuator({ | ||
db: () => { throw new Error('unexpected error'); }, | ||
db: () => { | ||
throw new Error('unexpected error'); | ||
}, | ||
redis: () => Promise.resolve({status: 'UP', usedMemory: '52m', uptimeDays: 16}) | ||
@@ -76,8 +95,10 @@ })); | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'DOWN', error: 'unexpected error'}, | ||
redis: {status: 'UP', usedMemory: '52m', uptimeDays: 16} | ||
details: { | ||
db: {status: 'DOWN', details: {error: 'unexpected error'}}, | ||
redis: {status: 'UP', details: {usedMemory: '52m', uptimeDays: 16}} | ||
} | ||
}, done); | ||
@@ -96,8 +117,10 @@ }); | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'DOWN', error: 'unexpected async error'} | ||
details: { | ||
db: {status: 'UP', details: {freeConnections: 10}}, | ||
redis: {status: 'DOWN', details: {error: 'unexpected async error'}} | ||
} | ||
}, done); | ||
@@ -116,7 +139,9 @@ }); | ||
request(app.callback()) | ||
.get('/health') | ||
.get('/actuator/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'DOWN', error: 'Check timed out'}, | ||
details: { | ||
db: {status: 'DOWN', details: {error: 'Check timed out'}} | ||
} | ||
}, done); | ||
@@ -144,3 +169,3 @@ }); | ||
request(app.callback()) | ||
.get('/info') | ||
.get('/actuator/info') | ||
.expect(200) | ||
@@ -154,46 +179,27 @@ .end((err, res) => { | ||
it('should return 200 and empty object if package.json not found', (done) => { | ||
it('should use parameter actuatorPath if it is defined', (done) => { | ||
//arrange | ||
sinon.stub(utils, 'loadPackageJson').returns(null); | ||
delete require.cache[require.resolve('../lib/index')]; | ||
const actuator = require('../lib/index'); | ||
const options = {actuatorPath: '/custom-actuator-path'}; | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
app.use(actuator({}, options)); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/info') | ||
.get('/custom-actuator-path/info') | ||
.expect(200) | ||
.end((err, res) => { | ||
if (err) return done(err); | ||
assert.deepEqual(res.body, {}); | ||
assert.isDefined(res.body.build.version); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('/env', () => { | ||
it('should return 200 and the list of environment variables', (done) => { | ||
it('should return 200 and empty object if package.json not found', (done) => { | ||
//arrange | ||
process.env.TEST_VAR = 'test'; | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
sinon.stub(utils, 'loadPackageJson').returns(null); | ||
delete require.cache[require.resolve('../lib/index')]; | ||
const actuator = require('../lib/index'); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/env') | ||
.expect(200) | ||
.end((err, res) => { | ||
if (err) return done(err); | ||
assert.equal(res.body.systemEnvironment.TEST_VAR, 'test'); | ||
done(); | ||
}); | ||
}); | ||
it('should hide secure data', (done) => { | ||
//arrange | ||
process.env.TEST_VAR = 'test'; | ||
process.env.USERNAME = 'test-username'; | ||
process.env.PASSWORD = 'test-password'; | ||
const app = new Koa(); | ||
@@ -204,9 +210,7 @@ app.use(actuator()); | ||
request(app.callback()) | ||
.get('/env') | ||
.get('/actuator/info') | ||
.expect(200) | ||
.end((err, res) => { | ||
if (err) return done(err); | ||
assert.equal(res.body.systemEnvironment.TEST_VAR, 'test'); | ||
assert.equal(res.body.systemEnvironment.USERNAME, '*******'); | ||
assert.equal(res.body.systemEnvironment.PASSWORD, '*******'); | ||
assert.deepEqual(res.body, {}); | ||
done(); | ||
@@ -217,23 +221,2 @@ }); | ||
describe('/metrics', () => { | ||
it('should return 200 and show some service info (like uptime, heap usage etc)', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/metrics') | ||
.expect(200) | ||
.end((err, res) => { | ||
if (err) return done(err); | ||
assert.property(res.body, 'uptime'); | ||
assert.property(res.body, 'processors'); | ||
assert.property(res.body, 'heap'); | ||
assert.property(res.body, 'heap.used'); | ||
assert.deepProperty(res.body, 'resources.memory'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
2
0
16610
317