backend-defaults: move over health service

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2024-06-05 13:27:10 +02:00
parent fce7887109
commit 918b397763
7 changed files with 107 additions and 109 deletions
@@ -1,46 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 Router from 'express-promise-router';
import { Request, Response } from 'express';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
export function createHealthRouter(options: {
lifecycle: RootLifecycleService;
}) {
const router = Router();
let isRunning = false;
options.lifecycle.addStartupHook(() => {
isRunning = true;
});
options.lifecycle.addShutdownHook(() => {
isRunning = false;
});
router.get('/v1/readiness', async (_request: Request, response: Response) => {
if (!isRunning) {
throw new Error('Backend has not started yet');
}
response.json({ status: 'ok' });
});
router.get('/v1/liveness', async (_request: Request, response: Response) => {
response.json({ status: 'ok' });
});
return router;
}
@@ -1,37 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { createHealthRouter } from './createHealthRouter';
/**
* @public
*/
export const healthServiceFactory = createServiceFactory({
service: coreServices.health,
deps: {
rootHttpRouter: coreServices.rootHttpRouter,
lifecycle: coreServices.rootLifecycle,
},
async factory({ lifecycle, rootHttpRouter }) {
rootHttpRouter.use('.backstage/health', createHealthRouter({ lifecycle }));
return {};
},
});
@@ -0,0 +1,13 @@
## API Report File for "@backstage/backend-defaults"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { HealthService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
// @public (undocumented)
export const healthServiceFactory: () => ServiceFactory<HealthService, 'root'>;
// (No @packageDocumentation comment for this package)
```
+4
View File
@@ -24,6 +24,7 @@
"./cache": "./src/entrypoints/cache/index.ts",
"./database": "./src/entrypoints/database/index.ts",
"./discovery": "./src/entrypoints/discovery/index.ts",
"./health": "./src/entrypoints/health/index.ts",
"./httpAuth": "./src/entrypoints/httpAuth/index.ts",
"./httpRouter": "./src/entrypoints/httpRouter/index.ts",
"./lifecycle": "./src/entrypoints/lifecycle/index.ts",
@@ -54,6 +55,9 @@
"discovery": [
"src/entrypoints/discovery/index.ts"
],
"health": [
"src/entrypoints/health/index.ts"
],
"httpAuth": [
"src/entrypoints/httpAuth/index.ts"
],
@@ -1,3 +1,6 @@
import { mockServices } from '@backstage/backend-test-utils';
import { DefaultHealthService } from './healthServiceFactory';
/*
* Copyright 2024 The Backstage Authors
*
@@ -13,22 +16,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mockServices } from '@backstage/backend-test-utils';
import request from 'supertest';
import { createHealthRouter } from './createHealthRouter';
import express from 'express';
describe('createHealthRouter', () => {
describe('DefaultHealthService', () => {
describe('readiness', () => {
it(`should return a 500 response if the server hasn't started yet`, async () => {
const hc = createHealthRouter({
const service = new DefaultHealthService({
lifecycle: mockServices.rootLifecycle.mock(),
});
const app = express().use(hc);
const response = await request(app).get('/v1/readiness');
expect(response.status).toBe(500);
await expect(service.getReadiness()).resolves.toEqual({ status: 503 });
});
it('should return 200 if the server has started', async () => {
@@ -38,12 +32,16 @@ describe('createHealthRouter', () => {
fn => (mockServerStartedFn = fn),
);
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
const service = new DefaultHealthService({
lifecycle: mockServices.rootLifecycle.mock(),
});
mockServerStartedFn();
const response = await request(app).get('/v1/readiness').expect(200);
expect(response.body).toEqual({ status: 'ok' });
await expect(service.getReadiness()).resolves.toEqual({
status: 200,
payload: { status: 'ok' },
});
});
it(`should return a 500 response if the server has stopped`, async () => {
@@ -57,25 +55,28 @@ describe('createHealthRouter', () => {
fn => (mockServerStoppedFn = fn),
);
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
const service = new DefaultHealthService({
lifecycle: mockServices.rootLifecycle.mock(),
});
mockServerStartedFn();
mockServerStoppedFn();
const response = await request(app).get('/v1/readiness');
expect(response.status).toBe(500);
await expect(service.getReadiness()).resolves.toEqual({
status: 503,
});
});
});
describe('liveness', () => {
it('should return 200 if the server has started', async () => {
const lifecycle = mockServices.rootLifecycle.mock();
const service = new DefaultHealthService({
lifecycle: mockServices.rootLifecycle.mock(),
});
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
const response = await request(app).get('/v1/liveness').expect(200);
expect(response.body).toEqual({ status: 'ok' });
await expect(service.getLiveness()).resolves.toEqual({
status: 200,
payload: { status: 'ok' },
});
});
});
});
@@ -0,0 +1,63 @@
import {
HealthService,
RootLifecycleService,
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
/*
* Copyright 2024 The Backstage Authors
*
* 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.
*/
/** @internal */
export class DefaultHealthService implements HealthService {
#isRunning = false;
constructor(readonly options: { lifecycle: RootLifecycleService }) {
options.lifecycle.addStartupHook(() => {
this.#isRunning = true;
});
options.lifecycle.addShutdownHook(() => {
this.#isRunning = false;
});
}
async getLiveness(): Promise<{ status: number; payload?: any }> {
return { status: 200, payload: { status: 'ok' } };
}
async getReadiness(): Promise<{ status: number; payload?: any }> {
if (!this.#isRunning) {
return {
status: 503,
payload: { message: 'Backend has not started yet', status: 'error' },
};
}
return { status: 200, payload: { status: 'ok' } };
}
}
/**
* @public
*/
export const healthServiceFactory = createServiceFactory({
service: coreServices.health,
deps: {
lifecycle: coreServices.rootLifecycle,
},
async factory({ lifecycle }) {
return new DefaultHealthService({ lifecycle });
},
});