Skip to content

Commit

Permalink
feat(agent): init alerts & notifiers (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreDemailly authored Jul 24, 2023
1 parent 7f280f4 commit c09381e
Show file tree
Hide file tree
Showing 15 changed files with 528 additions and 128 deletions.
45 changes: 39 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 33 additions & 6 deletions src/agent/data/init-db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,41 @@ PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS rules
(
name TEXT UNIQUE,
counter INTEGER DEFAULT 0,
lastRunAt INTEGER
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
counter INTEGER DEFAULT 0,
lastRunAt INTEGER
);

CREATE TABLE IF NOT EXISTS counters
(
name TEXT,
counter INTEGER,
timestamp INTEGER
id INTEGER PRIMARY KEY,
ruleId INTEGER,
counter INTEGER,
timestamp INTEGER,
FOREIGN KEY(ruleId) REFERENCES rules(id)
);

CREATE TABLE IF NOT EXISTS alerts
(
id INTEGER PRIMARY KEY,
ruleId INTEGER,
createdAt INTEGER,
FOREIGN KEY(ruleId) REFERENCES rules(id)
);

CREATE TABLE IF NOT EXISTS notifiers
(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS alertNotifs
(
alertId INTEGER,
notifierId INTEGER,
status TEXT DEFAULT "pending",
retries INTEGER DEFAULT 0,
FOREIGN KEY(alertId) REFERENCES alerts(id),
FOREIGN KEY(notifierId) REFERENCES notifiers(id)
);
11 changes: 7 additions & 4 deletions src/agent/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@myunisoft/sigyn.agent",
"name": "@sigyn/agent",
"version": "1.0.0",
"description": "Another inspired Rust's Result implementation.",
"description": "Loki alerting agent",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
Expand All @@ -19,7 +19,7 @@
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
"dev": "npm run build -- --watch",
"prepublishOnly": "npm run build",
"test": "glob -c \"node --loader tsx --no-warnings --test\" \"./test/**/*.spec.ts\"",
"test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"",
"coverage": "c8 -r html npm test",
"lint": "cross-env eslint src/**/*.ts"
},
Expand All @@ -32,12 +32,15 @@
"license": "MIT",
"dependencies": {
"@myunisoft/loki": "^1.2.0",
"@openally/result": "^1.2.0",
"better-sqlite3": "^8.4.0",
"dayjs": "^1.11.9",
"dotenv": "^16.3.1",
"ms": "^2.1.3",
"pino": "^8.14.1",
"toad-scheduler": "^3.0.0"
"pupa": "^3.1.0",
"toad-scheduler": "^3.0.0",
"ts-pattern": "^5.0.4"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.4",
Expand Down
24 changes: 24 additions & 0 deletions src/agent/src/alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Import Third-party Dependencies
import dayjs from "dayjs";
import { Logger } from "pino";

// Import Internal Dependencies
import { DbRule, getDB } from "./database";
import { Notifier } from "./notifier";
import { SigynNotifiers, SigynRule, getConfig } from "./config";

export function createAlert(rule: DbRule, config: SigynRule, logger: Logger) {
const notifier = Notifier.getNotifier(logger);
const rulegNotifiers = config.notifiers ?? [];
const globalNotifiers = Object.keys(getConfig().notifiers) as (keyof SigynNotifiers)[];
const notifierNames = rulegNotifiers.length > 0 ? rulegNotifiers : globalNotifiers;

for (const notifierName of notifierNames) {
notifier.sendAlert({ rule, notifier: notifierName });
}

getDB().prepare("INSERT INTO alerts (ruleId, createdAt) VALUES (?, ?)").run(
rule.id,
dayjs().unix()
);
}
57 changes: 57 additions & 0 deletions src/agent/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Import Node.js Dependencies
import path from "node:path";
import fs from "node:fs";

export interface SigynConfig {
notifiers: SigynNotifiers;
rules: SigynRule[]
}

export interface SigynNotifiers {
discord?: DiscordNotifier;
}

export interface DiscordNotifier {
webhookUrl: string;
}

export interface SigynRule {
name: string;
logql: string;
polling: string;
alert: SigynAlert;
disabled?: boolean;
notifiers?: (keyof SigynNotifiers)[];
}

export interface SigynAlert {
on: {
count: number;
interval: string;
},
template: SigynAlertTemplate;
}

export interface SigynAlertTemplate {
title?: string;
content?: string[];
}

let config: SigynConfig;

export function initConfig(location: string): SigynConfig {
const rawConfig = fs.readFileSync(path.join(location, "/config.json"), "utf-8");

config = JSON.parse(rawConfig);

// TODO: verify configs format ?
return config;
}

export function getConfig(): SigynConfig {
if (config === undefined) {
throw new Error("You must init config first");
}

return config;
}
37 changes: 32 additions & 5 deletions src/agent/src/database.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
// Import Node.js Dependencies
import path from "node:path";
import url from "node:url";
import fs from "node:fs";

// Import Third-party Dependencies
import SQLite3 from "better-sqlite3";
import { Logger } from "pino";

// CONSTANTS
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const kDefaultDatabaseFilename = process.env.SIGYN_DB ?? "sigyn.sqlite3";
const kDatabaseInitPath = path.join(__dirname, "../data/init-db.sql");

let db: SQLite3.Database;

export interface DbRule {
id: number;
name: string;
counter: number;
lastRunAt?: number;
}

export interface DbCounter {
id: number;
name: string;
counter: number;
timestamp: number;
}

export interface DbAlert {
id: number;
createdAt: number;
}

export interface DbNotifier {
id: number;
name: string;
}

export interface DbAlertNotif {
id: number;
alertId: number;
notifierId: number;
status: "pending" | "success" | "error";
retries: number;
}

export interface InitDbOptions {
/**
* @default process.env.SIGYN_DB.
Expand Down
41 changes: 41 additions & 0 deletions src/agent/src/discord/discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// NOTE: This will be redesigned & moved to @sigyn/discord.

// CONSTANTS
const kWebhookUsername = "Sigyn Agent";

export async function executeWebhook(options) {
// pupa is ESM only, need a dynamic import for CommonJS.
const { default: pupa } = await import("pupa");
const { webhookUrl, ruleConfig, rule } = options;

function webhookStructure() {
const counter = rule.counter;
const { count, interval } = ruleConfig.alert.on;
const ruleName = ruleConfig.name;
const polling = ruleConfig.polling;
const logql = ruleConfig.logql.replaceAll("`", "'");
const lokiUrl = "https://todo.com";
const templateData = { ruleName, count, counter, interval, polling, logql, lokiUrl };

const content: any[] = ruleConfig.alert.template.content.map((content) => pupa(
content,
templateData
));
if (ruleConfig.alert.template.title) {
content.unshift(pupa(ruleConfig.alert.template.title, templateData));
}

return {
content: content.join("\n"),
username: kWebhookUsername
};
}

await fetch(webhookUrl, {
method: "POST",
body: JSON.stringify(webhookStructure()),
headers: {
"Content-Type": "application/json"
}
});
}
Loading

0 comments on commit c09381e

Please sign in to comment.