server/index.js (149 lines of code) (raw):
// Copyright (c) 2022 EPAM Systems, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
const Knex = require('knex');
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function accessSecretVersion(secretName) {
const [version] = await client.accessSecretVersion({name: secretName});
return version.payload.data;
}
async function createTcpPool(config) {
const pair = process.env.DB_HOST.split(':');
return Knex({
client: 'pg',
connection: {
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: pair[0],
port: pair[1]
},
...config,
});
}
async function createUnixSocketPool(config) {
const dbSocketPath = process.env.DB_SOCKET_PATH || '/cloudsql';
return Knex({
client: 'pg',
connection: {
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: `${dbSocketPath}/${process.env.INSTANCE_CONNECTION_NAME}`,
},
...config,
});
}
async function createPool() {
const config = {pool: {}};
config.pool.max = 5;
config.pool.min = 3;
config.pool.acquireTimeoutMillis = 60000; // 60 seconds
config.pool.createTimeoutMillis = 30000; // 30 seconds
config.pool.idleTimeoutMillis = 600000; // 10 minutes
config.pool.createRetryIntervalMillis = 200; // 0.2 seconds
const {CLOUD_SQL_CREDENTIALS_SECRET} = process.env;
if (CLOUD_SQL_CREDENTIALS_SECRET) {
const secrets = await accessSecretVersion(CLOUD_SQL_CREDENTIALS_SECRET);
try {
process.env.DB_PASS = secrets.toString();
} catch (err) {
err.message = `Unable to parse secret from Secret Manager. Make sure that the secret is JSON formatted: \n ${err.message} `;
throw err;
}
}
if (process.platform === 'darwin') {
return createTcpPool(config);
}
return createUnixSocketPool(config);
}
//@TODO Introduce proper migration
async function ensureSchema(pool) {
const hasTable = await pool.schema.hasTable('stackEvent');
if (!hasTable) {
return pool.schema.createTable('stackEvent', table => {
table.increments('id').primary();
table.timestamps(true, true);
table.string('stackId').notNullable();
table.string('name').notNullable();
table.string('status').notNullable();
table.string('initiator').notNullable();
table.string('project').notNullable();
table.string('gcpUserAccount');
});
}
console.info("Ensured that table 'stackEvent' exists");
}
let poolValue;
async function pool() {
if (poolValue) {
return poolValue;
}
poolValue = await createPool();
await ensureSchema(poolValue);
return poolValue;
}
let sharedSecretValue;
async function sharedSecret() {
if (sharedSecretValue) {
return sharedSecretValue;
}
const {SHARED_SECRET} = process.env;
if (SHARED_SECRET) {
const secrets = await accessSecretVersion(SHARED_SECRET);
try {
sharedSecretValue = secrets.toString();
return sharedSecretValue;
} catch (err) {
err.message = `Unable to parse secret from Secret Manager. Make sure that the secret is JSON formatted: \n ${err.message} `;
throw err;
}
}
}
exports.event = async (req, res) => {
switch (req.method) {
case 'POST':
const key = await sharedSecret();
if (key) {
if (req.headers.authorization !== key) {
res
.status(403)
.send("")
return
}
}
const payload = req.body
if (!payload.stackId || !payload.name || !payload.status
|| !payload.initiator || !payload.project) {
res
.status(400)
.send("")
return
}
const event = {
stackId: payload.stackId,
name: payload.name,
status: payload.status,
initiator: payload.initiator,
project: payload.project,
gcpUserAccount: payload.gcpUserAccount,
created_at: new Date(),
updated_at: new Date(),
}
try {
const db = await pool();
await db('stackEvent').insert(event);
} catch (err) {
console.error(err)
res
.status(503)
.send(err)
return;
}
res
.status(200)
.send("")
break;
default:
res
.status(400)
.send("Sorry, not supported")
}
}