core/source/logic/order.ts
2024-07-02 15:02:35 +02:00

604 lines
12 KiB
TypeScript

/*
Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»heimdall« is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»heimdall« is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with »heimdall«. If not, see <http://www.gnu.org/licenses/>.
*/
namespace _heimdall.order
{
/**
*/
function schema_active(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"description": "whether the check shall be executed",
"type": "boolean",
"default": true,
};
}
/**
*/
function schema_threshold(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"description": "how often a condition has to occur in order to be reported",
"type": "integer",
"minimum": 1,
"default": 3,
};
}
/**
*/
function schema_annoy(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"description": "whether notifications about non-ok states shall be kept sending after the threshold has been surpassed",
"type": "boolean",
"default": false,
};
}
/**
*/
function schema_interval(
allow_null : boolean,
default_
) : _heimdall.helpers.json_schema.type_schema
{
return {
"anyOf": (
[]
.concat(
allow_null
? [
{
"type": "null",
}
]
: []
)
.concat(
[
{
"description": "in seconds",
"type": "integer",
"exclusiveMinimum": 0,
},
{
"description": "as text",
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week",
]
},
]
)
),
"default": default_,
};
}
/**
*/
function schema_schedule(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"type": "object",
"additionalProperties": false,
"properties": {
"regular_interval": schema_interval(false, (60 * 60)),
"attentive_interval": schema_interval(false, (60 * 2)),
"reminding_interval": schema_interval(true, (60 * 60 * 24)),
},
"required": [
],
};
}
/**
*/
function schema_notifications(
) : _heimdall.helpers.json_schema.type_schema
{
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
return {
"type": "array",
"items": {
"anyOf": (
Object.entries(notification_kind_implementations)
.map(
([key, value]) => ({
"title": ("notification channel '" + key + "'"),
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [key]
},
"parameters": value.parameters_schema(),
},
"required": [
"kind",
"parameters"
],
})
)
)
},
"default": [
{
"kind": "console",
"parameters": {
}
},
],
};
}
/**
*/
export function schema_root(
) : _heimdall.helpers.json_schema.type_schema
{
const check_kind_implementations = _heimdall.check_kinds.get_implementations();
return {
"type": "object",
"additionalProperties": false,
"properties": {
"defaults": {
"description": "default values for checks",
"type": "object",
"additionalProperties": false,
"properties": {
"active": schema_active(),
"threshold": schema_threshold(),
"annoy": schema_annoy(),
"schedule": schema_schedule(),
"notifications": schema_notifications(),
},
"required": [
],
},
"includes": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "list of relative or absolute paths to other hmdl files on the local machine, which shall be subsumed in the overall monitoring task"
},
"checks": {
"type": "array",
"items": {
"allOf": [
{
"description": "should represent a specific check",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"active": schema_active(),
"threshold": schema_threshold(),
"annoy": schema_annoy(),
"schedule": schema_schedule(),
"notifications": schema_notifications(),
},
"required": [
"name",
],
},
{
"anyOf": (
Object.entries(check_kind_implementations)
.map(
([key, value]) => ({
"title": ("check '" + key + "'"),
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [key]
},
"parameters": value.parameters_schema(),
"custom": {
"type": "any",
"description": "custom data, which shall be attached to notifications",
"default": null,
},
},
"required": [
"kind",
"parameters",
]
})
)
),
},
]
}
}
},
"required": [
]
};
}
/**
*/
function normalize_interval(
interval_raw
) : (null | _heimdall.type_interval)
{
if (interval_raw === null) {
return null;
}
else {
if (typeof(interval_raw) === "number") {
return interval_raw;
}
else if (typeof(interval_raw) === "string") {
const map_ : Record<("minute" | "hour" | "day" | "week"), int> = {
"minute": (60),
"hour": (60 * 60),
"day": (60 * 60 * 24),
"week": (60 * 60 * 24 * 7),
};
if (! (interval_raw in map_)) {
throw new Error("invalid string interval value: " + interval_raw);
}
else {
return map_[interval_raw];
}
}
else {
throw new Error("invalid type for interval value");
}
}
}
/**
*/
function normalize_schedule(
node
) : _heimdall.type_schedule
{
const node_ = Object.assign(
{
"regular_interval": (60 * 60),
"attentive_interval": (60 * 2),
"reminding_interval": (60 * 60 * 24),
},
node
);
return {
"regular_interval": normalize_interval(node_["regular_interval"]),
"attentive_interval": normalize_interval(node_["attentive_interval"]),
"reminding_interval": normalize_interval(node_["reminding_interval"]),
};
}
/**
*/
function normalize_notification(
node : any
) : type_notification
{
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
if (! (node["kind"] in notification_kind_implementations)) {
throw new Error("invalid notification kind: " + node["kind"]);
}
else {
return {
"kind": node["kind"],
"parameters": notification_kind_implementations[node["kind"]].normalize_order_node(node["parameters"]),
};
}
}
/**
*/
function normalize_defaults(
node : any
) : type_check_common
{
const node_ = Object.assign(
{
"active": true,
"threshold": 3,
"annoy": false,
"schedule": {
"regular_interval": (60 * 60),
"attentive_interval": (60 * 2),
"reminding_interval": (60 * 60 * 24),
},
"notifications": [
],
},
node
)
return {
"active": node_["active"],
"threshold": node_["threshold"],
"annoy": node_["annoy"],
"schedule": node_["schedule"],
"notifications": (
node_["notifications"]
.map(x => normalize_notification(x))
),
};
}
/**
*/
function normalize_check(
defaults : type_check_common,
node : {
name : string;
kind : string;
}
) : type_check
{
const check_kind_implementations = _heimdall.check_kinds.get_implementations();
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
if (! ("name" in node)) {
throw new Error("missing mandatory field in 'check' node: 'name'");
}
else {
if (! ("kind" in node)) {
throw new Error("missing mandatory field in 'check' node: 'kind'");
}
else {
if (! (node.kind in check_kind_implementations)) {
throw new Error("invalid check kind: " + node.kind);
}
else {
const node_ : type_check = (Object.assign(
lib_plankton.object.patched(
defaults,
{
"title": node.name,
"parameters": {},
"custom": null,
},
),
node
) as type_check);
let check : type_check = {
"name": node_.name,
"title": node_.title,
"kind": node_.kind,
"parameters": check_kind_implementations[node_.kind].normalize_order_node(node_.parameters),
"custom": node_.custom,
};
if ("active" in node_) {
check.active = node_["active"];
}
if ("threshold" in node_) {
check.threshold = node_["threshold"];
}
if ("annoy" in node_) {
check.annoy = node_["annoy"];
}
if ("schedule" in node_) {
check.schedule = normalize_schedule(node_["schedule"]);
}
if ("notifications" in node_) {
check.notifications = (
node_["notifications"]
.map(
x => normalize_notification(x),
)
);
}
if ("custom" in node_) {
check.custom = node_["custom"];
}
return check;
}
}
}
}
/**
*/
function normalize_root(
node : any,
options : {
use_implicit_default_values ?: boolean;
} = {}
) : type_order
{
options = Object.assign(
{
"use_implicit_default_values": true,
},
options
);
let counts : Record<string, int> = {};
const checks_raw : Array<any> = (
("checks" in node)
? node["checks"]
: []
);
checks_raw.forEach(
node_ => {
if (! (node_["name"] in counts)) {
counts[node_["name"]] = 0;
}
counts[node_["name"]] += 1;
}
);
const fails : Array<[string, int]> = (
Object.entries(counts)
.filter(
([key, value]) => (value > 1)
)
);
if (fails.length > 0) {
throw new Error(
lib_plankton.string.coin(
"ambiguous check names: {{names}}",
{
"names": fails.map(([key, value]) => key).join(","),
}
)
);
}
else {
const defaults_raw : any = (
("defaults" in node)
? node["defaults"]
: {}
);
const defaults : type_check_common = (
options.use_implicit_default_values
? normalize_defaults(
defaults_raw
)
: defaults_raw
);
const includes : Array<string> = (
("includes" in node)
? node["includes"]
: []
);
return {
"defaults": defaults,
"includes": includes,
"checks": (
checks_raw
.map(
node_ => normalize_check(
defaults,
node_
)
)
)
};
}
}
/**
*/
export async function load(
path : string,
options : {
root ?: boolean;
already_included ?: Array<string>;
} = {}
) : Promise<type_order>
{
const nm_path = require("path");
options = Object.assign(
{
"root": true,
"already_included": [],
},
options
);
if (options.already_included.includes(path)) {
throw new Error("circular dependency detected")
}
else {
const order_raw : {
includes ?: Array<string>;
checks ?: Array<any>;
} = lib_plankton.json.decode(await lib_plankton.file.read(path));
let includes : Array<string> = (
("includes" in order_raw)
? order_raw.includes
: []
);
for (let index = 0; index < includes.length; index += 1) {
const path_ : string = includes[index];
const sub_order : type_order = await load(
(
nm_path.isAbsolute(path_)
? path_
: nm_path.join(nm_path.dirname(path), path_)
),
{
"root": false,
"already_included": options.already_included.concat([path]),
}
)
if (! ("checks" in order_raw)) {
order_raw.checks = [];
}
order_raw.checks = order_raw.checks.concat(
sub_order.checks
.map(
check => Object.assign(
check,
{
"name": lib_plankton.string.coin(
"{{prefix}}.{{original_name}}",
{
"prefix": nm_path.basename(path_).split(".")[0],
"original_name": check["name"],
}
),
}
),
sub_order.checks
)
);
order_raw.includes = [];
}
return normalize_root(
order_raw,
{
"use_implicit_default_values": options["root"],
}
);
}
}
}