MNTOR-2431 - create alert for scan and profile limits (#3779)
* MNTOR-2431 - create alert for scan and profile limits
This commit is contained in:
Родитель
a601fb59e2
Коммит
6d53d6b144
|
@ -166,3 +166,9 @@ NIMBUS_SIDECAR_URL=http://localhost:8001
|
|||
|
||||
# The maximum number of jobs that the email breach alert worker will process.
|
||||
EMAIL_BREACH_ALERT_MAX_MESSAGES = 10000
|
||||
|
||||
# The maximum number of scans and profiles allowed. May be used for alerts, and for redirecting to waitlist.
|
||||
MAX_MANUAL_SCANS=100
|
||||
MAX_INITIAL_SCANS=100
|
||||
MAX_PROFILES_ACTIVATED=100
|
||||
MAX_PROFILES_CREATED=100
|
||||
|
|
|
@ -108,7 +108,7 @@ async function onerepFetch(
|
|||
}
|
||||
const onerepApiKey = process.env.ONEREP_API_KEY;
|
||||
if (!onerepApiKey) {
|
||||
throw new Error("ONEREP_API_BASE env var not set");
|
||||
throw new Error("ONEREP_API_KEY env var not set");
|
||||
}
|
||||
const url = new URL(path, onerepApiBase);
|
||||
const headers = new Headers(options.headers);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
table.timestamp('deleted_at')
|
||||
table.string('owner')
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function down (knex) {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export async function up(knex) {
|
||||
return knex.schema
|
||||
.createTable("stats", table => {
|
||||
table.increments('id').primary()
|
||||
table.string("name")
|
||||
table.string("current")
|
||||
table.string("max")
|
||||
table.string("type")
|
||||
table.timestamp("created_at").defaultTo(knex.fn.now())
|
||||
table.timestamp("modified_at").defaultTo(knex.fn.now())
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
export async function down(knex) {
|
||||
return knex.schema
|
||||
.dropTableIfExists("stats")
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import createDbConnection from "../connect.js";
|
||||
const knex = createDbConnection();
|
||||
|
||||
export { knex as knexStats }
|
||||
|
||||
// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
|
||||
/* c8 ignore start */
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} current
|
||||
* @param {string} max
|
||||
* @returns {Promise<import('knex/types/tables').SubscriberRow | null>} updated subscriber
|
||||
*/
|
||||
export async function addOnerepStats (name, current, max) {
|
||||
const res = await knex("stats").insert({ name, current, max, type: "onerep"}).returning("*");
|
||||
return res[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object}
|
||||
*/
|
||||
export async function getOnerepStats () {
|
||||
const res = await knex("stats").select("name", "current", "max").where("type", "onerep");
|
||||
return res[0];
|
||||
}
|
||||
|
||||
/* c8 ignore stop */
|
|
@ -370,4 +370,10 @@ declare module "knex/types/tables" {
|
|||
Pick<EmailNotificationRow, "updated_at">
|
||||
>;
|
||||
}
|
||||
interface StatsRow {
|
||||
name: string;
|
||||
current: string;
|
||||
max: string;
|
||||
type: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Sentry from "@sentry/nextjs";
|
||||
import { addOnerepStats, knexStats } from "../db/tables/stats.js";
|
||||
|
||||
const SENTRY_SLUG = "cron-onerep-stats-alerts";
|
||||
|
||||
const MAX_MANUAL_SCANS = parseInt(process.env.MAX_MANUAL_SCANS) || 0;
|
||||
const MAX_INITIAL_SCANS = parseInt(process.env.MAX_INITIAL_SCANS) || 0;
|
||||
const MAX_PROFILES_ACTIVATED =
|
||||
parseInt(process.env.MAX_PROFILES_ACTIVATED) || 0;
|
||||
const MAX_PROFILES_CREATED = parseInt(process.env.MAX_PROFILES_CREATED) || 0;
|
||||
|
||||
Sentry.init({
|
||||
environment: process.env.APP_ENV,
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
||||
const checkInId = Sentry.captureCheckIn({
|
||||
monitorSlug: SENTRY_SLUG,
|
||||
status: "in_progress",
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch the latest usage statistics from OneRep's API.
|
||||
*
|
||||
* @see https://docs.onerep.com/#tag/Statistics
|
||||
*/
|
||||
export async function checkStats() {
|
||||
const scans = await (await onerepFetch("/stats/scans")).json();
|
||||
const profiles = await (await onerepFetch("/stats/profiles")).json();
|
||||
|
||||
for (const alert of [
|
||||
["free_scans", scans.manual, MAX_MANUAL_SCANS],
|
||||
["paid_scans", scans.initial, MAX_INITIAL_SCANS],
|
||||
["profiles_activated", profiles.activated, MAX_PROFILES_ACTIVATED],
|
||||
["profiles_created", profiles.created, MAX_PROFILES_CREATED],
|
||||
]) {
|
||||
const [name, current, max] = alert;
|
||||
|
||||
await addOnerepStats(name, current, max);
|
||||
|
||||
if (current >= max) {
|
||||
const msg = `Alert: OneRep scans over limit for ${name}. Current: ${current}, max: ${max}`;
|
||||
console.error(msg);
|
||||
Sentry.captureMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use the shared version when this is converted to Typescript.
|
||||
async function onerepFetch(path, options = {}) {
|
||||
const onerepApiBase = process.env.ONEREP_API_BASE;
|
||||
if (!onerepApiBase) {
|
||||
throw new Error("ONEREP_API_BASE env var not set");
|
||||
}
|
||||
const onerepApiKey = process.env.ONEREP_API_KEY;
|
||||
if (!onerepApiKey) {
|
||||
throw new Error("ONEREP_API_KEY env var not set");
|
||||
}
|
||||
const url = new URL(path, onerepApiBase);
|
||||
const headers = new Headers(options?.headers);
|
||||
headers.set("Authorization", `Bearer ${onerepApiKey}`);
|
||||
headers.set("Accept", "application/json");
|
||||
headers.set("Content-Type", "application/json");
|
||||
return fetch(url, { ...options, headers });
|
||||
}
|
||||
|
||||
checkStats()
|
||||
.then(async (_) => {
|
||||
Sentry.captureCheckIn({
|
||||
checkInId,
|
||||
monitorSlug: SENTRY_SLUG,
|
||||
status: "ok",
|
||||
});
|
||||
knexStats.destroy();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
Sentry.captureException(err);
|
||||
});
|
Загрузка…
Ссылка в новой задаче