diff --git a/.travis.yml b/.travis.yml index 627cec2..dbe9429 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,5 @@ deploy: skip_cleanup: true script: - npx travis-deploy-once "npx semantic-release" + on: + branch: master diff --git a/README.md b/README.md index d977a13..db88c34 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ A [Node.js](https://nodejs.org) implementation of the [Health Checks API](https: - [Functionality](#functionality) - [Health status reports](#health-status-reports) - [Usage](#usage) + - [Service details (`about` endpoint)](#service-details-about-endpoint) + - [Configuration](#configuration) - [Example - Express.js powered application](#example---expressjs-powered-application) - [Check types](#check-types) - [`self` check](#self-check) @@ -106,6 +108,34 @@ The overall health state of the subject service/application is an aggregation of The module works as a middleware, exposing the [Health Checks API](https://hootsuite.github.io/health-checks-api/) routes via chosen `http server` framework routing system. +### Service details (`about` endpoint) + +The [Health Checks API `about` endpoint](https://hootsuite.github.io/health-checks-api/#status-about-get) is supposed to describe the underlying service using this module. +The module takes particular service description attributes either from the [Configuration](#configuration) or mapping them from the service's [package.json](https://docs.npmjs.com/files/package.json) as a fallback. When particular attribute is missing both in the service config and in `package.json` a default value is taken, when provided. + +Here is the table with particular fields, their mapping to config attributes and fallback mapping to `package.json` and optional defaults: + +| _Attribute name_ | _Config attribute name_ | _`package.json` fallback - attribute mapping_ | _Static or dynamic fallback (defaults)_ | +|------------------|-------------------------|------------------------------------------------|-----------------------------------------| +| id | name | name | - | +| name | name | name | - | +| description | description | description | - | +| version | version | version | 'x.x.x' | +| host | host | - | require('os').hostname() | +| protocol | protocol | - | 'http' | +| projectHome | projectHome | homepage | - | +| projectRepo | projectRepo | repository.url | 'unknown' | +| owners | owners | author + contributors | - | +| logsLinks | logsLinks | - | - | +| statsLinks | statsLinks | - | - | +| dependencies | checks | - | - | + +> __NOTE__ +> +> _The final value is resolved with a fallback from left to right, as presented in above table._ + +### Configuration + The module configuration is a single `yaml` file and represents the subject service/application context. The default path for the config file is `./conf/dependencies.yml`. diff --git a/lib/checks/self.js b/lib/checks/self.js index a66a3b4..c672209 100644 --- a/lib/checks/self.js +++ b/lib/checks/self.js @@ -8,10 +8,11 @@ const utils = require('../utils'); module.exports = class Self extends Check { constructor(config) { super(config); - this.config.type = 'internal'; + this.config.type = constants.SERVICE_TYPE_INTERNAL; this.config.url = '127.0.0.1'; - this.config.name = 'self-check'; - this.config.statusPath = 'self-check'; + this.config.name = constants.SELF_CHECK_ID; + this.id = constants.SELF_CHECK_ID; + this.config.statusPath = constants.SELF_CHECK_ID; this.status = [ constants.OK ]; this.metrics = this.config.metrics || Object.assign({}, constants.DEFAULT_METRICS_LIMITS); this.secondsToKeepMemoryLeakMsg = diff --git a/lib/constants.js b/lib/constants.js index f5d2e78..400f044 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -10,10 +10,13 @@ const HEALTH_STATUSES = [ OK, WARN, CRIT ]; const TIME_FACTOR = 0.001; +const SERVICE_TYPE_INTERNAL = 'internal'; +const SERVICE_TYPE_EXTERNAL = 'external'; + const DEFAULT_INTERVAL = 3000; const DEFAULT_CONFIG_PATH = './conf/dependencies.yml'; const DEFAULT_TIMEOUT = 2000; -const DEFAULT_SERVICE_TYPE = 'external'; +const DEFAULT_SERVICE_TYPE = SERVICE_TYPE_EXTERNAL; const DEFAULT_ADAPTER = 'express'; const DEFAULT_METRICS_LIMITS = { cpuUsage: { @@ -43,7 +46,7 @@ const MSG_NO_DESCRIPTION = 'No description provided.'; const MSG_MEMORY_LEAK_DETECTED = 'Memory leak detected!'; const MSG_ADAPTER_MUST_BE_A_FUNCTION = 'Given \'adapter\' parameter must be either a function or a name of a module exporting a function.'; -const MSG_UNKNOWN_STATUS_ENDPOINT = 'Unknow status endpoint'; +const MSG_UNKNOWN_STATUS_ENDPOINT = 'Unknown status endpoint'; const MSG_UNKNOWN_STATUS_ENDPOINT_DETAILS = 'Status endpoint does not exist: /status/'; const MSG_CANT_TRAVERSE = 'Can\'t traverse'; const MSG_IS_NOT_TRAVERSEABLE = 'is not traversable'; @@ -52,6 +55,8 @@ const HEADER_X_FORWARDED_FOR = 'x-forwarded-for'; const HEADER_X_FORWARDED_PROTO = 'x-forwarded-proto'; const HEADER_CONTENT_TYPE = 'content-type'; +const SELF_CHECK_ID = 'self-check'; + module.exports = { OK, WARN, @@ -86,4 +91,9 @@ module.exports = { HEADER_X_FORWARDED_FOR, HEADER_X_FORWARDED_PROTO, HEADER_CONTENT_TYPE, + + SELF_CHECK_ID, + + SERVICE_TYPE_INTERNAL, + SERVICE_TYPE_EXTERNAL, }; \ No newline at end of file diff --git a/lib/routes/about.js b/lib/routes/about.js index dff426c..c38f97a 100644 --- a/lib/routes/about.js +++ b/lib/routes/about.js @@ -1,12 +1,18 @@ 'use strict'; +const os = require('os'); const httpStatus = require('http-status'); const constants = require('../constants'); -const getServiceUrl = params => { - const host = params[constants.HEADER_X_FORWARDED_FOR] || params.host; - const proto = params[constants.HEADER_X_FORWARDED_PROTO] || constants.DEFAULT_PROTOCOL; - return `${proto}://${host}`; +const authors = packageJson => { + let result = []; + if (packageJson.author) { + result.push(packageJson.author); + } + if (packageJson.contributors) { + result = result.concat(packageJson.contributors); + } + return result.map(person => person.name ? `${person.name} <${person.email}>` : person); }; module.exports = async (params, service) => ({ @@ -17,17 +23,15 @@ module.exports = async (params, service) => ({ id: service.config.name || service.packageJson.name, name: service.config.name || service.packageJson.name, description: service.config.description || service.packageJson.description, - version: service.packageJson.version || constants.DEFAULT_SERVICE_VERSION, - host: service.config.host || getServiceUrl(params), - protocol: service.config.protocol || params[constants.HEADER_X_FORWARDED_PROTO] || constants.DEFAULT_PROTOCOL, + version: service.config.version || service.packageJson.version || constants.DEFAULT_SERVICE_VERSION, + host: service.config.host || os.hostname(), + protocol: service.config.protocol || constants.DEFAULT_PROTOCOL, projectHome: service.config.projectHome || service.packageJson.homepage, projectRepo: service.config.projectRepo || (service.packageJson.repository ? service.packageJson.repository.url : constants.DEFAULT_SERVICE_REPO_URL), - owners: service.config.owners || [ - service.packageJson.author && service.packageJson.author.name - ? `${service.packageJson.author.name} <${service.packageJson.author.email}>` - : service.packageJson.author - ], + owners: service.config.owners || authors(service.packageJson), + logsLinks: service.config.logsLinks, + statsLinks: service.config.statsLinks, dependencies: service.checks.map(check => check.status), }, }); \ No newline at end of file diff --git a/package.json b/package.json index d234054..f405f21 100644 --- a/package.json +++ b/package.json @@ -52,11 +52,14 @@ "keywords": [ "health", "healthcheck", + "healthchecks", + "health-check", "health-checks", "microservice", "microservices", "hootsuite", - "health-checks-api" + "health-checks-api", + "healthchecks-api" ], "devDependencies": { "commitizen": "^3.0.2", diff --git a/test/integration/conf/demo-app.yml b/test/integration/conf/demo-app.yml index a45f769..7253363 100644 --- a/test/integration/conf/demo-app.yml +++ b/test/integration/conf/demo-app.yml @@ -1,7 +1,13 @@ -version: "3.1" + +# the 'version' will be taken from the package.json name: demo-app description: Nice demo application :) +statsLinks: + - https://my-stats/demo-app +logsLinks: + - https://my-logs/demo-app/info + - https://my-logs/demo-app/debug checks: - check: self diff --git a/test/integration/conf/service-1.yml b/test/integration/conf/service-1.yml index f62312f..8aa7b4b 100644 --- a/test/integration/conf/service-1.yml +++ b/test/integration/conf/service-1.yml @@ -1,7 +1,11 @@ -version: "3.1" +version: "3.0.1" name: service-1 -description: Service-1 awesome service +description: Awesome Service-1 +statsLinks: + - https://my-stats/service-1 +logsLinks: + - https://my-logs/service-1 checks: - check: self diff --git a/test/integration/conf/service-2.yml b/test/integration/conf/service-2.yml index 0d657ae..333f743 100644 --- a/test/integration/conf/service-2.yml +++ b/test/integration/conf/service-2.yml @@ -1,7 +1,11 @@ -version: "3.1" +version: "3.0.2" name: service-2 -description: Service-1 awesome service +description: Incredible Service-2 +statsLinks: + - https://my-stats/service-2 +logsLinks: + - https://my-logs/service-2 checks: - check: self diff --git a/test/integration/conf/service-3.yml b/test/integration/conf/service-3.yml index 555898f..37a41b5 100644 --- a/test/integration/conf/service-3.yml +++ b/test/integration/conf/service-3.yml @@ -1,7 +1,11 @@ -version: "3.1" +version: "3.0.3" name: service-3 -description: Service-1 awesome service +description: Outstanding Service-3 +statsLinks: + - https://my-stats/service-3 +logsLinks: + - https://my-logs/service-3 checks: - check: self diff --git a/test/integration/package.json b/test/integration/package.json index 0093408..e9e9740 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "description": "", "main": "service.js", + "homepage": "https://my-org/health-check-test-service", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node service.js" @@ -19,6 +20,10 @@ "url": "https://github.com/valdemon" } ], + "repository": { + "type": "git", + "url": "https://my-repo/health-check-test-service.git" + }, "license": "MIT", "dependencies": { "healthchecks-api": "../..", diff --git a/test/unit/checks/self.test.js b/test/unit/checks/self.test.js index b8132f7..1f674be 100644 --- a/test/unit/checks/self.test.js +++ b/test/unit/checks/self.test.js @@ -60,10 +60,11 @@ it ('should instantiate the Self check class properly with default options', asy const testeeInstance = new Testee(); // uncomment following when https://github.com/facebook/jest/issues/2549 is solved. // expect(testeeInstance).toBeInstanceOf(Check); - expect(testeeInstance.config.type).toBe('internal'); + expect(testeeInstance.config.type).toBe(constants.SERVICE_TYPE_INTERNAL); expect(testeeInstance.config.url).toBe('127.0.0.1'); - expect(testeeInstance.config.name).toBe('self-check'); - expect(testeeInstance.config.statusPath).toBe('self-check'); + expect(testeeInstance.config.name).toBe(constants.SELF_CHECK_ID); + expect(testeeInstance.config.statusPath).toBe(constants.SELF_CHECK_ID); + expect(testeeInstance.id).toBe(constants.SELF_CHECK_ID); expect(testeeInstance.status.status).toEqual([ constants.OK ]); expect(testeeInstance.metrics).toEqual(constants.DEFAULT_METRICS_LIMITS); expect(testeeInstance.secondsToKeepMemoryLeakMsg).toBe(constants.DEFAULT_SECONDS_TO_KEEP_MEMORY_LEAK_MSG); diff --git a/test/unit/routes/about.test.js b/test/unit/routes/about.test.js index 6a2e1ca..9382f69 100644 --- a/test/unit/routes/about.test.js +++ b/test/unit/routes/about.test.js @@ -1,6 +1,7 @@ 'use strict'; const status = require('http-status'); +const os = require('os'); const constants = require('../constants'); const testee = require('../../../lib/routes/about'); @@ -30,15 +31,18 @@ const check2 = { }; it('should return a proper response when providing config data', async () => { - const host = 'localhost:9000'; + const host = 'localhost'; const packageJson = { author: 'John Doe', }; const config = { name: 'config_name', + host, description: 'config_description', projectHome: 'config_projectHome', projectRepo: 'config_projectRepo', + logsLinks: [ 'some-link-1', 'some-link-2'], + statsLinks: [ 'some-link-3', 'some-link-4'], }; return expect(testee({ host }, { @@ -57,17 +61,23 @@ it('should return a proper response when providing config data', async () => { }, config, { id: config.name, protocol: 'http', - host: `http://${host}`, + host, version: constants.DEFAULT_SERVICE_VERSION, owners: [ packageJson.author ], + logsLinks: config.logsLinks, + statsLinks: config.statsLinks, }), }); }); it('should return a proper response when providing package.json data', async () => { - const host = 'localhost:9000'; + const host = 'localhost'; const packageJson = { author: { name: 'John Doe', email: 'john.doe@mydomain.com' }, + contributors: [ + 'John Smith ', + { name: 'Mary Smith', email: 'mary.smith@mydomain.com' } + ], name: 'config_name', description: 'config_description', homepage: 'config_projectHome', @@ -95,9 +105,13 @@ it('should return a proper response when providing package.json data', async () description: packageJson.description, id: packageJson.name, protocol: 'http', - host: `http://${host}`, + host: os.hostname(), version: constants.DEFAULT_SERVICE_VERSION, - owners: [ `${packageJson.author.name} <${packageJson.author.email}>` ], + owners: [ + `${packageJson.author.name} <${packageJson.author.email}>`, + packageJson.contributors[0], + `${packageJson.contributors[1].name} <${packageJson.contributors[1].email}>`, + ], projectHome: packageJson.homepage, projectRepo: packageJson.repository.url, }), diff --git a/test/unit/routes/traverse.test.js b/test/unit/routes/traverse.test.js index 44a778d..a810658 100644 --- a/test/unit/routes/traverse.test.js +++ b/test/unit/routes/traverse.test.js @@ -1,6 +1,7 @@ 'use strict'; const status = require('http-status'); +const os = require('os'); const nock = require('nock'); const constants = require('../constants'); const testee = require('../../../lib/routes/traverse'); @@ -123,7 +124,7 @@ it('should return an \'about\' response when request paramete \'dependency\' is }, config, { id: config.name, protocol: 'http', - host: `http://${host}`, + host: os.hostname(), version: constants.DEFAULT_SERVICE_VERSION, owners: [ packageJson.author ], }),