Merge pull request #1592 from andrewthauer/backend-status-checks
[Backend-Common] Add backend status check handler middleware
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"helmet": "^3.22.0",
|
||||
"morgan": "^1.10.0",
|
||||
"stoppable": "^1.1.0",
|
||||
|
||||
@@ -17,3 +17,4 @@
|
||||
export * from './errorHandler';
|
||||
export * from './notFoundHandler';
|
||||
export * from './requestLoggingHandler';
|
||||
export * from './statusCheckHandler';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { statusCheckHandler } from './statusCheckHandler';
|
||||
|
||||
describe('statusCheckHandler', () => {
|
||||
it('gives status 200 when using default', async () => {
|
||||
const app = express();
|
||||
app.use('/healthcheck', await statusCheckHandler());
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify({ status: 'ok' }));
|
||||
});
|
||||
|
||||
it('gives status 200 when status function returns true', async () => {
|
||||
const app = express();
|
||||
const status = { foo: 'bar' };
|
||||
const statusCheck = () => Promise.resolve(status);
|
||||
app.use('/healthcheck', await statusCheckHandler({ statusCheck }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify(status));
|
||||
});
|
||||
|
||||
it('gives status 500 when status check throws an error', async () => {
|
||||
const app = express();
|
||||
const statusCheck = () => {
|
||||
throw Error('error!');
|
||||
};
|
||||
app.use('/healthcheck', await statusCheckHandler({ statusCheck }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NextFunction, Request, Response, RequestHandler } from 'express';
|
||||
|
||||
export type StatusCheck = () => Promise<any>;
|
||||
|
||||
export interface StatusCheckHandlerOptions {
|
||||
/**
|
||||
* Optional status function which returns a message.
|
||||
*/
|
||||
statusCheck?: StatusCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Express middleware for status checks.
|
||||
*
|
||||
* This is commonly used to implement healthcheck and readiness routes.
|
||||
*
|
||||
* @param options An optional configuration object.
|
||||
* @returns An Express error request handler
|
||||
*/
|
||||
export async function statusCheckHandler(
|
||||
options: StatusCheckHandlerOptions = {},
|
||||
): Promise<RequestHandler> {
|
||||
const statusCheck: StatusCheck = options.statusCheck
|
||||
? options.statusCheck
|
||||
: () => Promise.resolve({ status: 'ok' });
|
||||
|
||||
return async (_request: Request, response: Response, next: NextFunction) => {
|
||||
try {
|
||||
const status = await statusCheck();
|
||||
response.status(200).header('').send(status);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import * as winston from 'winston';
|
||||
import request from 'supertest';
|
||||
import { createStatusCheckRouter } from './createStatusCheckRouter';
|
||||
|
||||
describe('createStatusCheckRouter', () => {
|
||||
const logger = winston.createLogger();
|
||||
|
||||
it('gives status 200 when using default path', async () => {
|
||||
const app = express();
|
||||
app.use('', await createStatusCheckRouter({ logger }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify({ status: 'ok' }));
|
||||
});
|
||||
|
||||
it('gives status 200 when using custom path', async () => {
|
||||
const app = express();
|
||||
app.use('', await createStatusCheckRouter({ logger, path: '/ready' }));
|
||||
|
||||
const response = await request(app).get('/ready');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify({ status: 'ok' }));
|
||||
});
|
||||
|
||||
it('gives status 500 when status check throws an error', async () => {
|
||||
const app = express();
|
||||
const statusCheck = () => {
|
||||
throw Error('error!');
|
||||
};
|
||||
app.use('', await createStatusCheckRouter({ logger, statusCheck }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import Router from 'express-promise-router';
|
||||
import express from 'express';
|
||||
import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware';
|
||||
|
||||
export interface StatusCheckRouterOptions {
|
||||
logger: Logger;
|
||||
path?: string;
|
||||
statusCheck?: StatusCheck;
|
||||
}
|
||||
|
||||
export async function createStatusCheckRouter(
|
||||
options: StatusCheckRouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const router = Router();
|
||||
const { path = '/healthcheck', statusCheck } = options;
|
||||
|
||||
router.use(path, await statusCheckHandler({ statusCheck }));
|
||||
router.use(errorHandler());
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -15,4 +15,5 @@
|
||||
*/
|
||||
|
||||
export { createServiceBuilder } from './createServiceBuilder';
|
||||
export { createStatusCheckRouter } from './createStatusCheckRouter';
|
||||
export type { ServiceBuilder } from './types';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import cors from 'cors';
|
||||
import { Router } from 'express';
|
||||
import { Router, RequestHandler } from 'express';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@@ -64,7 +64,7 @@ export type ServiceBuilder = {
|
||||
* @param root The root URL to bind to (e.g. "/api/function1")
|
||||
* @param router An express router
|
||||
*/
|
||||
addRouter(root: string, router: Router): ServiceBuilder;
|
||||
addRouter(root: string, router: Router | RequestHandler): ServiceBuilder;
|
||||
|
||||
/**
|
||||
* Starts the server using the given settings.
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { ConfigReader, AppConfig } from '@backstage/config';
|
||||
import { loadConfig } from '@backstage/config-loader';
|
||||
import knex from 'knex';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import auth from './plugins/auth';
|
||||
import catalog from './plugins/catalog';
|
||||
import identity from './plugins/identity';
|
||||
@@ -63,6 +64,7 @@ async function main() {
|
||||
const configReader = ConfigReader.fromConfigs(configs);
|
||||
const createEnv = makeCreateEnv(configs);
|
||||
|
||||
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
|
||||
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
@@ -75,6 +77,7 @@ async function main() {
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
.addRouter('', await healthcheck(healthcheckEnv))
|
||||
.addRouter('/catalog', await catalog(catalogEnv))
|
||||
.addRouter('/rollbar', await rollbar(rollbarEnv))
|
||||
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createStatusCheckRouter } from '@backstage/backend-common';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createRouter({ logger }: PluginEnvironment) {
|
||||
return await createStatusCheckRouter({ logger, path: '/healthcheck' });
|
||||
}
|
||||
Reference in New Issue
Block a user