Add healthchecks and test image before push

This commit is contained in:
Reginald Bondoc
2022-12-11 22:36:46 +01:00
parent 8896e1232b
commit 752ebaa3eb
11 changed files with 389 additions and 93 deletions

View File

@@ -2,11 +2,14 @@ FROM node:16-bullseye-slim
WORKDIR /app
COPY package*.json .
COPY package.json package-lock.json ./
RUN npm install
RUN npm ci --only-production
COPY . .
CMD ["npm", "run", "start"]
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
CMD ["npm", "run", "start"]

27
backend/healthcheck.js Normal file
View File

@@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-console */
/* eslint-disable no-undef */
const http = require('http');
const PORT = process.env.PORT || 4000;
const options = {
host: 'localhost',
port: PORT,
timeout: 2000,
path: '/healthcheck'
};
const healthCheck = http.request(options, (res) => {
console.log(`HEALTHCHECK STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', function (err) {
console.error(err);
process.exit(1);
});
healthCheck.end();

View File

@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
@@ -2028,6 +2029,14 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@godaddy/terminus": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@godaddy/terminus/-/terminus-4.11.2.tgz",
"integrity": "sha512-e/kbOWpGKME42eltM/wXM3RxSUOrfureZxEd6Dt6NXyFoJ7E8lnmm7znXydJsL3B7ky4HRFZI+eHrep54NZbeQ==",
"dependencies": {
"stoppable": "^1.1.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
@@ -6731,7 +6740,129 @@
"treeverse",
"validate-npm-package-name",
"which",
"write-file-atomic"
"write-file-atomic",
"@colors/colors",
"@gar/promisify",
"@npmcli/disparity-colors",
"@npmcli/git",
"@npmcli/installed-package-contents",
"@npmcli/metavuln-calculator",
"@npmcli/move-file",
"@npmcli/name-from-folder",
"@npmcli/node-gyp",
"@npmcli/promise-spawn",
"@npmcli/query",
"@tootallnate/once",
"agent-base",
"agentkeepalive",
"aggregate-error",
"ansi-regex",
"ansi-styles",
"aproba",
"are-we-there-yet",
"asap",
"balanced-match",
"bin-links",
"binary-extensions",
"brace-expansion",
"builtins",
"cidr-regex",
"clean-stack",
"clone",
"cmd-shim",
"color-convert",
"color-name",
"color-support",
"common-ancestor-path",
"concat-map",
"console-control-strings",
"cssesc",
"debug",
"debuglog",
"defaults",
"delegates",
"depd",
"dezalgo",
"diff",
"emoji-regex",
"encoding",
"env-paths",
"err-code",
"fs.realpath",
"function-bind",
"gauge",
"has",
"has-flag",
"has-unicode",
"http-cache-semantics",
"http-proxy-agent",
"https-proxy-agent",
"humanize-ms",
"iconv-lite",
"ignore-walk",
"imurmurhash",
"indent-string",
"infer-owner",
"inflight",
"inherits",
"ip",
"ip-regex",
"is-core-module",
"is-fullwidth-code-point",
"is-lambda",
"isexe",
"json-stringify-nice",
"jsonparse",
"just-diff",
"just-diff-apply",
"lru-cache",
"minipass-collect",
"minipass-fetch",
"minipass-flush",
"minipass-json-stream",
"minipass-sized",
"minizlib",
"mute-stream",
"negotiator",
"normalize-package-data",
"npm-bundled",
"npm-normalize-package-bin",
"npm-packlist",
"once",
"path-is-absolute",
"postcss-selector-parser",
"promise-all-reject-late",
"promise-call-limit",
"promise-inflight",
"promise-retry",
"promzard",
"read-cmd-shim",
"readable-stream",
"retry",
"safe-buffer",
"safer-buffer",
"set-blocking",
"signal-exit",
"smart-buffer",
"socks",
"socks-proxy-agent",
"spdx-correct",
"spdx-exceptions",
"spdx-expression-parse",
"spdx-license-ids",
"string_decoder",
"string-width",
"strip-ansi",
"supports-color",
"unique-filename",
"unique-slug",
"util-deprecate",
"validate-npm-package-license",
"walk-up-path",
"wcwidth",
"wide-align",
"wrappy",
"yallist"
],
"dev": true,
"dependencies": {
@@ -10192,6 +10323,15 @@
"node": ">= 0.8"
}
},
"node_modules/stoppable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
"engines": {
"node": ">=4",
"npm": ">=6"
}
},
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
@@ -12607,6 +12747,14 @@
"strip-json-comments": "^3.1.1"
}
},
"@godaddy/terminus": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@godaddy/terminus/-/terminus-4.11.2.tgz",
"integrity": "sha512-e/kbOWpGKME42eltM/wXM3RxSUOrfureZxEd6Dt6NXyFoJ7E8lnmm7znXydJsL3B7ky4HRFZI+eHrep54NZbeQ==",
"requires": {
"stoppable": "^1.1.0"
}
},
"@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
@@ -18631,6 +18779,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
"stoppable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="
},
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",

View File

@@ -1,5 +1,6 @@
{
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-console */
import http from 'http';
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
@@ -7,63 +9,67 @@ import dotenv from 'dotenv';
dotenv.config();
import * as Sentry from '@sentry/node';
import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL, POSTHOG_PROJECT_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from './config';
import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL } from './config';
import { apiLimiter } from './helpers/rateLimiter';
import { createTerminus } from '@godaddy/terminus';
const app = express();
Sentry.init({
dsn: SENTRY_DSN,
tracesSampleRate: 1.0,
debug: NODE_ENV === 'production' ? false : true,
environment: NODE_ENV
dsn: SENTRY_DSN,
tracesSampleRate: 1.0,
debug: NODE_ENV === 'production' ? false : true,
environment: NODE_ENV
});
import {
signup as signupRouter,
auth as authRouter,
organization as organizationRouter,
workspace as workspaceRouter,
membershipOrg as membershipOrgRouter,
membership as membershipRouter,
key as keyRouter,
inviteOrg as inviteOrgRouter,
user as userRouter,
userAction as userActionRouter,
secret as secretRouter,
serviceToken as serviceTokenRouter,
password as passwordRouter,
stripe as stripeRouter,
integration as integrationRouter,
integrationAuth as integrationAuthRouter
signup as signupRouter,
auth as authRouter,
organization as organizationRouter,
workspace as workspaceRouter,
membershipOrg as membershipOrgRouter,
membership as membershipRouter,
key as keyRouter,
inviteOrg as inviteOrgRouter,
user as userRouter,
userAction as userActionRouter,
secret as secretRouter,
serviceToken as serviceTokenRouter,
password as passwordRouter,
stripe as stripeRouter,
integration as integrationRouter,
integrationAuth as integrationAuthRouter
} from './routes';
const connectWithRetry = () => {
mongoose.connect(MONGO_URL)
.then(() => console.log('Successfully connected to DB'))
.catch((e) => {
console.log('Failed to connect to DB ', e);
setTimeout(() => {
console.log(e);
}, 5000);
});
}
mongoose
.connect(MONGO_URL)
.then(() => console.log('Successfully connected to DB'))
.catch((e) => {
console.log('Failed to connect to DB ', e);
setTimeout(() => {
console.log(e);
}, 5000);
});
};
connectWithRetry();
app.enable('trust proxy');
app.use(cookieParser());
app.use(cors({
credentials: true,
origin: SITE_URL
}));
app.use(
cors({
credentials: true,
origin: SITE_URL
})
);
if (NODE_ENV === 'production') {
// enable app-wide rate-limiting + helmet security
// in production
app.disable('x-powered-by');
app.use(apiLimiter);
app.use(helmet());
// enable app-wide rate-limiting + helmet security
// in production
app.disable('x-powered-by');
app.use(apiLimiter);
app.use(helmet());
}
app.use(express.json());
@@ -86,6 +92,31 @@ app.use('/api/v1/stripe', stripeRouter);
app.use('/api/v1/integration', integrationRouter);
app.use('/api/v1/integration-auth', integrationAuthRouter);
app.listen(PORT, () => {
console.log('Listening on PORT ' + PORT);
const server = http.createServer(app);
const onSignal = () => {
console.log('Server is starting clean-up');
return Promise.all([
// your clean logic, like closing database connections
]);
};
const healthCheck = () => {
// `state.isShuttingDown` (boolean) shows whether the server is shutting down or not
return Promise
.resolve
// optionally include a resolve value to be included as
// info in the health check response
();
};
createTerminus(server, {
healthChecks: {
'/healthcheck': healthCheck,
onSignal
}
});
server.listen(PORT, () => {
console.log('Listening on PORT ' + PORT);
});