From f486d50efa13087a68d1a0e201db92b927ba80ee Mon Sep 17 00:00:00 2001 From: Dunemask Date: Sun, 19 Mar 2023 13:53:37 -0400 Subject: [PATCH] Minor Adjustments --- lib/server/core/Qualiteer.js | 1 + lib/server/core/client-listeners.js | 4 +- lib/server/core/socket-server.js | 1 - lib/server/database/postgres.js | 3 +- lib/server/rabbit/workers/KubeJobsWorker.js | 48 ++++++ lib/server/rabbit/workers/index.js | 3 +- libold/common/executor/executor-bundler.js | 38 +++++ libold/common/executor/executor-config.js | 5 + .../common/executor/executor-configurator.js | 17 ++ libold/common/executor/executor-entrypoint.js | 14 ++ libold/common/executor/rollup.config.js | 17 ++ libold/common/sockets/clients/Executor.js | 76 +++++++++ libold/common/sockets/clients/Initiator.js | 105 ++++++++++++ libold/common/sockets/clients/Viewer.js | 26 +++ libold/common/sockets/clients/index.js | 5 + libold/common/sockets/clients/web.index.js | 3 + libold/common/sockets/events.js | 15 ++ libold/common/sockets/modes.js | 8 + libold/index.js | 1 + libold/server/core/JobManager.js | 71 ++++++++ libold/server/core/Qualiteer.js | 56 +++++++ libold/server/core/client-listeners.js | 34 ++++ libold/server/core/crons.js | 13 ++ libold/server/core/socket-server.js | 54 +++++++ libold/server/database/delays.js | 23 +++ .../migrations/1_create_catalog_table.sql | 23 +++ .../migrations/2_create_results_table.sql | 15 ++ .../migrations/3_create_alerting_table.sql | 9 ++ libold/server/database/mocks/alerting-mock.js | 11 ++ libold/server/database/mocks/catalog-mock.js | 153 ++++++++++++++++++ libold/server/database/mocks/results-mock.js | 30 ++++ libold/server/database/pg-query.js | 121 ++++++++++++++ libold/server/database/postgres.js | 62 +++++++ libold/server/database/queries/alerting.js | 56 +++++++ libold/server/database/queries/catalog.js | 121 ++++++++++++++ libold/server/database/queries/results.js | 93 +++++++++++ libold/server/database/seeds/alerting-seed.js | 11 ++ libold/server/database/seeds/catalog-seed.js | 126 +++++++++++++++ libold/server/database/seeds/results-seed.js | 29 ++++ libold/server/database/tags.js | 26 +++ libold/server/k8s/k8s-common.js | 58 +++++++ libold/server/k8s/k8s-internal-engine.js | 14 ++ libold/server/k8s/k8s-internal.js | 20 +++ libold/server/k8s/k8s-job.json | 34 ++++ libold/server/k8s/k8s.js | 19 +++ libold/server/rabbit/rabbit-workers.js | 22 +++ .../server/rabbit/workers/KubeJobsWorker.js | 19 +++ .../rabbit/workers/TestResultsWorker.js | 45 ++++++ libold/server/rabbit/workers/index.js | 4 + libold/server/routes/alerting-route.js | 41 +++++ libold/server/routes/catalog-route.js | 44 +++++ libold/server/routes/dev-route.js | 13 ++ libold/server/routes/react-route.js | 8 + libold/server/routes/results-route.js | 27 ++++ libold/server/routes/router.js | 30 ++++ libold/server/routes/vitals-route.js | 7 + libold/server/util/logging.js | 28 ++++ package-lock.json | 130 ++------------- package.json | 2 +- public/assets/new-logo.png | Bin 0 -> 193218 bytes 60 files changed, 1965 insertions(+), 127 deletions(-) create mode 100644 lib/server/rabbit/workers/KubeJobsWorker.js create mode 100644 libold/common/executor/executor-bundler.js create mode 100644 libold/common/executor/executor-config.js create mode 100644 libold/common/executor/executor-configurator.js create mode 100644 libold/common/executor/executor-entrypoint.js create mode 100644 libold/common/executor/rollup.config.js create mode 100644 libold/common/sockets/clients/Executor.js create mode 100644 libold/common/sockets/clients/Initiator.js create mode 100644 libold/common/sockets/clients/Viewer.js create mode 100644 libold/common/sockets/clients/index.js create mode 100644 libold/common/sockets/clients/web.index.js create mode 100644 libold/common/sockets/events.js create mode 100644 libold/common/sockets/modes.js create mode 100644 libold/index.js create mode 100644 libold/server/core/JobManager.js create mode 100644 libold/server/core/Qualiteer.js create mode 100644 libold/server/core/client-listeners.js create mode 100644 libold/server/core/crons.js create mode 100644 libold/server/core/socket-server.js create mode 100644 libold/server/database/delays.js create mode 100644 libold/server/database/migrations/1_create_catalog_table.sql create mode 100644 libold/server/database/migrations/2_create_results_table.sql create mode 100644 libold/server/database/migrations/3_create_alerting_table.sql create mode 100644 libold/server/database/mocks/alerting-mock.js create mode 100644 libold/server/database/mocks/catalog-mock.js create mode 100644 libold/server/database/mocks/results-mock.js create mode 100644 libold/server/database/pg-query.js create mode 100644 libold/server/database/postgres.js create mode 100644 libold/server/database/queries/alerting.js create mode 100644 libold/server/database/queries/catalog.js create mode 100644 libold/server/database/queries/results.js create mode 100644 libold/server/database/seeds/alerting-seed.js create mode 100644 libold/server/database/seeds/catalog-seed.js create mode 100644 libold/server/database/seeds/results-seed.js create mode 100644 libold/server/database/tags.js create mode 100644 libold/server/k8s/k8s-common.js create mode 100644 libold/server/k8s/k8s-internal-engine.js create mode 100644 libold/server/k8s/k8s-internal.js create mode 100644 libold/server/k8s/k8s-job.json create mode 100644 libold/server/k8s/k8s.js create mode 100644 libold/server/rabbit/rabbit-workers.js create mode 100644 libold/server/rabbit/workers/KubeJobsWorker.js create mode 100644 libold/server/rabbit/workers/TestResultsWorker.js create mode 100644 libold/server/rabbit/workers/index.js create mode 100644 libold/server/routes/alerting-route.js create mode 100644 libold/server/routes/catalog-route.js create mode 100644 libold/server/routes/dev-route.js create mode 100644 libold/server/routes/react-route.js create mode 100644 libold/server/routes/results-route.js create mode 100644 libold/server/routes/router.js create mode 100644 libold/server/routes/vitals-route.js create mode 100644 libold/server/util/logging.js create mode 100644 public/assets/new-logo.png diff --git a/lib/server/core/Qualiteer.js b/lib/server/core/Qualiteer.js index 79d4bb5..3293c10 100644 --- a/lib/server/core/Qualiteer.js +++ b/lib/server/core/Qualiteer.js @@ -34,6 +34,7 @@ export default class Qualiteer { this.routes = buildRoutes(this.pg, this.sockets); this.rabbiteer = buildRabbiteer(this.pg, this.sockets); this.app.use(this.routes); + OK("INIT", "Initialized!"); } async _connect() { diff --git a/lib/server/core/client-listeners.js b/lib/server/core/client-listeners.js index 8a1fd87..e74f499 100644 --- a/lib/server/core/client-listeners.js +++ b/lib/server/core/client-listeners.js @@ -3,8 +3,8 @@ import evt from "../../common/sockets/events.js"; export const initiator = async (socket, jobs) => { const jobStr = socket.handshake.query.job; const jobReq = JSON.parse(jobStr); - - if (!jobReq || !(jobReq instanceof Object)) + + if (!jobReq || !(jobReq instanceof Object)) throw Error("No 'job' was included in the handshake query"); const job = await jobs.newJob(jobReq, socket.id); diff --git a/lib/server/core/socket-server.js b/lib/server/core/socket-server.js index eebcf37..8e712f0 100644 --- a/lib/server/core/socket-server.js +++ b/lib/server/core/socket-server.js @@ -49,7 +49,6 @@ const applySockets = (server, jobs, options) => { io.on("connection", (socket) => socketConnect(io, socket, jobs)); io.of("/").adapter.on("leave-room", (room, id) => socketDrop(io, room, id)); return io; - cle; }; export default applySockets; diff --git a/lib/server/database/postgres.js b/lib/server/database/postgres.js index 58ff79a..c716b96 100644 --- a/lib/server/database/postgres.js +++ b/lib/server/database/postgres.js @@ -5,6 +5,7 @@ import { migrate } from "postgres-migrations"; import createPgp from "pg-promise"; import moment from "moment"; import { INFO, WARN, OK, VERB } from "../util/logging.js"; + // Environment Variables const { QUALITEER_POSTGRES_DATABASE: database, @@ -46,7 +47,7 @@ const connect = (pg) => async () => { // Override fake methods const pgInstance = pgp(dbConfig); for (var k in pgInstance) pg[k] = pgInstance[k]; - VERB("POSTGRES", "Migrated Successfully"); + VERB("POSTGRES", "Migrated Successfully!"); await pg.connect(); VERB("POSTGRES", "Postgres connected Successfully!"); diff --git a/lib/server/rabbit/workers/KubeJobsWorker.js b/lib/server/rabbit/workers/KubeJobsWorker.js new file mode 100644 index 0000000..3a947f5 --- /dev/null +++ b/lib/server/rabbit/workers/KubeJobsWorker.js @@ -0,0 +1,48 @@ +// Imports +import { Worker } from "rabbiteer"; + +// Constants +const jobQueueName = "KubeJobs"; +const jobQueueRoutingName = "KubeJobsRouting"; +const exchangeName = "KubeJobsExchange"; +const setQueues = { + SET1Sec: 1000, + SET5Sec: 5000, + SET10Sec: 10_000, + SET30Sec: 30_000, + SET1Min: 60_000, + SET5Min: 60_000 * 5, + SET10Min: 60_000 * 10, + SET15Min: 60_000 * 15, + SET30Min: 60_000 * 30, + SET1Hr: 360_000, + SET2Hr: 360_000 * 2, + SET3Hr: 360_000 * 3, + SET4Hr: 360_000 * 4, +}; + +// Class +export default class KubeJobsWorker extends Worker { + constructor(skio) { + super(jobQueueName); + this.skio = skio; + } + + async configure(ch) { + await ch.assertExchange(exchangeName, "direct"); + await ch.assertQueue(this.queue, this.queueOptions); + await ch.bindQueue(this.queue, exchangeName, jobQueueRoutingName); + await this.configureSetQueues(ch); + await ch.consume(this.queue, (msg) => this.consume(msg, () => ch.ack(msg))); + } + + // Configure set queues that will all filter into this queue + async configureSetQueues(ch) { + await Promise.all(Object.keys(setQueues).map((k)=> + ch.assertQueue(k, { messageTtl: setQueues[k], deadLetterExchange: exchangeName, deadLetterRoutingKey: jobQueueRoutingName }))) + } + + onMessage(string) { + console.log(string); + } +} diff --git a/lib/server/rabbit/workers/index.js b/lib/server/rabbit/workers/index.js index b461244..b5954b7 100644 --- a/lib/server/rabbit/workers/index.js +++ b/lib/server/rabbit/workers/index.js @@ -1,4 +1,5 @@ +import KubeJobsWorker from "./KubeJobsWorker.js"; import TestResultsWorker from "./TestResultsWorker.js"; -const buildWorkers = (skio) => [new TestResultsWorker(skio)]; +const buildWorkers = (skio) => [new TestResultsWorker(skio), new KubeJobsWorker(skio)]; export default buildWorkers; diff --git a/libold/common/executor/executor-bundler.js b/libold/common/executor/executor-bundler.js new file mode 100644 index 0000000..012ba16 --- /dev/null +++ b/libold/common/executor/executor-bundler.js @@ -0,0 +1,38 @@ +import { URL } from "node:url"; +import path from "node:path"; +import caxa from "caxa"; +import { rollup } from "rollup"; +import loadConfigFile from "rollup/loadConfigFile"; +import { executorLibraryDir, binName, scriptName } from "./executor-config.js"; +// Fix import +const { default: caxaPackage } = caxa; +// Rollup Config +const rollupConfigPath = path.resolve(executorLibraryDir, "rollup.config.js"); + +// Build functions +async function packageBin() { + console.log("Packaging bundle into binary"); + return caxaPackage({ + input: "dist/bundles/", + output: `bin/${binName}`, + command: ["{{caxa}}/node_modules/.bin/node", `{{caxa}}/${scriptName}`], + uncompressionMessage: "Unpacking, please wait...", + }); +} + +async function rollupBundle() { + console.log("Rolling up executor into bundle"); + const { options, warnings } = await loadConfigFile(rollupConfigPath); + if (warnings.count !== 0) + console.log(`Rollup has ${warnings.count} warnings`); + warnings.flush(); + + for (const optionsObj of options) { + const bundle = await rollup(optionsObj); + await Promise.all(optionsObj.output.map(bundle.write)); + } +} + +await rollupBundle(); +await packageBin(); +console.log("Done"); diff --git a/libold/common/executor/executor-config.js b/libold/common/executor/executor-config.js new file mode 100644 index 0000000..9020020 --- /dev/null +++ b/libold/common/executor/executor-config.js @@ -0,0 +1,5 @@ +export const executorLibraryDir = new URL(".", import.meta.url).pathname; +export const binName = "qltr-executor"; +export const configName = "executor.config.mjs"; +export const scriptName = "qualiteer-executor.mjs"; +export const entrypointName = "executor-entrypoint.js"; diff --git a/libold/common/executor/executor-configurator.js b/libold/common/executor/executor-configurator.js new file mode 100644 index 0000000..e355624 --- /dev/null +++ b/libold/common/executor/executor-configurator.js @@ -0,0 +1,17 @@ +const funcify = (v) => () => v; + +export function verify(config) { + for (var k in config) { + if (typeof config[k] !== "function") + throw Error("All config options must be functions!"); + } +} + +export function normalize(conf) { + const config = { ...conf }; + for (var k in config) { + if (typeof config[k] === "function") continue; + config[k] = funcify(config[k]); + } + return config; +} diff --git a/libold/common/executor/executor-entrypoint.js b/libold/common/executor/executor-entrypoint.js new file mode 100644 index 0000000..28418d3 --- /dev/null +++ b/libold/common/executor/executor-entrypoint.js @@ -0,0 +1,14 @@ +import path from "node:path"; +import Executor from "../sockets/clients/Executor.js"; +import { normalize } from "./executor-configurator.js"; +import { configName as executorConfigName } from "./executor-config.js"; +const executorConfigPath = path.resolve(executorConfigName); +const { default: executorConfig } = await import(executorConfigPath); + +// Load config and args +const args = process.argv.slice(2); +const payload = JSON.parse(Buffer.from(args[0], "base64").toString("utf8")); +const config = normalize(executorConfig(payload)); +// Start Executor +const exec = new Executor(config, payload); +exec.runJob(); diff --git a/libold/common/executor/rollup.config.js b/libold/common/executor/rollup.config.js new file mode 100644 index 0000000..09f068e --- /dev/null +++ b/libold/common/executor/rollup.config.js @@ -0,0 +1,17 @@ +import path from "node:path"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import { terser } from "rollup-plugin-terser"; +import { + executorLibraryDir, + entrypointName, + scriptName, +} from "./executor-config.js"; + +export default { + input: path.resolve(executorLibraryDir, entrypointName), + output: { + file: `dist/bundles/${scriptName}`, + }, + plugins: [nodeResolve(), commonjs(), terser()], +}; diff --git a/libold/common/sockets/clients/Executor.js b/libold/common/sockets/clients/Executor.js new file mode 100644 index 0000000..a53bfd1 --- /dev/null +++ b/libold/common/sockets/clients/Executor.js @@ -0,0 +1,76 @@ +import io from "socket.io-client"; +import cp from "child_process"; + +import modes from "../modes.js"; +import events from "../events.js"; + +export { default as events } from "../events.js"; +export { default as modes } from "../modes.js"; + +// Data Stream Types +const ERR = "e"; +const OUT = "o"; + +export default class Executor { + constructor(config, payload) { + this.url = config.url(payload) ?? process.env.QUALITEER_EXECUTOR_URL; + this.jobId = config.jobId(payload) ?? process.env.QUALITEER_JOB_ID; + this.command = config.command(payload) ?? process.env.QUALITEER_COMMAND; + this.mode = modes.EXEC; + + // Internal Buffer + this.buf = {}; + this.buf[ERR] = ""; + this.buf[OUT] = ""; + + // Methods + this.spawn = this.spawn.bind(this); + this.report = this.report.bind(this); + this.onProcClose = this.onProcClose.bind(this); + this.onClose = this.onClose.bind(this); + } + + spawn() { + const cmdArgs = this.command; + const cmd = cmdArgs.shift(); + this.proc = cp.spawn(cmd, cmdArgs); + + // Set Encoding + this.proc.stdout.setEncoding("utf8"); + this.proc.stderr.setEncoding("utf8"); + + // Process Events + this.proc.stdout.on("data", (d) => this.report(d.toString(), OUT)); + this.proc.stderr.on("data", (d) => this.report(d.toString(), ERR)); + this.proc.on("close", this.onProcClose); + } + + runJob() { + this.socket = io(this.url, { + query: { mode: this.mode, jobId: this.jobId }, + }); + this.socket.on("connect", this.spawn); + this.socket.on("disconnect", this.onClose); + } + + onClose() { + console.log("Server disconnected, terminating process."); + if (this.proc) this.proc.kill("SIGKILL"); + } + + onProcClose(code) { + this.socket.emit(events.JOB_CLS, code, () => this.socket.disconnect()); + console.log(`Process finished with code ${code}`); + } + + report(d, dType) { + this.buf[dType] += d; + if (!this.buf[dType].includes("\n")) return; + if (this.buf[dType].endsWith("\n")) + this.buf[dType] = this.buf[dType].slice(0, -1); + this.socket.emit(events.JOB_REP, this.buf[dType]); + if (dType === ERR) console.error(`err: ${this.buf[dType]}`); + else console.log(`out: ${this.buf[dType]}`); + this.buf[dType] = ""; + } +} diff --git a/libold/common/sockets/clients/Initiator.js b/libold/common/sockets/clients/Initiator.js new file mode 100644 index 0000000..fae86e0 --- /dev/null +++ b/libold/common/sockets/clients/Initiator.js @@ -0,0 +1,105 @@ +import { io } from "socket.io-client"; +import modes from "../modes.js"; +import events from "../events.js"; + +export { default as events } from "../events.js"; +export { default as modes } from "../modes.js"; + +export default class Initiator { + constructor(url, options = {}) { + this.url = url; + this.mode = modes.INIT; + this.onLog = options.onLog ?? ((d) => console.log(`job: ${d}`)); + this.onClose = options.onClose ?? (() => {}); + this.onCreate = options.onCreate ?? ((id) => console.log(`job id: ${id}`)); + this.onPipelineClose = + options.onPipelineClose ?? + (() => { + console.log("job pipeline closed"); + }); + this.sk = null; + } + + async newJob(jobRequest, onLog, onClose, onCreate) { + onLog = onLog ?? this.onLog.bind(this); + onClose = onClose ?? this.onClose.bind(this); + onCreate = onCreate ?? this.onCreate.bind(this); + const sk = io(this.url, { + query: { mode: this.mode, job: JSON.stringify(jobRequest) }, + }); + sk.on(events.JOB_LOG, onLog); + sk.on(events.JOB_CLS, function onJobClose(c) { + sk.disconnect(); + onClose(c); + }); + this.sk = sk; + return new Promise((res) => + sk.on(events.JOB_CRT, function onJobCreate(id) { + onCreate(id); + res({ ...jobRequest, id }); + }) + ); + } + + async newPipelineJob( + jobRequest, + onLog, + onClose, + onCreate, + onPipelineTrigger, + onPipelineClose + ) { + onLog = onLog ?? this.onLog.bind(this); + onClose = onClose ?? this.onClose.bind(this); + onCreate = onCreate ?? this.onCreate.bind(this); + onPipelineTrigger = + onPipelineTrigger ?? + ((pipeline) => { + console.log("job trg:", pipeline); + const { triggers } = pipeline; + if (!Object.keys(triggers).length) onPipelineClose(); + // For each trigger + for (var testName in triggers) { + const delay = triggers[testName].__testDelay ?? 0; + delete triggers[testName].__testDelay; + const jobReq = { + ...jobRequest, + pipeline: { + ...pipeline, + triggers: triggers[testName], + __test: testName, + }, + }; + setTimeout( + () => + this.newPipelineJob( + jobReq, + onLog, + onClose, + onCreate, + onPipelineTrigger, + onPipelineClose + ), + delay + ); + } + }); + onPipelineClose = onPipelineClose ?? this.onPipelineClose.bind(this); + const sk = io(this.url, { + query: { mode: this.mode, job: JSON.stringify(jobRequest) }, + }); + sk.on(events.JOB_LOG, onLog); + sk.on(events.JOB_CLS, function onJobClose(c) { + sk.disconnect(); + onClose(c); + }); + sk.on(events.PPL_TRG, onPipelineTrigger); + this.sk = sk; + return new Promise((res) => + sk.on(events.JOB_CRT, function onJobCreate(id) { + onCreate(id); + res({ ...jobRequest, id }); + }) + ); + } +} diff --git a/libold/common/sockets/clients/Viewer.js b/libold/common/sockets/clients/Viewer.js new file mode 100644 index 0000000..a4240d1 --- /dev/null +++ b/libold/common/sockets/clients/Viewer.js @@ -0,0 +1,26 @@ +import io from "socket.io-client"; +import modes from "../modes.js"; +import events from "../events.js"; + +export { default as events } from "../events.js"; +export { default as modes } from "../modes.js"; + +export default class Viewer { + constructor(url, options = {}) { + this.url = url; + this.mode = modes.VIEW; + this.onLog = options.onLog ?? console.log; + this.onClose = options.onClose ?? (() => {}); + } + + viewJob(jobId, onLog, onClose) { + onLog = onLog ?? this.onLog.bind(this); + onClose = onClose ?? this.onClose.bind(this); + const sk = io(this.url, { + query: { mode: this.mode, jobId }, + }); + sk.on(events.JOB_LOG, onLog); + sk.on(events.JOB_CLS, onClose); + return sk; + } +} diff --git a/libold/common/sockets/clients/index.js b/libold/common/sockets/clients/index.js new file mode 100644 index 0000000..fcc0f2d --- /dev/null +++ b/libold/common/sockets/clients/index.js @@ -0,0 +1,5 @@ +export { default as Initiator } from "./Initiator.js"; + +export { default as Viewer } from "./Viewer.js"; + +export { default as Executor } from "./Executor.js"; diff --git a/libold/common/sockets/clients/web.index.js b/libold/common/sockets/clients/web.index.js new file mode 100644 index 0000000..ce6f3e2 --- /dev/null +++ b/libold/common/sockets/clients/web.index.js @@ -0,0 +1,3 @@ +export { default as Initiator } from "./Initiator.js"; + +export { default as Viewer } from "./Viewer.js"; diff --git a/libold/common/sockets/events.js b/libold/common/sockets/events.js new file mode 100644 index 0000000..e03794a --- /dev/null +++ b/libold/common/sockets/events.js @@ -0,0 +1,15 @@ +const JOB_REP = "jr"; // Job Report Event +const JOB_LOG = "jl"; // Job Log Event +const JOB_CLS = "jc"; // Job Close Event +const JOB_CRT = "jcr"; // Job Create Event +const PPL_TRG = "plr"; // Pipeline Trigger Event +const ERR = "e"; // Socket Error + +export default { + JOB_REP, + JOB_LOG, + JOB_CLS, + JOB_CRT, + PPL_TRG, + ERR, +}; diff --git a/libold/common/sockets/modes.js b/libold/common/sockets/modes.js new file mode 100644 index 0000000..1495f48 --- /dev/null +++ b/libold/common/sockets/modes.js @@ -0,0 +1,8 @@ +const INIT = "i"; // Intiator Socket +const EXEC = "e"; // Execution Socket +const VIEW = "v"; // View Socket +export default { + INIT, + EXEC, + VIEW, +}; diff --git a/libold/index.js b/libold/index.js new file mode 100644 index 0000000..671ecfe --- /dev/null +++ b/libold/index.js @@ -0,0 +1 @@ +export { default } from "./server/core/Qualiteer.js"; diff --git a/libold/server/core/JobManager.js b/libold/server/core/JobManager.js new file mode 100644 index 0000000..70abe7b --- /dev/null +++ b/libold/server/core/JobManager.js @@ -0,0 +1,71 @@ +import { v4 } from "uuid"; +import { getTest } from "../database/queries/catalog.js"; +import applyJobInternally from "../k8s/k8s-internal.js"; +import applyJob from "../k8s/k8s.js"; + +const maxJobs = process.env.MAX_JOBS ? parseInt(process.env.MAX_JOBS) : 3; +const internalDeploy = process.env.INTERNAL_DEPLOY === "true"; +const launchJob = internalDeploy ? applyJobInternally : applyJob; + +async function getTests(job) { + if (job.pipeline) return [await getTest(job.pipeline.__test)]; + if (!job.testNames) return []; + const tests = await Promise.all(job.testNames.map((name) => getTest(name))); + return tests; +} + +class JobManager { + constructor() { + this.clientMaxJobs = maxJobs; + this.clients = {}; + } + + getJob(clientId, jobId) { + return this.clients[clientId].jobs.find((j) => j.id === jobId); + } + + getJobById(jobId) { + for (var client of Object.values(this.clients)) { + const job = client.jobs.find((j) => j.id === jobId); + if (!job) continue; + return job; + } + } + + pushLog(jobId, log) { + const job = this.getJobById(jobId); + if (!job) return; + + if (log instanceof Array) job.log.push(...log); + else job.log.push(log); + } + + closeJob(jobId, exitcode) { + const job = this.getJobById(jobId); + if (!job) return; + job.exitcode = exitcode; + } + + async newJob(jobRequest, id) { + if (!jobRequest) throw Error("Request Must Be Object!"); + if (!this.clients[id]) this.clients[id] = { jobs: [] }; + const job = { ...jobRequest }; + job.image = "registry.dunemask.net/garden/dev/reed:latest"; + job.id = v4(); + job.log = []; + this.clients[id].jobs.push(job); + job.dashboardSocketId = id; + job.tests = await getTests(job); + for (var t of job.tests) if (!t) throw Error("1 or more tests not found!"); + launchJob(job); + return { ...job }; + } + + removeJob(clientId, id) { + this.clients[clientId].jobs = this.clients[clientId].jobs.filter( + (j) => j.id !== id + ); + } +} + +export default new JobManager(); diff --git a/libold/server/core/Qualiteer.js b/libold/server/core/Qualiteer.js new file mode 100644 index 0000000..79d4bb5 --- /dev/null +++ b/libold/server/core/Qualiteer.js @@ -0,0 +1,56 @@ +// Imports +import fig from "figlet"; +import http from "http"; +import express from "express"; +import { INFO, OK, logInfo } from "../util/logging.js"; + +// Import Core Modules +import buildRoutes from "../routes/router.js"; +import pg from "../database/postgres.js"; +import injectSockets from "./socket-server.js"; +import JobManager from "./JobManager.js"; +import buildRabbiteer from "../rabbit/rabbit-workers.js"; + +// Constants +const title = "QLTR"; +const rabbiteerEnabled = process.env.QUALITEER_RABBITEER_ENABLED !== "false"; +const port = process.env.QUALITEER_DEV_PORT ?? 52000; + +// Class +export default class Qualiteer { + constructor(options = {}) { + for (var k in options) this[k] = options[k]; + this.jobs = JobManager; + this.port = options.port ?? port; + } + + async _preinitialize() { + logInfo(fig.textSync(title, "Cyberlarge")); + INFO("INIT", "Initializing..."); + this.app = express(); + this.pg = pg; + this.server = http.createServer(this.app); + this.sockets = injectSockets(this.server, this.jobs); + this.routes = buildRoutes(this.pg, this.sockets); + this.rabbiteer = buildRabbiteer(this.pg, this.sockets); + this.app.use(this.routes); + } + + async _connect() { + await this.pg.connect(); + if (!rabbiteerEnabled) return; + await this.rabbiteer.connect(); + } + + start() { + const qt = this; + return new Promise(async function init(res) { + qt._preinitialize(); + await qt._connect(); + qt.server.listen(qt.port, function onStart() { + OK("SERVER", `Running on ${qt.port}`); + res(); + }); + }); + } +} diff --git a/libold/server/core/client-listeners.js b/libold/server/core/client-listeners.js new file mode 100644 index 0000000..462fde6 --- /dev/null +++ b/libold/server/core/client-listeners.js @@ -0,0 +1,34 @@ +import evt from "../../common/sockets/events.js"; + +export const initiator = async (socket, jobs) => { + const jobStr = socket.handshake.query.job; + const jobReq = JSON.parse(jobStr); + console.log(jobReq); + if (!jobReq || !(jobReq instanceof Object)) + throw Error("No 'job' was included in the handshake query"); + + const job = await jobs.newJob(jobReq, socket.id); + socket.join(job.id); + socket.emit(evt.JOB_CRT, job.id); +}; + +export const executor = (io, socket, jobs) => { + const jobId = socket.handshake.query.jobId; + if (!jobId) throw Error("No 'jobId' was included in the handshake query"); + + socket.join(jobId); + socket.on(evt.JOB_REP, function onReport(log) { + jobs.pushLog(jobId, log); + io.to(jobId).emit(evt.JOB_LOG, log); + }); + socket.on(evt.JOB_CLS, function onClose(code) { + jobs.closeJob(jobId, code); + io.to(jobId).emit(evt.JOB_CLS, code); + }); +}; + +export const viewer = (socket) => { + const jobId = socket.handshake.query.jobId; + if (!jobId) throw Error("No 'jobId' was included in the handshake query"); + socket.join(jobId); +}; diff --git a/libold/server/core/crons.js b/libold/server/core/crons.js new file mode 100644 index 0000000..8c6cf3a --- /dev/null +++ b/libold/server/core/crons.js @@ -0,0 +1,13 @@ +import cron from "cron"; +const { CronJob } = cron; + +// Remove Expired Silenced Tests +const expiredSilenced = () => { + console.log("Would Update Silenced Tests"); +}; + +const silencedCron = new CronJob("* * * * * *", expiredSilenced); + +export default async function startCrons() { + silencedCron.start(); +} diff --git a/libold/server/core/socket-server.js b/libold/server/core/socket-server.js new file mode 100644 index 0000000..8e712f0 --- /dev/null +++ b/libold/server/core/socket-server.js @@ -0,0 +1,54 @@ +import { Server as Skio } from "socket.io"; +import evt from "../../common/sockets/events.js"; +import modes from "../../common/sockets/modes.js"; + +import { initiator, executor, viewer } from "./client-listeners.js"; + +const socketDrop = (io, room, id) => { + const { rooms } = io.of("/").adapter; + const clients = rooms.get(room); + if (clients.size > 1 || clients.size === 0) return; + const socketId = Array.from(clients)[0]; + const s = io.sockets.sockets.get(socketId); + s.disconnect(); +}; + +const socketConnect = async (io, socket, jobs) => { + const { mode } = socket.handshake.query; + try { + switch (mode) { + case modes.INIT: + await initiator(socket, jobs); + break; + case modes.EXEC: + executor(io, socket, jobs); + break; + case modes.VIEW: + viewer(socket); + break; + default: + socket.send(evt.ERR, "Invalid Mode!"); + socket.disconnect(); + break; + } + } catch (err) { + console.log(err); + socket.send(evt.ERR, err); + socket.disconnect(); + } +}; + +const socketAuth = (socket, next) => { + const { token } = socket.handshake.auth; + // next(new Error("Bad Token")); + next(); +}; + +const applySockets = (server, jobs, options) => { + const io = new Skio(server); + io.on("connection", (socket) => socketConnect(io, socket, jobs)); + io.of("/").adapter.on("leave-room", (room, id) => socketDrop(io, room, id)); + return io; +}; + +export default applySockets; diff --git a/libold/server/database/delays.js b/libold/server/database/delays.js new file mode 100644 index 0000000..e25cdd3 --- /dev/null +++ b/libold/server/database/delays.js @@ -0,0 +1,23 @@ +const seconds = 1000; +const minutes = 60 * seconds; +const hours = 60 * minutes; +export const DELAYS = { + "1sec": 1 * seconds, + "5sec": 5 * seconds, + "10sec": 10 * seconds, + "30sec": 30 * seconds, + "1min": 1 * minutes, + "5min": 5 * minutes, + "10min": 10 * minutes, + "15min": 15 * minutes, + "30min": 30 * minutes, + "1hour": 1 * hours, + "2hour": 2 * hours, + "3hour": 3 * hours, + "4hour": 4 * hours, +}; + +export default function getDelay(delayStr) { + if (DELAYS[delayStr]) return DELAYS[delayStr]; + return 0; +} diff --git a/libold/server/database/migrations/1_create_catalog_table.sql b/libold/server/database/migrations/1_create_catalog_table.sql new file mode 100644 index 0000000..8a561c8 --- /dev/null +++ b/libold/server/database/migrations/1_create_catalog_table.sql @@ -0,0 +1,23 @@ +CREATE SEQUENCE catalog_id_seq; +CREATE TABLE catalog ( + id bigint NOT NULL DEFAULT nextval('catalog_id_seq') PRIMARY KEY, + name varchar(255) DEFAULT NULL, + class varchar(255) DEFAULT NULL, + image varchar(255) DEFAULT NULL, + "path" varchar(255) DEFAULT NULL, + description varchar(1023) DEFAULT NULL, + type varchar(31) DEFAULT NULL, + created TIMESTAMP NOT NULL DEFAULT now(), + mr varchar(255) DEFAULT NULL, + tags varchar(255)[] DEFAULT NULL, + crons varchar(127)[] DEFAULT NULL, + env varchar(31)[] DEFAULT NULL, + regions varchar(15)[] DEFAULT NULL, + triggers varchar(255)[] DEFAULT NULL, + pipeline BOOLEAN DEFAULT FALSE, + coverage varchar(255)[] DEFAULT NULL, + projects varchar(255)[] DEFAULT NULL, + delay varchar(31) DEFAULT NULL, + CONSTRAINT unique_name UNIQUE(name) +); +ALTER SEQUENCE catalog_id_seq OWNED BY catalog.id; diff --git a/libold/server/database/migrations/2_create_results_table.sql b/libold/server/database/migrations/2_create_results_table.sql new file mode 100644 index 0000000..089f10e --- /dev/null +++ b/libold/server/database/migrations/2_create_results_table.sql @@ -0,0 +1,15 @@ +CREATE SEQUENCE results_id_seq; +CREATE TABLE results ( + id bigint NOT NULL DEFAULT nextval('results_id_seq') PRIMARY KEY, + name varchar(255) DEFAULT NULL, + class varchar(255) DEFAULT NULL, + "method" varchar(255) DEFAULT NULL, + env varchar(31) DEFAULT NULL, + "timestamp" TIMESTAMP NOT NULL DEFAULT now(), + triage BOOLEAN DEFAULT FALSE, + failed BOOLEAN DEFAULT FALSE, + message varchar(2047) DEFAULT NULL, + screenshot varchar(255) DEFAULT NULL, + console varchar(255) DEFAULT NULL +); +ALTER SEQUENCE results_id_seq OWNED BY results.id; diff --git a/libold/server/database/migrations/3_create_alerting_table.sql b/libold/server/database/migrations/3_create_alerting_table.sql new file mode 100644 index 0000000..563a0aa --- /dev/null +++ b/libold/server/database/migrations/3_create_alerting_table.sql @@ -0,0 +1,9 @@ +CREATE SEQUENCE alerting_id_seq; +CREATE TABLE alerting ( + id bigint NOT NULL DEFAULT nextval('alerting_id_seq') PRIMARY KEY, + name varchar(255) DEFAULT NULL, + class varchar(255) DEFAULT NULL, + "method" varchar(255) DEFAULT NULL, + expires TIMESTAMP NOT NULL DEFAULT now() +); +ALTER SEQUENCE alerting_id_seq OWNED BY alerting.id; diff --git a/libold/server/database/mocks/alerting-mock.js b/libold/server/database/mocks/alerting-mock.js new file mode 100644 index 0000000..6726f8d --- /dev/null +++ b/libold/server/database/mocks/alerting-mock.js @@ -0,0 +1,11 @@ +export const silencedMock = () => { + return [ + { + name: `failing`, + class: `failing.js`, + method: "FAKEMETHOD", + id: 0, + silencedUntil: new Date().toJSON(), + }, + ]; +}; diff --git a/libold/server/database/mocks/catalog-mock.js b/libold/server/database/mocks/catalog-mock.js new file mode 100644 index 0000000..4368946 --- /dev/null +++ b/libold/server/database/mocks/catalog-mock.js @@ -0,0 +1,153 @@ +export const testsMock = () => { + return [ + { + id: 0, + name: "single", + class: "single.js", + image: "node:latest", + isPipeline: false, + type: "api", + description: "This is a single test", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_core", "skip_alt"], + path: "tests/assets/suite/single.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 1, + name: "failing", + class: "failing.js", + image: "node:latest", + isPipeline: false, + type: "ui", + description: "This is a failing test", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_core"], + path: "tests/assets/suite/failing.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 2, + name: "primary", + class: "primary.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a primary test", + tags: [ + "cron_1hour", + "reg_us", + "proj_core", + "skip_alt", + "pipeline_secondary1", + ], + path: "tests/assets/suite/primary.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 3, + name: "secondary1", + class: "secondary1.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a secondary test", + tags: [ + "cron_1hour", + "reg_us", + "proj_core", + "compound_tertiary1", + "compound_tertiary2", + ], + path: "tests/assets/suite/secondary1.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 4, + name: "secondary2", + class: "secondary2.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a secondary2 test", + tags: ["cron_1hour", "reg_us", "proj_core", "compound_tertiary3"], + path: "tests/assets/suite/secondary2.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 5, + name: "tertiary1", + class: "tertiary1.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a third test", + tags: ["cron_1hour", "reg_us", "proj_core"], + path: "tests/assets/suite/tertiary1.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 6, + name: "tertiary2", + class: "tertiary2.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a third2 test", + tags: ["cron_1hour", "reg_us", "proj_core"], + path: "tests/assets/suite/tertiary2.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 5, + name: "tertiary3", + class: "tertiary3.js", + image: "node:latest", + isPipeline: true, + type: "api", + description: "This is a third3 test", + tags: ["cron_1hour", "reg_us", "proj_core"], + path: "tests/assets/suite/tertiary3.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + { + id: 6, + name: "single-alt", + class: "single-alt.js", + image: "node:latest", + isPipeline: false, + type: "ui", + description: "This is an alternative test", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_alt"], + path: "tests/assets/suite/single-alt.js", + created: Date.now(), + mergeRequest: "https://example.com", + }, + ]; +}; + +export const mappingsMock = () => { + return [ + [ + { name: "primary", delay: 0 }, + { name: "secondary1", delay: 1000 }, + { name: "tertiary1", delay: 0 }, + ], + [ + { name: "primary", delay: 0 }, + { name: "secondary1", delay: 1000 }, + { name: "tertiary2", delay: 8000 }, + ], + [ + { name: "primary", delay: 0 }, + { name: "secondary2", delay: 0 }, + { name: "tertiary3", delay: 3000 }, + ], + ]; +}; diff --git a/libold/server/database/mocks/results-mock.js b/libold/server/database/mocks/results-mock.js new file mode 100644 index 0000000..e51bdb1 --- /dev/null +++ b/libold/server/database/mocks/results-mock.js @@ -0,0 +1,30 @@ +export const failingMock = () => { + return [ + { + name: "failing", + class: "failing.js", + timestamp: new Date().toJSON(), + method: "FAKEMETHOD", + cron: "1hour", + type: "api", + dailyFails: 12, + screenshot: "https://picsum.photos/1920/1080", + recentResults: [1, 0, 0, 1, 0], + isPipeline: false, + failedMessage: `Some Test FailureMessage`, + }, + { + name: "secondary1", + class: "secondary1.js", + timestamp: new Date().toJSON(), + method: "FAKEMETHOD", + cron: "1hour", + type: "api", + dailyFails: 1, + screenshot: "https://picsum.photos/1920/1080", + recentResults: [1, 0, 0, 1, 0], + isPipeline: true, + failedMessage: `Some Test FailureMessage from Secondary1`, + }, + ]; +}; diff --git a/libold/server/database/pg-query.js b/libold/server/database/pg-query.js new file mode 100644 index 0000000..ce21af6 --- /dev/null +++ b/libold/server/database/pg-query.js @@ -0,0 +1,121 @@ +const buildPostgresEntry = (entry) => { + const pgEntry = { ...entry }; + Object.keys(pgEntry).forEach((col) => { + if (pgEntry[col] === undefined) delete pgEntry[col]; + }); + return pgEntry; +}; + +export const buildPostgresValue = (jsVar) => { + if (jsVar === null) return "null"; + if (typeof jsVar === "string") return buildPostgresString(jsVar); + if (Array.isArray(jsVar) && jsVar.length === 0) return "null"; + if (Array.isArray(jsVar) && isTypeArray(jsVar, "string")) + return buildPostgresStringArray(jsVar); + return jsVar; +}; + +const buildPostgresStringArray = (jsonArray) => { + if (jsonArray.length === 0) return null; + var pgArray = [...jsonArray]; + var arrayString = "ARRAY ["; + pgArray.forEach((e, i) => (pgArray[i] = `'${e}'`)); + arrayString += pgArray.join(","); + arrayString += "]"; + return arrayString; +}; + +const isTypeArray = (jsonArray, type) => + jsonArray.every((e) => typeof e === type); + +const buildPostgresString = (jsonString) => + (jsonString && `'${jsonString.replaceAll("'", "''")}'`) || null; + +export const insertQuery = (table, jsEntry) => { + if (typeof jsEntry !== "object") throw Error("PG Inserts must be objects!"); + const entry = buildPostgresEntry(jsEntry); + const cols = Object.keys(entry); + cols.forEach((col, i) => { + entry[col] = buildPostgresValue(entry[col]); + cols[i] = `"${col}"`; + }); + var query = `INSERT INTO ${table}(${cols.join(",")})\n`; + query += `VALUES(${Object.values(entry).join(",")})`; + return query; +}; + +export const deleteQuery = (table, jsEntry) => { + if (typeof jsEntry !== "object") + throw Error("PG Delete conditionals must be an object!"); + const entry = buildPostgresEntry(jsEntry); + const cols = Object.keys(entry); + const conditionals = []; + for (var col of cols) { + entry[col] = buildPostgresValue(entry[col]); + if (entry[col] === "null") conditionals.push(`x.${col} IS NULL`); + else conditionals.push(`x.${col}=${entry[col]}`); + } + return `DELETE FROM ${table} x WHERE ${conditionals.join(" AND ")}`; +}; +export const onConflictUpdate = (conflicts, updates) => { + if (!Array.isArray(conflicts)) throw Error("PG Conflicts must be an array!"); + if (typeof updates !== "object") throw Error("PG Updates must be objects!"); + const entry = buildPostgresEntry(updates); + var query = `ON CONFLICT (${conflicts.join(",")}) DO UPDATE SET\n`; + const cols = Object.keys(entry); + for (var col of cols) { + entry[col] = buildPostgresValue(entry[col]); + } + query += cols.map((c) => `${c}=${entry[c]}`).join(","); + return query; +}; +export const clearTableQuery = (table) => { + return `TRUNCATE ${table}`; +}; + +export const selectWhereQuery = (table, jsEntry, joinWith) => { + if (typeof jsEntry !== "object") throw Error("PG Where must be an object!"); + const where = buildPostgresEntry(jsEntry); + const cols = Object.keys(where); + var query = `SELECT * FROM ${table} AS x WHERE\n`; + for (var col of cols) { + where[col] = buildPostgresValue(where[col]); + } + return (query += cols.map((c) => `x.${c}=${where[c]}`).join(joinWith)); +}; +export const updateWhereQuery = (table, updates, wheres, joinWith) => { + if (typeof updates !== "object") throw Error("PG Updates must be an object!"); + if (typeof wheres !== "object") throw Error("PG Wheres must be an object!"); + const update = buildPostgresEntry(updates); + const where = buildPostgresEntry(wheres); + const updateCols = Object.keys(update); + const whereCols = Object.keys(where); + var query = `UPDATE ${table}\n`; + var updateQuery = updateCols + .map((c) => `${c} = ${buildPostgresValue(update[c])}`) + .join(","); + var whereQuery = whereCols + .map((c) => `${c} = ${buildPostgresValue(where[c])}`) + .join(joinWith); + return (query += `SET ${updateQuery} WHERE ${whereQuery}`); +}; +export const updateWhereAnyQuery = (table, updates, wheres) => + updateWhereQuery(table, updates, wheres, " OR "); +export const updateWhereAllQuery = (table, updates, wheres) => + updateWhereQuery(table, updates, wheres, " AND "); +export const selectWhereAnyQuery = (table, where) => + selectWhereQuery(table, where, " OR "); +export const selectWhereAllQuery = (table, where) => + selectWhereQuery(table, where, " AND "); + +export default { + selectWhereAnyQuery, + selectWhereAllQuery, + updateWhereAnyQuery, + updateWhereAllQuery, + insertQuery, + deleteQuery, + buildPostgresValue, + onConflictUpdate, + clearTableQuery, +}; diff --git a/libold/server/database/postgres.js b/libold/server/database/postgres.js new file mode 100644 index 0000000..58ff79a --- /dev/null +++ b/libold/server/database/postgres.js @@ -0,0 +1,62 @@ +// Imports +import path from "node:path"; +import { URL } from "node:url"; +import { migrate } from "postgres-migrations"; +import createPgp from "pg-promise"; +import moment from "moment"; +import { INFO, WARN, OK, VERB } from "../util/logging.js"; +// Environment Variables +const { + QUALITEER_POSTGRES_DATABASE: database, + QUALITEER_POSTGRES_ENABLED: pgEnabled, + QUALITEER_POSTGRES_HOST: host, + QUALITEER_POSTGRES_PASSWORD: password, + QUALITEER_POSTGRES_PORT: port, + QUALITEER_POSTGRES_USER: user, +} = process.env; + +// Postgres-promise Configuration +// Ensure dates get saved as UTC date strings +// This prevents the parser from doing strange datetime operations +const pgp = createPgp(); +pgp.pg.types.setTypeParser(1114, (str) => moment.utc(str).format()); + +// Database Config +const dbConfig = { + database: database ?? "qualiteer", + user: user ?? "postgres", + password: password ?? "postgres", + host: host ?? "localhost", + port: port ?? 5432, + ensureDatabaseExists: true, +}; + +const databaseDir = new URL(".", import.meta.url).pathname; +const migrationsDir = path.resolve(databaseDir, "migrations/"); + +const queryMock = (str) => INFO("POSTGRES MOCK", str); + +const connect = (pg) => async () => { + if (pgEnabled === "false") { + WARN("POSTGRES", "Postgres Disabled!"); + return { query: queryMock }; + } + VERB("POSTGRES", "Migrating..."); + await migrate(dbConfig, migrationsDir); + // Override fake methods + const pgInstance = pgp(dbConfig); + for (var k in pgInstance) pg[k] = pgInstance[k]; + VERB("POSTGRES", "Migrated Successfully"); + await pg.connect(); + VERB("POSTGRES", "Postgres connected Successfully!"); + + OK("POSTGRES", `Connected to database ${dbConfig.database}!`); +}; + +const buildPostgres = () => { + var pg = { query: queryMock }; + pg.connect = connect(pg); + return pg; +}; + +export default buildPostgres(); diff --git a/libold/server/database/queries/alerting.js b/libold/server/database/queries/alerting.js new file mode 100644 index 0000000..becf27a --- /dev/null +++ b/libold/server/database/queries/alerting.js @@ -0,0 +1,56 @@ +import pg from "../postgres.js"; +import { silencedMock } from "../mocks/alerting-mock.js"; +import moment from "moment"; +// Imports +import { + insertQuery, + selectWhereAnyQuery, + updateWhereAnyQuery, + deleteQuery, +} from "../pg-query.js"; +// Constants +const table = "alerting"; +const PG_DISABLED = process.env.POSTGRES_DISABLED; + +export const upsertAlertSilence = async (silence) => { + const { + id, + name, + class: className, + method, + expires: duration, + keepExpires, + } = silence; + const { h, m } = duration; + const expires = moment().add(h, "hours").add(m, "minutes").utc().format(); + const entry = { + name, + class: className, + method, + expires: keepExpires ? undefined : expires, + }; + const asUpdate = {}; + for (var k of Object.keys(entry)) + asUpdate[k] = entry[k] === "*" ? null : entry[k]; + var query = id + ? updateWhereAnyQuery(table, asUpdate, { id }) + : insertQuery(table, entry); + return pg.query(query); +}; + +export const deleteAlertSilence = async (silence) => { + const { id } = silence; + const query = deleteQuery(table, { id }); + return pg.query(query); +}; + +// Queries +export const getSilencedTests = async () => { + if (PG_DISABLED) return silencedMock(); + const query = `SELECT * from ${table}`; + const silenced = await pg.query(query); + silenced.forEach((t, i) => { + for (var k of Object.keys(t)) silenced[i][k] = t[k] === null ? "*" : t[k]; + }); + return silenced; +}; diff --git a/libold/server/database/queries/catalog.js b/libold/server/database/queries/catalog.js new file mode 100644 index 0000000..f545188 --- /dev/null +++ b/libold/server/database/queries/catalog.js @@ -0,0 +1,121 @@ +import pg from "../postgres.js"; +// Imports +import { + insertQuery, + selectWhereAnyQuery, + onConflictUpdate, +} from "../pg-query.js"; +import { WARN } from "../../util/logging.js"; + +import getFilteredTags from "../tags.js"; +import getDelay from "../delays.js"; +// Constants +const table = "catalog"; +const PG_DISABLED = process.env.POSTGRES_DISABLED; +import { testsMock, mappingsMock } from "../mocks/catalog-mock.js"; +// Queries + +export const removeDroppedTests = async (testNames) => { + // BUG: After dropping a test, the id jumps ridiculously high + const pgNames = testNames.map((tn) => `'${tn}'`).join(","); + const query = `DELETE FROM catalog as x where x.name not in (${pgNames});`; + return pg.query(query); +}; + +export const getTest = async (name) => { + const query = selectWhereAnyQuery(table, { name }); + const results = await pg.query(query); + if (results.length > 1) + WARN("CATALOG", `More than 1 test found for '${name}'`); + return results[0]; +}; + +export const getTests = async () => { + if (PG_DISABLED) return testsMock(); + const query = `SELECT * from ${table}`; + return pg.query(query); +}; + +export const getPipelineMappings = async () => { + if (PG_DISABLED) return mappingsMock(); + const query = `SELECT * from ${table} WHERE pipeline`; + const tests = await pg.query(query); + const mappings = []; + var newTrigger; + for (var test of tests) { + if (test.triggers) continue; + const { name, delay: delayStr } = test; + var triggerStack = [{ name, delay: getDelay(delayStr), delayStr }]; + newTrigger = { name, delayStr }; + while ( + (newTrigger = tests.find( + (te) => te.triggers && te.triggers.includes(newTrigger.name) + )) !== null + ) { + if (!newTrigger) break; + triggerStack.push({ + name: newTrigger.name, + delay: getDelay(newTrigger.delay), + delayStr: newTrigger.delay, + }); + } + mappings.push(triggerStack.reverse()); + } + return mappings; +}; + +export const getProjects = async () => { + if (PG_DISABLED) { + const tests = testsMock(); + } +}; + +export const truncateTests = async () => { + if (PG_DISABLED) return console.log(`Would truncate table ${table}`); + const query = `TRUNCATE ${table} RESTART IDENTITY CASCADE;`; + return await pg.query(query); +}; + +export const upsertTest = async (test) => { + if (PG_DISABLED) return console.log("Would insert test", test); + const { + name, + class: className, + image, + path, + description, + type, + created, + mergeRequest, + tags, + } = test; + + const filteredTags = getFilteredTags(tags); + + const env = + filteredTags.ignore && filteredTags.env + ? filteredTags.env.filter((e) => !filteredTags.ignore.includes(e)) + : filteredTags.env; + const catalogEntry = { + name, + class: className, + image, + path, + description: description ? description : null, + type, + created, + mr: mergeRequest, + tags, + crons: filteredTags.crons, + env, + regions: filteredTags.regions, + triggers: filteredTags.triggers, + pipeline: filteredTags.pipeline ? true : false, + coverage: filteredTags.coverage, + projects: filteredTags.projects, + delay: filteredTags.delay ? filteredTags.delay[0] : null, + }; + const query = + insertQuery(table, catalogEntry) + onConflictUpdate(["name"], catalogEntry); + return await pg.query(query); +}; diff --git a/libold/server/database/queries/results.js b/libold/server/database/queries/results.js new file mode 100644 index 0000000..ce23ec5 --- /dev/null +++ b/libold/server/database/queries/results.js @@ -0,0 +1,93 @@ +import pg from "../postgres.js"; +import { failingMock } from "../mocks/results-mock.js"; +// Imports +import { + insertQuery, + selectWhereAnyQuery, + selectWhereAllQuery, + updateWhereAnyQuery, +} from "../pg-query.js"; +// Constants +const table = "results"; +const recentResultsMax = 5; +const PG_DISABLED = process.env.POSTGRES_DISABLED; + +// Queries +export const insertTestResult = (testResult) => { + const { + name, + class: className, + method, + env, + timestamp, + triage, + failed, + message, + screenshot, + console: cs, + } = testResult; + + var query = insertQuery(table, { + name, + class: className, + method, + env, + timestamp, + triage, + failed, + message, + screenshot, + console: cs, + }); + + query += "\n RETURNING *"; + return pg.query(query); +}; + +export const getCurrentlyFailing = async () => { + if (PG_DISABLED) return failingMock(); + /* This can probably be changed into a super query, but perhaps faster/smaller */ + const recent = `SELECT * FROM ${table} WHERE (timestamp BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()) AND NOT(failed AND triage)`; + const slimCatalog = `SELECT name, crons, class, type, pipeline, env AS enabled_env FROM catalog`; + const failing = `SELECT * FROM recent INNER JOIN slim_catalog USING(name) WHERE timestamp = (SELECT MAX(timestamp) FROM recent r2 WHERE recent.name = r2.name) AND failed`; + const applicableFailing = `SELECT name, count(*) as fails FROM recent WHERE recent.name IN (SELECT name FROM failing) GROUP BY name`; + /*const runHistory = `SELECT name, timestamp, failed FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY name ORDER BY timestamp) as n + FROM ${table} WHERE name IN (SELECT name FROM failing)) as ord WHERE n <= ${recentResultsMax} ORDER BY name DESC`;*/ + const runHistory = `SELECT name, timestamp, failed FROM results WHERE NOT triage AND name IN (SELECT name FROM failing) ORDER BY timestamp DESC LIMIT ${recentResultsMax}`; + // const recentQuery = pg.query(recent); + const failingQuery = pg.query( + `WITH recent as (${recent}), slim_catalog as (${slimCatalog}) ${failing}` + ); + const applicableQuery = pg.query( + `WITH recent as (${recent}), slim_catalog as (${slimCatalog}), failing as (${failing}) ${applicableFailing}` + ); + const historyQuery = pg.query( + `WITH recent as (${recent}), slim_catalog as (${slimCatalog}), failing as (${failing}) ${runHistory}` + ); + + const [currentlyFailing, applicableFails, failHistory] = await Promise.all([ + failingQuery, + applicableQuery, + historyQuery, + ]); + for (var i = 0; i < currentlyFailing.length; i++) { + currentlyFailing[i].dailyFails = parseInt( + applicableFails.find((af) => af.name === currentlyFailing[i].name).fails + ); + currentlyFailing[i].recentResults = []; + currentlyFailing[i].enabledEnv = currentlyFailing[i].enabled_env; + currentlyFailing[i].isPipeline = currentlyFailing[i].pipeline; + delete currentlyFailing[i].enabled_env; + delete currentlyFailing[i].pipeline; + for (var fh of failHistory) { + if (fh.name !== currentlyFailing[i].name) continue; + currentlyFailing[i].recentResults.push(fh); + } + } + return currentlyFailing; +}; + +export const ignoreResult = async ({ id }) => { + const query = updateWhereAnyQuery(table, { failed: false }, { id }); + return pg.query(query); +}; diff --git a/libold/server/database/seeds/alerting-seed.js b/libold/server/database/seeds/alerting-seed.js new file mode 100644 index 0000000..92a6380 --- /dev/null +++ b/libold/server/database/seeds/alerting-seed.js @@ -0,0 +1,11 @@ +export const table = "alerting"; +export const seed = () => { + return [ + { + name: `failing`, + class: `failing.js`, + method: "FAKEMETHOD", + expires: new Date().toJSON(), + }, + ]; +}; diff --git a/libold/server/database/seeds/catalog-seed.js b/libold/server/database/seeds/catalog-seed.js new file mode 100644 index 0000000..3a849f5 --- /dev/null +++ b/libold/server/database/seeds/catalog-seed.js @@ -0,0 +1,126 @@ +export const table = "catalog"; +export const seed = () => { + return [ + { + name: "single", + class: "single.js", + image: "node:latest", + path: "tests/assets/suite/single.js", + description: "This is a single test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_core", "ignore_alt"], + }, + { + name: "failing", + class: "failing.js", + image: "node:latest", + path: "tests/assets/suite/failing.js", + description: "This is a failing test", + type: "ui", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_core"], + }, + { + name: "primary", + class: "primary.js", + image: "node:latest", + path: "tests/assets/suite/primary.js", + description: "This is a primary test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: [ + "pipeline", + "cron_1hour", + "reg_us", + "proj_core", + "ignore_alt", + "triggers_secondary1", + "triggers_secondary2", + ], + }, + { + name: "secondary1", + class: "secondary1.js", + image: "node:latest", + path: "tests/assets/suite/secondary1.js", + description: "This is a secondary test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: [ + "pipeline", + "cron_1hour", + "reg_us", + "proj_core", + "triggers_tertiary1", + "triggers_tertiary2", + "delay_1sec", + ], + }, + { + name: "secondary2", + class: "secondary2.js", + image: "node:latest", + path: "tests/assets/suite/secondary2.js", + description: "This is a secondary2 test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: [ + "pipeline", + "cron_1hour", + "reg_us", + "proj_core", + "triggers_tertiary3", + ], + }, + { + name: "tertiary1", + class: "tertiary1.js", + image: "node:latest", + path: "tests/assets/suite/tertiary1.js", + description: "This is a third test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["pipeline", "cron_1hour", "reg_us", "proj_core"], + }, + { + name: "tertiary2", + class: "tertiary2.js", + image: "node:latest", + path: "tests/assets/suite/tertiary2.js", + description: "This is a third2 test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["pipeline", "cron_1hour", "reg_us", "proj_core", "delay_10sec"], + }, + { + name: "tertiary3", + class: "tertiary3.js", + image: "node:latest", + path: "tests/assets/suite/tertiary3.js", + description: "This is a third3 test", + type: "api", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["pipeline", "cron_1hour", "reg_us", "proj_core", "delay_5sec"], + }, + { + name: "single-alt", + class: "single-alt.js", + image: "node:latest", + path: "tests/assets/suite/single-alt.js", + description: "This is an alternative test", + type: "ui", + created: new Date().toJSON(), + mergeRequest: "https://example.com", + tags: ["cron_1hour", "reg_us", "env_ci", "proj_alt"], + }, + ]; +}; diff --git a/libold/server/database/seeds/results-seed.js b/libold/server/database/seeds/results-seed.js new file mode 100644 index 0000000..1ea0612 --- /dev/null +++ b/libold/server/database/seeds/results-seed.js @@ -0,0 +1,29 @@ +export const table = "results"; +export const seed = () => { + return [ + { + name: "failing", + class: "failing.js", + method: "FAKEMETHOD", + env: "prod", + timestamp: new Date().toJSON(), + triage: false, + failed: true, + message: "Some Test FailureMessage", + screenshot: "https://picsum.photos/1920/1080", + console: "https://example.com", + }, + { + name: "secondary1", + class: "secondary1.js", + method: "FAKEMETHOD", + env: "prod", + timestamp: new Date().toJSON(), + triage: false, + failed: true, + message: "Some Test FailureMessage from Secondary1", + screenshot: "https://picsum.photos/1920/1080", + console: "https://example.com", + }, + ]; +}; diff --git a/libold/server/database/tags.js b/libold/server/database/tags.js new file mode 100644 index 0000000..e28ab64 --- /dev/null +++ b/libold/server/database/tags.js @@ -0,0 +1,26 @@ +import { WARN } from "../util/logging.js"; +export const TAGS = { + IGNORE: { name: "ignore", tag: "ignore_", value: (t) => t }, + CRON: { name: "crons", tag: "cron_", value: (t) => t }, + ENV: { name: "env", tag: "env_", value: (t) => t }, + REGIONS: { name: "regions", tag: "reg_", value: (t) => t }, + PIPELINE: { name: "pipeline", tag: "is_pipeline", value: (t) => t }, + COVERAGE: { name: "coverage", tag: "coverage_", value: (t) => t }, + PROJECT: { name: "projects", tag: "proj_", value: (t) => t }, + DELAY: { name: "delay", tag: "delay_", value: (t) => t }, + TRIGGERS: { name: "triggers", tag: "triggers_", value: (t) => t }, +}; + +export default function getFilteredTags(tags) { + const filtered = {}; + for (var t of tags) { + const tag = Object.values(TAGS).find((ta) => t.startsWith(ta.tag)); + if (!tag) { + WARN("CATALOG", `Tag '${t}' did not have a valid prefix!`); + continue; + } + if (!filtered[tag.name]) filtered[tag.name] = []; + filtered[tag.name].push(tag.value(t.replace(tag.tag, ""))); + } + return filtered; +} diff --git a/libold/server/k8s/k8s-common.js b/libold/server/k8s/k8s-common.js new file mode 100644 index 0000000..a5f7664 --- /dev/null +++ b/libold/server/k8s/k8s-common.js @@ -0,0 +1,58 @@ +import fs from "node:fs"; +import { URL } from "node:url"; +import path from "node:path"; +const { + QUALITEER_EXECUTOR_URL, + QUALITEER_EXECUTOR_USE_SCRIPT, + QUALITEER_EXECUTOR_BIN, + QUALITEER_EXECUTOR_BIN_URL, +} = process.env; + +const executorUrl = QUALITEER_EXECUTOR_URL; +const executorAsScript = QUALITEER_EXECUTOR_USE_SCRIPT === "true"; +const executorBin = QUALITEER_EXECUTOR_BIN ?? `qltr-executor`; +const executorBinFetchUrl = QUALITEER_EXECUTOR_BIN_URL; + +const jobsDir = "jobs/"; +const jobsPath = path.resolve(jobsDir); +const k8sFolder = new URL(".", import.meta.url).pathname; +const defaultsFilePath = path.resolve(k8sFolder, "k8s-job.json"); +const defaults = JSON.parse(fs.readFileSync(defaultsFilePath)); + +function commandBuilder(jobId, jobRequest) { + const executorPayload = JSON.stringify({ + jobId, + jobRequest, + url: executorUrl, + }); + const payload = Buffer.from(executorPayload, "utf8").toString("base64"); + return [`./${executorBin}`, payload]; +} + +export function jobBuilder(jobRequest) { + const { resources, name, image, id: jobId } = jobRequest; + // Safety Checks + if (!jobId) throw Error("'jobId' required!"); + if (!image) throw Error("'image' required!"); + + // Apply configuration + const job = { ...defaults }; + job.metadata.name = `qltr-${jobId}`; + const container = job.spec.template.spec.containers[0]; + container.name = job.metadata.name; + container.command = commandBuilder(jobId, jobRequest); + container.image = JSON.stringify(image); + // Apply resources + job.resources = { ...job.resources, ...resources }; + return job; +} + +export const createFile = (job) => { + const { name } = job.metadata; + if (!fs.existsSync(jobsPath)) fs.mkdirSync(jobsPath); + const filePath = path.resolve(jobsDir, `${name}.json`); + fs.writeFileSync(filePath, JSON.stringify(job)); + return filePath; +}; + +export const deleteFile = (filePath) => fs.unlinkSync(filePath); diff --git a/libold/server/k8s/k8s-internal-engine.js b/libold/server/k8s/k8s-internal-engine.js new file mode 100644 index 0000000..8496f96 --- /dev/null +++ b/libold/server/k8s/k8s-internal-engine.js @@ -0,0 +1,14 @@ +import { INFO, ERR, OK, VERB } from "../../util/logging.js"; +import cp from "node:child_process"; + +const jobStr = process.argv.slice(2)[0]; +const job = JSON.parse(jobStr); +const { command } = job.spec.template.spec.containers[0]; +INFO("EXEC", "Internal Executor Starting!"); +cp.exec(command, (error, stdout, stderr) => { + if (error) ERR("EXEC", error); + //if (stdout) VERB("EXEC-STDOUT", stdout); + //if (stderr) VERB("EXEC-STDERR", stderr); + OK("EXEC", "Internal Executor Finished!"); + process.exit(error ? 1 : 0); +}); diff --git a/libold/server/k8s/k8s-internal.js b/libold/server/k8s/k8s-internal.js new file mode 100644 index 0000000..cd0936d --- /dev/null +++ b/libold/server/k8s/k8s-internal.js @@ -0,0 +1,20 @@ +import cp from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { jobBuilder, createFile, deleteFile } from "./k8s-common.js"; + +// Constants +const internalEngine = path.resolve("./lib/jobs/k8s/k8s-internal-engine.js"); + +// Functions +const applyFileInternally = (filePath) => { + const job = fs.readFileSync(filePath, { encoding: "utf8" }); + cp.fork(internalEngine, [job]); +}; + +export default async function createJobInternally(jobRequest) { + const job = jobBuilder(jobRequest); + const filePath = createFile(job); + applyFileInternally(filePath); + deleteFile(filePath); +} diff --git a/libold/server/k8s/k8s-job.json b/libold/server/k8s/k8s-job.json new file mode 100644 index 0000000..e42eb34 --- /dev/null +++ b/libold/server/k8s/k8s-job.json @@ -0,0 +1,34 @@ +{ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "qltr-job-test-suite-1" + }, + "spec": { + "ttlSecondsAfterFinished": 2, + "template": { + "spec": { + "containers": [ + { + "name": "qltr-job-test-suite-1", + "image": "node:latest", + "imagePullPolicy": "Always", + "command": ["node", "--version"], + "envFrom": [ + { + "configMapRef": { + "name": "qualiteer-job-environment" + } + } + ] + } + ], + "imagePullSecrets": [ + { "name": "usw-registry-secret", "namespace": "default" } + ], + "restartPolicy": "Never" + } + }, + "backoffLimit": 4 + } +} diff --git a/libold/server/k8s/k8s.js b/libold/server/k8s/k8s.js new file mode 100644 index 0000000..a273f1f --- /dev/null +++ b/libold/server/k8s/k8s.js @@ -0,0 +1,19 @@ +import k8s from "@kubernetes/client-node"; +import { INFO, ERR } from "../util/logging.js"; +import { jobBuilder, createFile, deleteFile } from "./k8s-common.js"; + +export default async function createJob(jobRequest) { + //console.log(await jobRequest.tests); + const job = jobBuilder(jobRequest); + job.spec.template.spec.containers[0].image = + "registry.dunemask.net/garden/dev/reed:latest"; + const kc = new k8s.KubeConfig(); + kc.loadFromCluster(); + const batchV1Api = kc.makeApiClient(k8s.BatchV1Api); + const batchV1beta1Api = kc.makeApiClient(k8s.BatchV1beta1Api); + const jobName = job.metadata.name; + batchV1Api + .createNamespacedJob("dunestorm-dunemask", job) + .then((res) => INFO("K8S", `Job ${jobName} created!`)) + .catch((err) => ERR("K8S", err)); +} diff --git a/libold/server/rabbit/rabbit-workers.js b/libold/server/rabbit/rabbit-workers.js new file mode 100644 index 0000000..24ace07 --- /dev/null +++ b/libold/server/rabbit/rabbit-workers.js @@ -0,0 +1,22 @@ +import Rabbiteer from "rabbiteer"; +import buildWorkers from "./workers/index.js"; + +// Pull Environment Variables +const { + QUALITEER_RABBIT_HOST: host, + QUALITEER_RABBIT_USER: user, + QUALITEER_RABBIT_PASS: pass, +} = process.env; + +// Rabbit Config +const rabbitConfig = { + protocol: "amqp:", + host: `amqp://${host}` ?? "localhost", + user: user ?? "guest", + pass: pass ?? "guest", +}; + +const buildRabbiteer = (pg, skio) => + new Rabbiteer(null, buildWorkers(skio), { autoRabbit: rabbitConfig }); + +export default buildRabbiteer; diff --git a/libold/server/rabbit/workers/KubeJobsWorker.js b/libold/server/rabbit/workers/KubeJobsWorker.js new file mode 100644 index 0000000..7743bd4 --- /dev/null +++ b/libold/server/rabbit/workers/KubeJobsWorker.js @@ -0,0 +1,19 @@ +// Imports +import { Worker } from "rabbiteer"; +// Class +export default class KubeJobsWorker extends Worker { + constructor() { + super("KubeJobs"); + } + + async configure(ch) { + await ch.assertExchange("KubeJobsExchange", "direct"); + await ch.assertQueue(this.queue, this.queueOptions); + await ch.bindQueue(this.queue, "KubeJobsExchange", "KubeJobs"); + await ch.consume(this.queue, (msg) => this.consume(msg, () => ch.ack(msg))); + } + + onMessage(string) { + console.log(`Died: ${string}`); + } +} diff --git a/libold/server/rabbit/workers/TestResultsWorker.js b/libold/server/rabbit/workers/TestResultsWorker.js new file mode 100644 index 0000000..b26805d --- /dev/null +++ b/libold/server/rabbit/workers/TestResultsWorker.js @@ -0,0 +1,45 @@ +// Imports +import { Worker } from "rabbiteer"; +import { VERB } from "../../util/logging.js"; +import { insertTestResult } from "../../database/queries/results.js"; +import evt from "../../../common/sockets/events.js"; +// Class +export default class TestResultsWorker extends Worker { + constructor(skio) { + super("TestResults"); + this.skio = skio; + } + + /* Example Test Result + { + testName: “SomeTest”, + testClass: “SomeClass”, + testMethod: “SomeMethod”, + testType: “API/UI”, + testTimestamp: 123893024, + origin: “TestSuite”, + failed: true, + failedMessage: “Some Failure”, + screenshotUrl: “https://screenshot”, + expectedScreenshotUrl: “https://expected” + consoleLogUrl: “https://consolelog” +} +*/ + async onMessage(testResult) { + const { pipeline } = testResult; + await this.handleReporting(testResult); + // Alter to start next test + // TODO the delay should be autopopulated either by the suite, or filled in by the server + if (pipeline) return this.pipelineTrigger(pipeline); + } + + pipelineTrigger(pipeline) { + const { dashboardSocketId: dsi } = pipeline; + this.skio.to(dsi).emit(evt.PPL_TRG, pipeline); + } + + handleReporting(result) { + VERB("TestResults", result.name); + insertTestResult(result); + } +} diff --git a/libold/server/rabbit/workers/index.js b/libold/server/rabbit/workers/index.js new file mode 100644 index 0000000..b461244 --- /dev/null +++ b/libold/server/rabbit/workers/index.js @@ -0,0 +1,4 @@ +import TestResultsWorker from "./TestResultsWorker.js"; + +const buildWorkers = (skio) => [new TestResultsWorker(skio)]; +export default buildWorkers; diff --git a/libold/server/routes/alerting-route.js b/libold/server/routes/alerting-route.js new file mode 100644 index 0000000..226d5dc --- /dev/null +++ b/libold/server/routes/alerting-route.js @@ -0,0 +1,41 @@ +import { Router, json as jsonMiddleware } from "express"; +import { + getSilencedTests, + upsertAlertSilence, + deleteAlertSilence, +} from "../database/queries/alerting.js"; +const router = Router(); + +// Apply Middlewares +router.use(jsonMiddleware()); + +// Get Routes +router.get("/silenced", (req, res) => { + getSilencedTests().then((t) => res.send(t)); +}); + +// Post Routes +router.post("/silence", (req, res) => { + const { name, class: className, method, expires, keepExpires } = req.body; + if (!name || !className || !method) + return res + .status(400) + .send("'name', 'class', and 'method' are all required Fields!"); + if (expires === null) + return deleteAlertSilence(req.body) + .then(() => res.sendStatus(200)) + .catch((e) => res.status(500).send(e)); + const { h, m } = keepExpires ? {} : expires; + if (!keepExpires && (h == null || m == null)) + return res.status(400).send("Both 'h' and 'm' are required fields!"); + if (!keepExpires && (h < 0 || m < 0)) + return res + .status(400) + .send("'h' and 'm' must be greater than or equal to 0!"); + // TODO set max times as well + if (!keepExpires && (h > 72 || m > 59)) + res.status(400).send("'h' and 'm' must not exceed the set maxes!"); + upsertAlertSilence(req.body).then(() => res.sendStatus(200)); +}); + +export default router; diff --git a/libold/server/routes/catalog-route.js b/libold/server/routes/catalog-route.js new file mode 100644 index 0000000..3f6147d --- /dev/null +++ b/libold/server/routes/catalog-route.js @@ -0,0 +1,44 @@ +import { Router, json as jsonMiddleware } from "express"; +import { + getTests, + getPipelineMappings, + upsertTest, + truncateTests, + removeDroppedTests, +} from "../database/queries/catalog.js"; +const router = Router(); + +const maxSize = 1024 * 1024 * 100; // 100MB + +// Apply Middlewares +router.use(jsonMiddleware({ limit: maxSize })); + +// Get Routes +router.get("/tests", (req, res) => { + getTests().then((t) => res.json(t)); +}); + +router.get("/pipeline-mappings", (req, res) => { + getPipelineMappings().then((m) => res.json(m)); +}); + +// Post Routes +router.post("/update", (req, res) => { + if (!req.body) return res.status(400).send("Body required!"); + if (!Array.isArray(req.body)) + return res.status(400).send("Body must be an array!"); + const wrongImage = req.body.find(({ image }) => image !== req.body[0].image); + if (wrongImage) + return res.status(400).send("Tests cannot have unique images!"); + const testNames = req.body.map(({ name }) => name); + + // Upsert new tests + const upserts = Promise.all( + req.body.map((catalogItem) => upsertTest(catalogItem)) + ); + const dropRm = upserts.then(() => removeDroppedTests(testNames)); + + dropRm.then(() => res.sendStatus(200)).catch((e) => res.status(500).send(e)); +}); + +export default router; diff --git a/libold/server/routes/dev-route.js b/libold/server/routes/dev-route.js new file mode 100644 index 0000000..3240588 --- /dev/null +++ b/libold/server/routes/dev-route.js @@ -0,0 +1,13 @@ +import { Router, json as jsonMiddleware } from "express"; +import TestResultsWorker from "../rabbit/workers/TestResultsWorker.js"; + +export default function buildDevRoute(pg, skio) { + const router = Router(); + router.use(jsonMiddleware()); + router.post("/rabbit/TestResults", (req, res) => { + const { testResult } = req.body; + new TestResultsWorker(skio).onMessage(testResult); + res.sendStatus(200); + }); + return router; +} diff --git a/libold/server/routes/react-route.js b/libold/server/routes/react-route.js new file mode 100644 index 0000000..9438566 --- /dev/null +++ b/libold/server/routes/react-route.js @@ -0,0 +1,8 @@ +import express, { Router } from "express"; +import path from "path"; +const router = Router(); +router.use("/", express.static(path.resolve("./build"))); +router.get("/*", (req, res) => + res.sendFile(path.resolve("./build/index.html")) +); +export default router; diff --git a/libold/server/routes/results-route.js b/libold/server/routes/results-route.js new file mode 100644 index 0000000..c2629bd --- /dev/null +++ b/libold/server/routes/results-route.js @@ -0,0 +1,27 @@ +import { Router, json as jsonMiddleware } from "express"; +import { + getCurrentlyFailing, + ignoreResult, +} from "../database/queries/results.js"; +const router = Router(); + +// Apply Middlewares +router.use(jsonMiddleware()); + +// Get Routes +router.get("/failing", (req, res) => { + getCurrentlyFailing().then((f) => res.json(f)); +}); + +// Post Routes +router.post("/history", (req, res) => { + res.send([]); +}); + +router.post("/ignore", (req, res) => { + if (!req.body || !req.body.id) + return res.status(400).send("'id' is required!"); + ignoreResult(req.body).then(() => res.sendStatus(200)); +}); + +export default router; diff --git a/libold/server/routes/router.js b/libold/server/routes/router.js new file mode 100644 index 0000000..c8a62fc --- /dev/null +++ b/libold/server/routes/router.js @@ -0,0 +1,30 @@ +// Imports +import express from "express"; + +// Routes +import vitals from "../routes/vitals-route.js"; +import results from "../routes/results-route.js"; +import alerting from "../routes/alerting-route.js"; +import react from "../routes/react-route.js"; +import catalog from "../routes/catalog-route.js"; + +import buildDevRoute from "../routes/dev-route.js"; + +export default function buildRoutes(pg, skio) { + const router = express.Router(); + // Special Routes + router.use(vitals); + router.all("/", (req, res) => res.redirect("/qualiteer")); + if (process.env.USE_DEV_ROUTER === "true") + router.use("/api/dev", buildDevRoute(pg, skio)); + + // Middlewares + + // Routes + router.use("/qualiteer", react); // Static Build Route + router.use("/api/results", results); + router.use("/api/alerting", alerting); + router.use("/api/catalog", catalog); + + return router; +} diff --git a/libold/server/routes/vitals-route.js b/libold/server/routes/vitals-route.js new file mode 100644 index 0000000..e6844bb --- /dev/null +++ b/libold/server/routes/vitals-route.js @@ -0,0 +1,7 @@ +import { Router } from "express"; +const router = Router(); + +// Get Routes +router.get("/healthz", (req, res) => res.sendStatus(200)); + +export default router; diff --git a/libold/server/util/logging.js b/libold/server/util/logging.js new file mode 100644 index 0000000..2c62b3e --- /dev/null +++ b/libold/server/util/logging.js @@ -0,0 +1,28 @@ +// Imports +import { Chalk } from "chalk"; +const { redBright, greenBright, yellowBright, cyanBright, magentaBright } = + new Chalk({ level: 2 }); + +// Logging +const logColor = (color, header, ...args) => + console.log(color(header), ...args); + +export const logError = (...args) => logColor(redBright, ...args); + +export const logConfirm = (...args) => logColor(greenBright, ...args); + +export const logWarn = (...args) => logColor(yellowBright, ...args); + +export const logInfo = (...args) => logColor(cyanBright, ...args); + +export const logVerbose = (...args) => logColor(magentaBright, ...args); + +export const ERR = (header, ...args) => logError(`[${header}]`, ...args); + +export const OK = (header, ...args) => logConfirm(`[${header}]`, ...args); + +export const WARN = (header, ...args) => logWarn(`[${header}]`, ...args); + +export const INFO = (header, ...args) => logInfo(`[${header}]`, ...args); + +export const VERB = (header, ...args) => logVerbose(`[${header}]`, ...args); diff --git a/package-lock.json b/package-lock.json index b3096f6..8fe01fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "vite": "3.1.0" }, "optionalDependencies": { - "rabbiteer": "gitlab:Dunemask/rabbiteer" + "rabbiteer": "gitlab:Dunemask/rabbiteer#d2b8b92427a79ecccfa31d07269aec6fa5e550b3" } }, "node_modules/@acuminous/bitsyntax": { @@ -1802,41 +1802,6 @@ "node": ">=8" } }, - "node_modules/bitsyntax": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", - "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", - "optional": true, - "dependencies": { - "buffer-more-ints": "~1.0.0", - "debug": "~2.6.9", - "safe-buffer": "~5.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/bitsyntax/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/bitsyntax/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "optional": true - }, - "node_modules/bitsyntax/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1871,12 +1836,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "optional": true - }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -4878,34 +4837,18 @@ ] }, "node_modules/rabbiteer": { - "version": "1.0.0", - "resolved": "git+ssh://git@gitlab.com/Dunemask/rabbiteer.git#9cd93940fdfefc0c8dca09f8d79ed6202861044e", + "version": "0.0.2", + "resolved": "git+ssh://git@gitlab.com/Dunemask/rabbiteer.git#d2b8b92427a79ecccfa31d07269aec6fa5e550b3", + "integrity": "sha512-BDNUn8FQfbQ8jJSTHh00rQaPweNqhkAaAY5m2E3FbOIiQCbBTWCJ1avR3Ho6UedNlJlZ+zTsvOZY9h6O9TPz9Q==", "license": "LGPL-2.1-only", "optional": true, "dependencies": { - "amqplib": "^0.8.0" + "amqplib": "^0.10.3" }, "peerDependencies": { "express": "^4.18.1" } }, - "node_modules/rabbiteer/node_modules/amqplib": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", - "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", - "optional": true, - "dependencies": { - "bitsyntax": "~0.1.0", - "bluebird": "^3.7.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "safe-buffer": "~5.2.1", - "url-parse": "~1.5.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7624,40 +7567,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bitsyntax": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", - "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", - "optional": true, - "requires": { - "buffer-more-ints": "~1.0.0", - "debug": "~2.6.9", - "safe-buffer": "~5.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "optional": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } - } - }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -7691,12 +7600,6 @@ } } }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "optional": true - }, "body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -9834,27 +9737,12 @@ "dev": true }, "rabbiteer": { - "version": "git+ssh://git@gitlab.com/Dunemask/rabbiteer.git#9cd93940fdfefc0c8dca09f8d79ed6202861044e", - "from": "rabbiteer@gitlab:Dunemask/rabbiteer", + "version": "git+ssh://git@gitlab.com/Dunemask/rabbiteer.git#d2b8b92427a79ecccfa31d07269aec6fa5e550b3", + "integrity": "sha512-BDNUn8FQfbQ8jJSTHh00rQaPweNqhkAaAY5m2E3FbOIiQCbBTWCJ1avR3Ho6UedNlJlZ+zTsvOZY9h6O9TPz9Q==", + "from": "rabbiteer@gitlab:Dunemask/rabbiteer#d2b8b92427a79ecccfa31d07269aec6fa5e550b3", "optional": true, "requires": { - "amqplib": "^0.8.0" - }, - "dependencies": { - "amqplib": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", - "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", - "optional": true, - "requires": { - "bitsyntax": "~0.1.0", - "bluebird": "^3.7.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "safe-buffer": "~5.2.1", - "url-parse": "~1.5.1" - } - } + "amqplib": "^0.10.3" } }, "randombytes": { diff --git a/package.json b/package.json index 778f18d..11f15a5 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "vite": "3.1.0" }, "optionalDependencies": { - "rabbiteer": "gitlab:Dunemask/rabbiteer" + "rabbiteer": "gitlab:Dunemask/rabbiteer#d2b8b92427a79ecccfa31d07269aec6fa5e550b3" } } diff --git a/public/assets/new-logo.png b/public/assets/new-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bb89165a6fb8feec8675e48457224dcd8e75cd19 GIT binary patch literal 193218 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%0At6tp$0j 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≺MjlCRNR}s+ramRTZeJ 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