Files
kener/scripts/startup.js
Raj Nandan Sharma b9fe4f069f new build
2023-12-25 22:13:27 +05:30

251 lines
8.8 KiB
JavaScript

/*
The startup js script will
check if monitors.yaml exists
if it does, it will read the file and parse it into a json array of objects
each objects will have a name, url, method: required
name of each of these objects need to be unique
*/
import fs from "fs-extra";
import yaml from "js-yaml";
import { Cron } from "croner";
import { FOLDER, FOLDER_MONITOR, FOLDER_SITE, API_TIMEOUT } from "./constants.js";
import { IsValidURL, IsValidHTTPMethod, LoadMonitorsPath, LoadSitePath } from "./tool.js";
import { GetAllGHLabels, CreateGHLabel } from "./github.js";
import { Minuter } from "./cron-minute.js";
let monitors = [];
let site = {};
const envSecrets = [];
const defaultEval = `(function (statusCode, responseTime, responseData) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
status: 'UP',
latency: responseTime,
}
}
return {
status: 'DOWN',
latency: responseTime,
}
})`;
function checkIfDuplicateExists(arr) {
return new Set(arr).size !== arr.length;
}
function getWordsStartingWithDollar(text) {
const regex = /\$\w+/g;
const wordsArray = text.match(regex);
return wordsArray || [];
}
if (!fs.existsSync(FOLDER)) {
fs.mkdirSync(FOLDER);
console.log(".kener folder created successfully!");
}
const Startup = async () => {
try {
const fileContent = fs.readFileSync(LoadMonitorsPath(), "utf8");
site = yaml.load(fs.readFileSync(LoadSitePath(), "utf8"));
monitors = yaml.load(fileContent);
} catch (error) {
console.log(error);
process.exit(1);
}
// Use the 'monitors' array of JSON objects as needed
//check if each object has name, url, method
//if not, exit with error
//if yes, check if name is unique
for (let i = 0; i < monitors.length; i++) {
const monitor = monitors[i];
let name = monitor.name;
let tag = monitor.tag;
let hasAPI = monitor.api !== undefined && monitor.api !== null;
let folderName = name.replace(/[^a-z0-9]/gi, "-").toLowerCase();
monitors[i].folderName = folderName;
if (!name || !tag) {
console.log("name, tag are required");
process.exit(1);
}
if(hasAPI) {
let url = monitor.api.url;
let method = monitor.api.method;
let headers = monitor.api.headers;
let evaluator = monitor.api.eval;
let body = monitor.api.body;
let timeout = monitor.api.timeout;
//url
if (!!url) {
if (!IsValidURL(url)) {
console.log("url is not valid");
process.exit(1);
}
}
if (!!method) {
if (!IsValidHTTPMethod(method)) {
console.log("method is not valid");
process.exit(1);
}
method = method.toUpperCase();
} else {
method = "GET";
}
monitors[i].api.method = method;
//headers
if (headers === undefined || headers === null) {
monitors[i].api.headers = undefined;
} else {
//check if headers is a valid json
try {
JSON.parse(headers);
} catch (error) {
console.log("headers is not valid ");
process.exit(1);
}
}
//eval
if (evaluator === undefined || evaluator === null) {
monitors[i].api.eval = defaultEval;
} else {
let evalResp = eval(evaluator + `(200, 1000, "")`);
if (evalResp === undefined || evalResp === null || evalResp.status === undefined || evalResp.status === null || evalResp.latency === undefined || evalResp.latency === null) {
console.log("eval is not valid ");
process.exit(1);
}
}
//body
if (body === undefined || body === null) {
monitors[i].api.body = undefined;
} else {
//check if body is a valid string
if (typeof body !== "string") {
console.log("body is not valid should be a string");
process.exit(1);
}
}
//timeout
if (timeout === undefined || timeout === null) {
monitors[i].api.timeout = API_TIMEOUT;
} else {
//check if timeout is a valid number
if (isNaN(timeout) || timeout < 0) {
console.log("timeout is not valid ");
process.exit(1);
}
}
}
monitors[i].path0Day = `${FOLDER}/${folderName}.0day.utc.json`;
monitors[i].hasAPI = hasAPI;
//secrets can be in url/body/headers
//match in monitor.url if a words starts with $, get the word
const requiredSecrets = getWordsStartingWithDollar(`${monitor.url} ${monitor.body} ${JSON.stringify(monitor.headers)}`).map((x) => x.substr(1));
//iterate over process.env
for (const [key, value] of Object.entries(process.env)) {
if (requiredSecrets.indexOf(key) !== -1) {
envSecrets.push({
find: `$${key}`,
replace: value,
});
}
}
}
if (site.github === undefined || site.github.owner === undefined || site.github.repo === undefined) {
console.log("github owner and repo are required");
process.exit(1);
}
if (site.github.incidentSince === undefined || site.github.incidentSince === null) {
site.github.incidentSince = 48;
}
if (checkIfDuplicateExists(monitors.map((monitor) => monitor.folderName)) === true) {
console.log("duplicate monitor detected");
process.exit(1);
}
if (checkIfDuplicateExists(monitors.map((monitor) => monitor.tag)) === true) {
console.log("duplicate tag detected");
process.exit(1);
}
fs.ensureFileSync(FOLDER_MONITOR);
fs.ensureFileSync(FOLDER_SITE);
try {
fs.writeFileSync(FOLDER_MONITOR, JSON.stringify(monitors, null, 4));
fs.writeFileSync(FOLDER_SITE, JSON.stringify(site, null, 4));
} catch (error) {
console.log(error);
process.exit(1);
}
if (!!site.github && !!site.github.owner && !!site.github.repo) {
const ghowner = site.github.owner;
const ghrepo = site.github.repo;
const ghlabels = await GetAllGHLabels(ghowner, ghrepo);
const tagsAndDescription = monitors.map((monitor) => {
return { tag: monitor.tag, description: monitor.name };
});
//add incident label if does not exist
if (ghlabels.indexOf("incident") === -1) {
await CreateGHLabel(ghowner, ghrepo, "incident", "Status of the site");
}
if (ghlabels.indexOf("resolved") === -1) {
await CreateGHLabel(ghowner, ghrepo, "resolved", "Incident is resolved", "65dba6");
}
if (ghlabels.indexOf("identified") === -1) {
await CreateGHLabel(ghowner, ghrepo, "identified", "Incident is Identified", "EBE3D5");
}
if (ghlabels.indexOf("investigating") === -1) {
await CreateGHLabel(ghowner, ghrepo, "investigating", "Incident is investigated", "D4E2D4");
}
if (ghlabels.indexOf("incident-degraded") === -1) {
await CreateGHLabel(ghowner, ghrepo, "incident-degraded", "Status is degraded of the site", "f5ba60");
}
if (ghlabels.indexOf("incident-down") === -1) {
await CreateGHLabel(ghowner, ghrepo, "incident-down", "Status is down of the site", "ea3462");
}
//add tags if does not exist
for (let i = 0; i < tagsAndDescription.length; i++) {
const tag = tagsAndDescription[i].tag;
const description = tagsAndDescription[i].description;
if (ghlabels.indexOf(tag) === -1) {
await CreateGHLabel(ghowner, ghrepo, tag, description);
}
}
}
// init monitors
for (let i = 0; i < monitors.length; i++) {
const monitor = monitors[i];
if (!fs.existsSync(monitor.path0Day)) {
fs.ensureFileSync(monitor.path0Day);
fs.writeFileSync(monitor.path0Day, JSON.stringify({}));
}
console.log("Staring One Minute Cron for ", monitor.path0Day);
await Minuter(envSecrets, monitor, site.github);
}
//trigger minute cron
for (let i = 0; i < monitors.length; i++) {
const monitor = monitors[i];
let cronExpession = "* * * * *";
if (monitor.cron !== undefined && monitor.cron !== null) {
cronExpession = monitor.cron;
}
console.log("Staring " + cronExpession + " Cron for ", monitor.name);
Cron(cronExpession, async () => {
await Minuter(envSecrets, monitor, site.github);
});
}
};
export { Startup };