core/source/logic/order.ts

604 lines
12 KiB
TypeScript
Raw Permalink Normal View History

2024-07-02 15:02:35 +02:00
/*
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/>.
*/
2023-06-18 23:29:57 +02:00
namespace _heimdall.order
{
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function schema_active(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"description": "whether the check shall be executed",
"type": "boolean",
"default": true,
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
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,
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
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,
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function schema_interval(
2023-06-19 18:27:04 +02:00
allow_null : boolean,
2023-06-18 23:29:57 +02:00
default_
) : _heimdall.helpers.json_schema.type_schema
{
return {
2023-07-06 16:13:26 +02:00
"anyOf": (
[]
.concat(
allow_null
? [
{
"type": "null",
}
2023-06-18 23:29:57 +02:00
]
2023-07-06 16:13:26 +02:00
: []
)
.concat(
[
{
"description": "in seconds",
"type": "integer",
"exclusiveMinimum": 0,
},
{
"description": "as text",
"type": "string",
"enum": [
"minute",
"hour",
"day",
"week",
]
},
]
)
),
2023-06-18 23:29:57 +02:00
"default": default_,
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function schema_schedule(
2023-06-19 18:27:04 +02:00
) : _heimdall.helpers.json_schema.type_schema
2023-06-18 23:29:57 +02:00
{
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": [
],
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function schema_notifications(
2023-06-19 18:27:04 +02:00
) : _heimdall.helpers.json_schema.type_schema
2023-06-18 23:29:57 +02:00
{
2023-08-03 08:34:33 +02:00
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
2023-06-18 23:29:57 +02:00
return {
"type": "array",
"items": {
"anyOf": (
2023-06-19 13:05:17 +02:00
Object.entries(notification_kind_implementations)
2023-06-18 23:29:57 +02:00
.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": {
}
},
],
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
export function schema_root(
2023-06-19 18:27:04 +02:00
) : _heimdall.helpers.json_schema.type_schema
2023-06-18 23:29:57 +02:00
{
2023-08-03 08:34:33 +02:00
const check_kind_implementations = _heimdall.check_kinds.get_implementations();
2023-06-18 23:29:57 +02:00
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(),
2023-08-03 08:34:33 +02:00
"notifications": schema_notifications(),
2023-06-18 23:29:57 +02:00
},
"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(),
2023-08-03 08:34:33 +02:00
"notifications": schema_notifications(),
2023-06-18 23:29:57 +02:00
},
"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": {
2023-07-27 17:37:45 +02:00
"type": "any",
2023-06-18 23:29:57 +02:00
"description": "custom data, which shall be attached to notifications",
"default": null,
},
},
"required": [
"kind",
"parameters",
]
})
)
),
},
]
}
}
},
"required": [
]
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_interval(
interval_raw
2023-06-19 18:40:13 +02:00
) : (null | _heimdall.type_interval)
2023-06-18 23:29:57 +02:00
{
if (interval_raw === null) {
return null;
}
else {
if (typeof(interval_raw) === "number") {
return interval_raw;
}
else if (typeof(interval_raw) === "string") {
2023-06-19 18:27:04 +02:00
const map_ : Record<("minute" | "hour" | "day" | "week"), int> = {
2023-06-18 23:29:57 +02:00
"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");
}
}
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_schedule(
node
2023-06-19 18:40:13 +02:00
) : _heimdall.type_schedule
2023-06-18 23:29:57 +02:00
{
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"]),
};
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_notification(
2023-06-19 18:27:04 +02:00
node : any
) : type_notification
2023-06-18 23:29:57 +02:00
{
2023-08-03 08:34:33 +02:00
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
2023-06-19 13:05:17 +02:00
if (! (node["kind"] in notification_kind_implementations)) {
2023-06-18 23:29:57 +02:00
throw new Error("invalid notification kind: " + node["kind"]);
}
else {
return {
"kind": node["kind"],
2023-06-19 13:05:17 +02:00
"parameters": notification_kind_implementations[node["kind"]].normalize_order_node(node["parameters"]),
2023-06-18 23:29:57 +02:00
};
}
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_defaults(
2023-06-19 18:27:04 +02:00
node : any
) : type_check_common
2023-06-18 23:29:57 +02:00
{
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"]
2023-08-03 08:34:33 +02:00
.map(x => normalize_notification(x))
2023-06-18 23:29:57 +02:00
),
};
}
2023-06-19 13:05:17 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_check(
2023-06-19 13:05:17 +02:00
defaults : type_check_common,
node : {
name : string;
kind : string;
}
) : type_check
2023-06-18 23:29:57 +02:00
{
2023-08-03 08:34:33 +02:00
const check_kind_implementations = _heimdall.check_kinds.get_implementations();
const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
2023-06-18 23:29:57 +02:00
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 {
2023-06-19 13:05:17 +02:00
if (! (node.kind in check_kind_implementations)) {
throw new Error("invalid check kind: " + node.kind);
2023-06-18 23:29:57 +02:00
}
else {
2023-12-02 09:43:15 +01:00
const node_ : type_check = (Object.assign(
2023-08-31 19:15:40 +02:00
lib_plankton.object.patched(
2023-06-18 23:29:57 +02:00
defaults,
{
2023-06-19 13:05:17 +02:00
"title": node.name,
2023-06-18 23:29:57 +02:00
"parameters": {},
"custom": null,
},
),
node
2023-12-02 09:43:15 +01:00
) as type_check);
2023-06-19 13:05:17 +02:00
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,
};
2023-06-18 23:29:57 +02:00
if ("active" in node_) {
2023-06-19 13:05:17 +02:00
check.active = node_["active"];
2023-06-18 23:29:57 +02:00
}
if ("threshold" in node_) {
2023-06-19 13:05:17 +02:00
check.threshold = node_["threshold"];
2023-06-18 23:29:57 +02:00
}
if ("annoy" in node_) {
2023-06-19 13:05:17 +02:00
check.annoy = node_["annoy"];
2023-06-18 23:29:57 +02:00
}
if ("schedule" in node_) {
2023-06-19 13:05:17 +02:00
check.schedule = normalize_schedule(node_["schedule"]);
2023-06-18 23:29:57 +02:00
}
if ("notifications" in node_) {
2023-06-19 13:05:17 +02:00
check.notifications = (
2023-06-18 23:29:57 +02:00
node_["notifications"]
.map(
2023-08-03 08:34:33 +02:00
x => normalize_notification(x),
2023-06-18 23:29:57 +02:00
)
);
}
if ("custom" in node_) {
2023-06-19 13:05:17 +02:00
check.custom = node_["custom"];
2023-06-18 23:29:57 +02:00
}
2023-06-19 13:05:17 +02:00
return check;
2023-06-18 23:29:57 +02:00
}
}
}
}
2023-06-19 13:05:17 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
function normalize_root(
2023-06-19 18:27:04 +02:00
node : any,
2023-06-19 13:05:17 +02:00
options : {
use_implicit_default_values ?: boolean;
} = {}
) : type_order
2023-06-18 23:29:57 +02:00
{
options = Object.assign(
{
"use_implicit_default_values": true,
},
options
2023-08-03 08:34:33 +02:00
);
2023-06-19 13:05:17 +02:00
let counts : Record<string, int> = {};
const checks_raw : Array<any> = (
2023-06-18 23:29:57 +02:00
("checks" in node)
? node["checks"]
: []
);
checks_raw.forEach(
node_ => {
if (! (node_["name"] in counts)) {
counts[node_["name"]] = 0;
}
counts[node_["name"]] += 1;
}
);
2023-06-19 13:05:17 +02:00
const fails : Array<[string, int]> = (
2023-06-18 23:29:57 +02:00
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 {
2023-06-19 13:05:17 +02:00
const defaults_raw : any = (
2023-06-18 23:29:57 +02:00
("defaults" in node)
? node["defaults"]
: {}
2023-08-31 19:15:40 +02:00
);
2023-06-19 13:05:17 +02:00
const defaults : type_check_common = (
options.use_implicit_default_values
2023-06-18 23:29:57 +02:00
? normalize_defaults(
defaults_raw
)
: defaults_raw
2023-08-31 19:15:40 +02:00
);
2023-06-19 13:05:17 +02:00
const includes : Array<string> = (
2023-06-18 23:29:57 +02:00
("includes" in node)
? node["includes"]
: []
2023-08-31 19:15:40 +02:00
);
2023-06-18 23:29:57 +02:00
return {
"defaults": defaults,
"includes": includes,
"checks": (
checks_raw
.map(
node_ => normalize_check(
defaults,
node_
)
)
)
};
}
}
2023-06-19 18:27:04 +02:00
/**
*/
2023-06-18 23:29:57 +02:00
export async function load(
path : string,
2023-06-19 13:05:17 +02:00
options : {
root ?: boolean;
already_included ?: Array<string>;
} = {}
) : Promise<type_order>
2023-06-18 23:29:57 +02:00
{
2023-06-19 13:05:17 +02:00
const nm_path = require("path");
2023-06-18 23:29:57 +02:00
options = Object.assign(
{
"root": true,
2023-06-19 13:05:17 +02:00
"already_included": [],
2023-06-18 23:29:57 +02:00
},
options
2023-08-03 08:34:33 +02:00
);
2023-06-19 15:04:15 +02:00
if (options.already_included.includes(path)) {
2023-06-18 23:29:57 +02:00
throw new Error("circular dependency detected")
}
else {
2023-07-06 15:09:34 +02:00
const order_raw : {
includes ?: Array<string>;
checks ?: Array<any>;
} = lib_plankton.json.decode(await lib_plankton.file.read(path));
2023-06-18 23:29:57 +02:00
let includes : Array<string> = (
("includes" in order_raw)
2023-07-06 15:09:34 +02:00
? order_raw.includes
2023-06-18 23:29:57 +02:00
: []
);
for (let index = 0; index < includes.length; index += 1) {
2023-06-19 15:04:15 +02:00
const path_ : string = includes[index];
const sub_order : type_order = await load(
2023-06-18 23:29:57 +02:00
(
2023-06-19 13:05:17 +02:00
nm_path.isAbsolute(path_)
2023-06-18 23:29:57 +02:00
? path_
2023-06-19 13:05:17 +02:00
: nm_path.join(nm_path.dirname(path), path_)
2023-06-18 23:29:57 +02:00
),
{
"root": false,
2023-06-19 13:05:17 +02:00
"already_included": options.already_included.concat([path]),
2023-06-18 23:29:57 +02:00
}
)
if (! ("checks" in order_raw)) {
2023-07-06 15:09:34 +02:00
order_raw.checks = [];
2023-06-18 23:29:57 +02:00
}
2023-07-06 15:09:34 +02:00
order_raw.checks = order_raw.checks.concat(
2023-06-19 15:04:15 +02:00
sub_order.checks
2023-06-18 23:29:57 +02:00
.map(
check => Object.assign(
check,
{
"name": lib_plankton.string.coin(
"{{prefix}}.{{original_name}}",
{
2023-06-19 13:05:17 +02:00
"prefix": nm_path.basename(path_).split(".")[0],
2023-06-18 23:29:57 +02:00
"original_name": check["name"],
}
),
}
),
2023-06-19 15:04:15 +02:00
sub_order.checks
2023-06-18 23:29:57 +02:00
)
);
2023-07-06 15:09:34 +02:00
order_raw.includes = [];
2023-06-18 23:29:57 +02:00
}
return normalize_root(
order_raw,
{
"use_implicit_default_values": options["root"],
}
);
}
}
}