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

@@ -0,0 +1,30 @@
version: '3'
services:
backend:
container_name: infisical-backend-test
restart: unless-stopped
depends_on:
- mongo
image: infisical/backend:test
command: npm run start
environment:
- NODE_ENV=production
- MONGO_URL=mongodb://test:example@mongo:27017/?authSource=admin
- MONGO_USERNAME=test
- MONGO_PASSWORD=example
networks:
- infisical-test
mongo:
container_name: infisical-mongo-test
image: mongo
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=test
- MONGO_INITDB_ROOT_PASSWORD=example
networks:
- infisical-test
networks:
infisical-test:

26
.github/resources/healthcheck.sh vendored Executable file
View File

@@ -0,0 +1,26 @@
# Name of the target container to check
container_name="$1"
# Timeout in seconds. Default: 60
timeout=$((${2:-60}));
if [ -z $container_name ]; then
echo "No container name specified";
exit 1;
fi
echo "Container: $container_name";
echo "Timeout: $timeout sec";
try=0;
is_healthy="false";
while [ $is_healthy != "true" ];
do
try=$(($try + 1));
printf "■";
is_healthy=$(docker inspect --format='{{json .State.Health}}' $container_name | jq '.Status == "healthy"');
sleep 1;
if [[ $try -eq $timeout ]]; then
echo " Container was not ready within timeout";
exit 1;
fi
done

View File

@@ -3,40 +3,38 @@ name: Push to Docker Hub
on: [workflow_dispatch] on: [workflow_dispatch]
jobs: jobs:
backend-image: backend-image:
name: Build backend image name: Build backend image
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: ☁️ Checkout source
name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- - name: 🔧 Set up QEMU
name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- - name: 🔧 Set up Docker Buildx
name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- - name: 🐋 Login to Docker Hub
name: 🐋 Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# - - name: 📦 Build backend and export to Docker
# name: 📦 Build backend and export to Docker uses: docker/build-push-action@v3
# uses: docker/build-push-action@v3 with:
# with: load: true
# load: true context: backend
# context: backend tags: infisical/backend:test
# tags: infisical/backend:test - name: ⏻ Spawn backend container and dependencies
# - run: |
# name: 🧪 Test backend image docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
# run: | - name: 🧪 Test backend image
# docker run --rm infisical/backend:test run: |
- ./.github/resources/healthcheck.sh infisical-backend-test
name: 🏗️ Build backend and push - name: ⏻ Shut down backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true
@@ -44,42 +42,40 @@ jobs:
tags: infisical/backend:latest tags: infisical/backend:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
frontend-image: frontend-image:
name: Build frontend image name: Build frontend image
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: ☁️ Checkout source
name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- - name: 🔧 Set up QEMU
name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- - name: 🔧 Set up Docker Buildx
name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- - name: 🐋 Login to Docker Hub
name: 🐋 Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# - - name: 📦 Build frontend and export to Docker
# name: 📦 Build frontend and export to Docker uses: docker/build-push-action@v3
# uses: docker/build-push-action@v3 with:
# with: load: true
# load: true context: frontend
# context: frontend tags: infisical/frontend:test
# tags: infisical/frontend:test build-args: |
# build-args: | POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
# POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} - name: ⏻ Spawn frontend container
# - run: |
# name: 🧪 Test frontend image docker run -d --rm --name infisical-frontend-test infisical/frontend:test
# run: | - name: 🧪 Test frontend image
# docker run --rm infisical/frontend:test run: |
- ./.github/resources/healthcheck.sh infisical-frontend-test
name: 🏗️ Build frontend and push - name: ⏻ Shut down frontend container
run: |
docker stop infisical-frontend-test
- name: 🏗️ Build frontend and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true

View File

@@ -2,11 +2,14 @@ FROM node:16-bullseye-slim
WORKDIR /app WORKDIR /app
COPY package*.json . COPY package.json package-lock.json ./
RUN npm install RUN npm ci --only-production
COPY . . 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", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
@@ -2028,6 +2029,14 @@
"url": "https://opencollective.com/eslint" "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": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.7", "version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
@@ -6731,7 +6740,129 @@
"treeverse", "treeverse",
"validate-npm-package-name", "validate-npm-package-name",
"which", "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, "dev": true,
"dependencies": { "dependencies": {
@@ -10192,6 +10323,15 @@
"node": ">= 0.8" "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": { "node_modules/strict-uri-encode": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "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" "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": { "@humanwhocodes/config-array": {
"version": "0.11.7", "version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", "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", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" "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": { "strict-uri-encode": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",

View File

@@ -1,5 +1,6 @@
{ {
"dependencies": { "dependencies": {
"@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@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 express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
import cors from 'cors'; import cors from 'cors';
@@ -7,8 +9,9 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
import * as Sentry from '@sentry/node'; 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 { apiLimiter } from './helpers/rateLimiter';
import { createTerminus } from '@godaddy/terminus';
const app = express(); const app = express();
@@ -39,7 +42,8 @@ import {
} from './routes'; } from './routes';
const connectWithRetry = () => { const connectWithRetry = () => {
mongoose.connect(MONGO_URL) mongoose
.connect(MONGO_URL)
.then(() => console.log('Successfully connected to DB')) .then(() => console.log('Successfully connected to DB'))
.catch((e) => { .catch((e) => {
console.log('Failed to connect to DB ', e); console.log('Failed to connect to DB ', e);
@@ -47,16 +51,18 @@ const connectWithRetry = () => {
console.log(e); console.log(e);
}, 5000); }, 5000);
}); });
} };
connectWithRetry(); connectWithRetry();
app.enable('trust proxy'); app.enable('trust proxy');
app.use(cookieParser()); app.use(cookieParser());
app.use(cors({ app.use(
cors({
credentials: true, credentials: true,
origin: SITE_URL origin: SITE_URL
})); })
);
if (NODE_ENV === 'production') { if (NODE_ENV === 'production') {
// enable app-wide rate-limiting + helmet security // enable app-wide rate-limiting + helmet security
@@ -86,6 +92,31 @@ app.use('/api/v1/stripe', stripeRouter);
app.use('/api/v1/integration', integrationRouter); app.use('/api/v1/integration', integrationRouter);
app.use('/api/v1/integration-auth', integrationAuthRouter); app.use('/api/v1/integration-auth', integrationAuthRouter);
app.listen(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); console.log('Listening on PORT ' + PORT);
}); });

View File

@@ -60,5 +60,8 @@ EXPOSE 3000
ENV PORT 3000 ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED 1
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node scripts/healthcheck.js
CMD ["/app/scripts/start.sh"] CMD ["/app/scripts/start.sh"]

View File

@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-console */
/* eslint-disable no-undef */
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
timeout: 2000,
path: '/'
};
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();