diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index cf177a6..41d31f5 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -1487,6 +1487,9 @@ declare namespace lib_plankton.file { * @author fenris */ function write_buffer(path: string, content: Buffer, options?: {}): Promise; + /** + */ + function delete_(path: string): Promise; } declare namespace lib_plankton.translate { /** @@ -1905,3 +1908,45 @@ declare namespace lib_plankton.http { decode(x: string): type_response; } } +declare namespace lib_plankton.sqlite { + /** + */ + type type_query_raw = { + template: string; + arguments: Record; + }; + /** + */ + type type_connection = { + handle: any; + }; + /** + */ + function get_connection(database_path: string): Promise; + /** + */ + function drop_connection(connection: type_connection): Promise; + /** + * for CREATE TABLE, DROP TABLE, etc. + */ + function query_set(connection: type_connection, query_raw: type_query_raw): Promise; + /** + * for INSERT, UPDATE, DELETE + */ + function query_put(connection: type_connection, query_raw: type_query_raw): Promise<{ + id: (null | number); + affected: (null | number); + }>; + /** + * for SELECT + * + * @todo rather use handle procedure for rows instead of returning an array + */ + function query_get(connection: type_connection, query_raw: type_query_raw): Promise>>; + /** + * for SELECT + * + * @todo rather use handle procedure for rows instead of returning an array + */ + function query_probe(connection: type_connection, query_raw: type_query_raw, handler: ((row: Record) => void)): void; +} diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index d1b81aa..8e6d118 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -4274,7 +4274,7 @@ var lib_plankton; return (new Promise(function (resolve, reject) { nm_fs.readFile(path, { "encoding": "utf8", - "flag": "r", + "flag": "r" }, function (error, content) { if (error == null) { resolve(content); @@ -4293,7 +4293,7 @@ var lib_plankton; var nm_fs = require("fs"); return (new Promise(function (resolve, reject) { nm_fs.readFile(path, { - "flag": "r", + "flag": "r" }, function (error, content) { if (error == null) { resolve(content); @@ -4330,13 +4330,13 @@ var lib_plankton; function write(path, content, options) { if (options === void 0) { options = {}; } options = Object.assign({ - "encoding": "utf-8", + "encoding": "utf-8" }, options); var nm_fs = require("fs"); return (new Promise(function (resolve, reject) { nm_fs.writeFile(path, content, { "encoding": options.encoding, - "flag": "w", + "flag": "w" }, function (error) { if (error == null) { resolve(undefined); @@ -4357,7 +4357,7 @@ var lib_plankton; var nm_fs = require("fs"); return (new Promise(function (resolve, reject) { nm_fs.writeFile(path, content, { - "flag": "w", + "flag": "w" }, function (error) { if (error == null) { resolve(undefined); @@ -4369,6 +4369,17 @@ var lib_plankton; })); } file.write_buffer = write_buffer; + /** + */ + function delete_(path) { + var nm_fs = require("fs"); + return (new Promise(function (resolve, reject) { + nm_fs.unlink(path, function () { + resolve(undefined); + }); + })); + } + file.delete_ = delete_; })(file = lib_plankton.file || (lib_plankton.file = {})); })(lib_plankton || (lib_plankton = {})); /* @@ -4851,8 +4862,8 @@ var lib_plankton; "info": info, "hidden": hidden, "parameters": { - "index": index, - }, + "index": index + } })); }; /** @@ -4870,8 +4881,8 @@ var lib_plankton; "hidden": hidden, "parameters": { "indicators_short": indicators_short, - "indicators_long": indicators_long, - }, + "indicators_long": indicators_long + } })); }; /** @@ -5117,17 +5128,17 @@ var lib_plankton; "symbols": { "delimiter": " ", "prefix": "--", - "assignment": "=", - }, + "assignment": "=" + } }, "url": { "symbols": { "delimiter": "&", "prefix": "", - "assignment": "=", + "assignment": "=" } } - }, + } }; /** * @author fenris @@ -5204,14 +5215,14 @@ var lib_plankton; "pattern_from": pattern_from, "pattern_to": pattern_to, "input": input, - "result": result, + "result": result }); input = result; } } } lib_plankton.log.debug("lib_args:read:current_input", { - "input": input, + "input": input }); } // parsing @@ -5222,18 +5233,18 @@ var lib_plankton; var index_expected_1 = 0; parts.forEach(function (part) { lib_plankton.log.debug("lib_args:read:analyzing", { - "part": part, + "part": part }); var found = [ function () { lib_plankton.log.debug("lib_args:read:probing_as_volatile", { - "part": part, + "part": part }); for (var _i = 0, _a = Object.entries(_this.filter(args.enum_kind.volatile)); _i < _a.length; _i++) { var _b = _a[_i], name = _b[0], argument = _b[1]; lib_plankton.log.debug("lib_args:read:probing_as_volatile:trying", { "part": part, - "argument": argument.toString(), + "argument": argument.toString() }); var pattern = ""; { @@ -5252,12 +5263,12 @@ var lib_plankton; pattern += pattern_back; } lib_plankton.log.debug("lib_args:read:probing_as_volatile:pattern", { - "pattern": pattern, + "pattern": pattern }); var regexp = new RegExp(pattern); var matching = regexp.exec(part); lib_plankton.log.debug("lib_args:read:probing_as_volatile:matching", { - "matching": matching, + "matching": matching }); if (matching == null) { // do nothing @@ -5271,7 +5282,7 @@ var lib_plankton; }, function () { lib_plankton.log.debug("lib_args:read:probing_as_positional", { - "part": part, + "part": part }); var positional = _this.filter(args.enum_kind.positional); for (var _i = 0, _a = Object.entries(positional); _i < _a.length; _i++) { @@ -5282,7 +5293,7 @@ var lib_plankton; else { lib_plankton.log.debug("lib_args:read:probing_as_positional:trying", { "part": part, - "argument": argument.toString(), + "argument": argument.toString() }); var pattern = ""; { @@ -5291,12 +5302,12 @@ var lib_plankton; pattern += pattern_back; } lib_plankton.log.debug("lib_args:read:probing_as_positional:pattern", { - "pattern": pattern, + "pattern": pattern }); var regexp = new RegExp(pattern); var matching = regexp.exec(part); lib_plankton.log.debug("lib_args:read:probing_as_positional:matching", { - "matching": matching, + "matching": matching }); if (matching == null) { return false; @@ -5313,7 +5324,7 @@ var lib_plankton; ].some(function (x) { return x(); }); if (!found) { lib_plankton.log.warning("lib_args:read:could_not_parse", { - "part": part, + "part": part }); } }); @@ -5831,7 +5842,7 @@ var lib_plankton; ((request.query === null) ? "" : request.query)); - // console.info({target,options}); + console.info({ target }); switch (options.implementation) { default: { return Promise.reject("invalid implementation: " + options.implementation); @@ -6019,3 +6030,145 @@ var lib_plankton; http.class_http_response = class_http_response; })(http = lib_plankton.http || (lib_plankton.http = {})); })(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:sqlite«. + +Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:sqlite« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:sqlite« 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:sqlite«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var sqlite; + (function (sqlite) { + /** + */ + function get_connection(database_path) { + return (new Promise((resolve, reject) => { + const nm_sqlite3 = require("sqlite3"); + const handle = new nm_sqlite3.Database(database_path, (nm_sqlite3.OPEN_READWRITE + | + nm_sqlite3.OPEN_CREATE + | + nm_sqlite3.OPEN_FULLMUTEX), (error) => { + if (error) { + reject(error); + } + else { + resolve({ + "handle": handle, + }); + } + }); + })); + } + sqlite.get_connection = get_connection; + /** + */ + function drop_connection(connection) { + return (new Promise((resolve, reject) => { + connection.handle.close(() => { + resolve(undefined); + }); + })); + } + sqlite.drop_connection = drop_connection; + /** + */ + function query_transform(query_raw) { + return { + "template": query_raw.template, + "arguments": Object.fromEntries(Object.entries(query_raw.arguments) + .map(([key, value]) => ([(":" + key), value]))), + }; + } + /** + * for CREATE TABLE, DROP TABLE, etc. + */ + async function query_set(connection, query_raw) { + const query_transformed = query_transform(query_raw); + return (new Promise((resolve, reject) => { + connection.handle.run(query_transformed.template, query_transformed.arguments, (error) => { + if (error) { + reject(error); + } + else { + resolve(undefined); + } + }); + })); + } + sqlite.query_set = query_set; + /** + * for INSERT, UPDATE, DELETE + */ + async function query_put(connection, query_raw) { + const query_transformed = query_transform(query_raw); + return (new Promise((resolve, reject) => { + connection.handle.run(query_transformed.template, query_transformed.arguments, + // must be a classic function due the special use of "this" + function (error) { + if (error) { + reject(error); + } + else { + resolve({ + "id": this.lastID, + "affected": this.changes, + }); + } + }); + })); + } + sqlite.query_put = query_put; + /** + * for SELECT + * + * @todo rather use handle procedure for rows instead of returning an array + */ + async function query_get(connection, query_raw) { + const query_transformed = query_transform(query_raw); + return (new Promise((resolve, reject) => { + connection.handle.all(query_transformed.template, query_transformed.arguments, (error, rows) => { + if (error) { + reject(error); + } + else { + resolve(rows); + } + }); + })); + } + sqlite.query_get = query_get; + /** + * for SELECT + * + * @todo rather use handle procedure for rows instead of returning an array + */ + function query_probe(connection, query_raw, handler) { + const query_transformed = query_transform(query_raw); + connection.handle.each(query_transformed.template, query_transformed.arguments, function (error, row) { + if (error) { + // do nothing + // log? + } + else { + handler(row); + } + }); + } + sqlite.query_probe = query_probe; + })(sqlite = lib_plankton.sqlite || (lib_plankton.sqlite = {})); +})(lib_plankton || (lib_plankton = {})); diff --git a/source/app/cli.ts b/source/app/cli.ts new file mode 100644 index 0000000..cf5f5eb --- /dev/null +++ b/source/app/cli.ts @@ -0,0 +1,288 @@ +/** + */ +async function main( +) : Promise +{ + await _heimdall.init(); + + // consts + const version : string = "0.9"; + + // args + const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler( + { + "order_path": new lib_plankton.args.class_argument( + { + "name": "order_path", + "type": lib_plankton.args.enum_type.string, + "kind": lib_plankton.args.enum_kind.positional, + "mode": lib_plankton.args.enum_mode.replace, + "default": "monitoring.hmdl.json", + "parameters": { + "index": 0, + }, + "info": lib_plankton.translate.get("help.args.order_path"), + } + ), + "show_help": new lib_plankton.args.class_argument( + { + "name": "help", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["help"], + "indicators_short": ["h"], + }, + "info": lib_plankton.translate.get("help.args.show_help"), + } + ), + "show_version": new lib_plankton.args.class_argument( + { + "name": "version", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["version"], + "indicators_short": ["r"], + }, + "info": lib_plankton.translate.get("help.args.show_version"), + } + ), + "show_schema": new lib_plankton.args.class_argument( + { + "name": "schema", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["schema"], + "indicators_short": ["s"], + }, + "info": lib_plankton.translate.get("help.args.show_schema"), + } + ), + "expose_full_order": new lib_plankton.args.class_argument( + { + "name": "expose_full_order", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["expose-full-order"], + "indicators_short": ["e"], + }, + "info": lib_plankton.translate.get("help.args.expose_full_order"), + } + ), + "erase_state": new lib_plankton.args.class_argument( + { + "name": "erase_state", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["erase-state"], + "indicators_short": ["x"], + }, + "info": lib_plankton.translate.get("help.args.erase_state"), + } + ), + "database_path": new lib_plankton.args.class_argument( + { + "name": "database_path", + "type": lib_plankton.args.enum_type.string, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": null, + "parameters": { + "indicators_long": ["database-path"], + "indicators_short": ["d"], + }, + "info": lib_plankton.translate.get("help.args.database_path"), + } + ), + "mutex_path": new lib_plankton.args.class_argument( + { + "name": "mutex_path", + "type": lib_plankton.args.enum_type.string, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": "/tmp/heimdall.lock", + "parameters": { + "indicators_long": ["mutex-path"], + "indicators_short": ["m"], + }, + "info": lib_plankton.translate.get("help.args.mutex_path"), + } + ), + "send_ok_notifications": new lib_plankton.args.class_argument( + { + "name": "send_ok_notifications", + "type": lib_plankton.args.enum_type.boolean, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": false, + "parameters": { + "indicators_long": ["send-ok-notifications"], + "indicators_short": ["y"], + }, + "info": lib_plankton.translate.get( + "help.args.send_ok_notifications", + { + "condition_name": lib_plankton.translate.get("conditions.ok"), + } + ), + } + ), + "language": new lib_plankton.args.class_argument( + { + "name": "language", + "type": lib_plankton.args.enum_type.string, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": null, + "parameters": { + "indicators_long": ["language"], + "indicators_short": ["l"], + }, + "info": lib_plankton.translate.get("help.args.language"), + } + ), + "time_to_live": new lib_plankton.args.class_argument( + { + "name": "time_to_live", + "type": lib_plankton.args.enum_type.float, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": (60 * 60 * 24 * 7), + "parameters": { + "indicators_long": ["time-to-live"], + "indicators_short": ["t"], + }, + "info": lib_plankton.translate.get("help.args.time_to_live"), + } + ), + "verbosity": new lib_plankton.args.class_argument( + { + "name": "verbosity", + "type": lib_plankton.args.enum_type.integer, + "kind": lib_plankton.args.enum_kind.volatile, + "mode": lib_plankton.args.enum_mode.replace, + "default": 1, + "parameters": { + "indicators_long": ["verbosity"], + "indicators_short": ["v"], + }, + "info": lib_plankton.translate.get("help.args.verbosity"), + } + ), + } + ); + const args : Record = arg_handler.read( + lib_plankton.args.enum_environment.cli, + process.argv.slice(2).join(" ") + ); + + // setup logging + lib_plankton.log.conf_push( + [ + new lib_plankton.log.class_channel_minlevel( + new lib_plankton.log.class_channel_stdout(), + [ + lib_plankton.log.enum_level.debug, + lib_plankton.log.enum_level.info, + lib_plankton.log.enum_level.notice, + lib_plankton.log.enum_level.warning, + lib_plankton.log.enum_level.error, + ][Math.min(4, args["verbosity"])] + ), + ] + ); + + // exec + if (args.show_help) { + process.stdout.write( + arg_handler.generate_help( + { + "programname": "heimdall", + "description": lib_plankton.translate.get("help.title"), + "executable": "heimdall", + } + ) + ); + } + else { + if (args["show_version"]) { + process.stdout.write(version + "\n"); + } + else { + if (args["show_schema"]) { + process.stdout.write( + lib_plankton.json.encode( + _heimdall.order.schema_root( + ), + true + ) + + + "\n" + ) + } + else { + const nm_path = require("path"); + const nm_fs = require("fs"); + if (! nm_fs.existsSync(args["order_path"])) { + lib_plankton.log.error( + lib_plankton.translate.get("misc.order_file_not_found"), + { + "path": args["order_path"], + } + ); + } + else { + const order : _heimdall.type_order = await _heimdall.order.load( + nm_path.normalize(args["order_path"]) + ); + if (args["expose_full_order"]) { + process.stdout.write(lib_plankton.json.encode(order, true) + "\n"); + } + else { + await _heimdall.master.wrap_mutex( + args["mutex_path"], + () => _heimdall.master.wrap_database( + args["database_path"], + args["order_path"], + async () => { + await _heimdall.master.clean( + { + "time_to_live": args["time_to_live"], + "erase_state": args["erase_state"], + } + ); + await _heimdall.master.run( + order, + { + "send_ok_notifications": args["send_ok_notifications"], + } + ); + } + ) + ) + .catch( + () => {process.exit(2);} + ); + } + } + } + } + } +} + + +main(); diff --git a/source/logic/base.ts b/source/base.ts similarity index 100% rename from source/logic/base.ts rename to source/base.ts diff --git a/source/logic/helpers/json_schema.ts b/source/helpers/json_schema.ts similarity index 100% rename from source/logic/helpers/json_schema.ts rename to source/helpers/json_schema.ts diff --git a/source/logic/helpers/misc.ts b/source/helpers/misc.ts similarity index 87% rename from source/logic/helpers/misc.ts rename to source/helpers/misc.ts index c629ffa..35e0033 100644 --- a/source/logic/helpers/misc.ts +++ b/source/helpers/misc.ts @@ -1,6 +1,17 @@ namespace _heimdall.helpers.misc { + /** + */ + export function get_env_language( + ) : (null | string) + { + const env_lang : string = process.env["LANG"]; + const locale : string = env_lang.split(".")[0]; + const language : string = locale.split("_")[0]; + return language; + } + /** */ export function format_bytes( diff --git a/source/init.ts b/source/init.ts new file mode 100644 index 0000000..c08cfbd --- /dev/null +++ b/source/init.ts @@ -0,0 +1,49 @@ +namespace _heimdall +{ + + /** + */ + export async function init( + ) : Promise + { + const workdir : string = __dirname; + + // translation + { + const language_order : Array = ["de", "en"]; + await lib_plankton.translate.initialize_promise( + { + "verbosity": 1, + "order": language_order, + "packages": await Promise.all( + [ + {"identifier": "de", "path": (workdir + "/localization/de.json")}, + {"identifier": "en", "path": (workdir + "/localization/en.json")}, + ] + .map( + (entry) => ( + lib_plankton.file.read(entry.path) + .then(content => Promise.resolve(JSON.parse(content))) + .then(tree => Promise.resolve({"meta": {"identifier": entry.identifier}, "tree": tree})) + ) + ) + ), + } + ); + const env_language : (null | string) = _heimdall.helpers.misc.get_env_language(); + if ( + ! ( + (env_language !== null) + && + language_order.includes(env_language) + ) + ) { + // do nothing + } + else { + lib_plankton.translate.promote(env_language); + } + } + } + +} diff --git a/source/localization/de.json b/source/localization/de.json index bf03499..58c8b1a 100644 --- a/source/localization/de.json +++ b/source/localization/de.json @@ -37,5 +37,5 @@ "misc.check_procedure_failed": "Prüfungs-Prozedur fehlgeschlagen", "misc.still_running": "läuft bereits/noch", "misc.cleanup_info": "{{count}} alte Ergebnis-Datensätze gelöscht", - "misc.order_file_not_found": "Auftrags-Datei nicht gefunden: {{path}}" + "misc.order_file_not_found": "Auftrags-Datei nicht gefunden" } diff --git a/source/localization/en.json b/source/localization/en.json index 2020fdc..bfa7680 100644 --- a/source/localization/en.json +++ b/source/localization/en.json @@ -37,5 +37,5 @@ "misc.check_procedure_failed": "check procedure failed", "misc.still_running": "already/still running", "misc.cleanup_info": "removed {{count}} old result entries", - "misc.order_file_not_found": "order file not found: {{path}}" + "misc.order_file_not_found": "order file not found" } diff --git a/source/logic/check_kinds/_abstract.ts b/source/logic/check_kinds/_abstract.ts deleted file mode 100644 index a1e19b5..0000000 --- a/source/logic/check_kinds/_abstract.ts +++ /dev/null @@ -1,12 +0,0 @@ -namespace _heimdall.check_kinds -{ - - /** - */ - export type type_check_kind = { - parameters_schema : (() => _heimdall.helpers.json_schema.type_schema); - normalize_order_node : ((node : any) => any); - run : (parameters) => Promise<_heimdall.type_result>; - }; - -} diff --git a/source/logic/check_kinds/_base.ts b/source/logic/check_kinds/_base.ts new file mode 100644 index 0000000..935aba3 --- /dev/null +++ b/source/logic/check_kinds/_base.ts @@ -0,0 +1,59 @@ +namespace _heimdall.check_kinds +{ + + /** + */ + export type type_check_kind = { + parameters_schema : ( + () + => + _heimdall.helpers.json_schema.type_schema + ); + normalize_order_node : ( + (node : any) + => + any + ); + run : ( + (parameters) + => + Promise<_heimdall.type_result> + ); + }; + + + /** + */ + var _implementations : Record = {}; + + + /** + */ + export function register_implementation( + name : string, + check_kind : type_check_kind + ) : void + { + _implementations[name] = check_kind; + } + + + /** + */ + export function get_implementation( + name : string + ) : type_check_kind + { + return _implementations[name]; + } + + + /** + */ + export function get_implementations( + ) : Record + { + return _implementations; + } + +} diff --git a/source/logic/check_kinds/file_state.ts b/source/logic/check_kinds/file_state.ts index e6764d8..5d6489f 100644 --- a/source/logic/check_kinds/file_state.ts +++ b/source/logic/check_kinds/file_state.ts @@ -366,14 +366,13 @@ namespace _heimdall.check_kinds.file_state /** */ - export function check_kind_implementation( - ) : type_check_kind - { - return { + register_implementation( + "file_state", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "run": run, - }; - } + } + ); } diff --git a/source/logic/check_kinds/generic_remote.ts b/source/logic/check_kinds/generic_remote.ts index 798d7a8..c47f1af 100644 --- a/source/logic/check_kinds/generic_remote.ts +++ b/source/logic/check_kinds/generic_remote.ts @@ -242,14 +242,13 @@ namespace _heimdall.check_kinds.generic_remote /** */ - export function check_kind_implementation( - ) : type_check_kind - { - return { + register_implementation( + "generic_remote", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "run": run, - }; - } + } + ); } diff --git a/source/logic/check_kinds/http_request.ts b/source/logic/check_kinds/http_request.ts index 392b335..9ec0b98 100644 --- a/source/logic/check_kinds/http_request.ts +++ b/source/logic/check_kinds/http_request.ts @@ -380,14 +380,13 @@ namespace _heimdall.check_kinds.http_request /** */ - export function check_kind_implementation( - ) : type_check_kind - { - return { + register_implementation( + "http_request", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "run": run, - }; - } + } + ); } diff --git a/source/logic/check_kinds/script.ts b/source/logic/check_kinds/script.ts index d4a6b32..d51c0e5 100644 --- a/source/logic/check_kinds/script.ts +++ b/source/logic/check_kinds/script.ts @@ -118,14 +118,13 @@ namespace _heimdall.check_kinds.script /** */ - export function check_kind_implementation( - ) : type_check_kind - { - return { + register_implementation( + "script", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "run": run, - }; - } + } + ); } diff --git a/source/logic/check_kinds/tls_certificate.ts b/source/logic/check_kinds/tls_certificate.ts index 2f27637..3da1306 100644 --- a/source/logic/check_kinds/tls_certificate.ts +++ b/source/logic/check_kinds/tls_certificate.ts @@ -237,14 +237,13 @@ namespace _heimdall.check_kinds.tls_certificate /** */ - export function check_kind_implementation( - ) : type_check_kind - { - return { + register_implementation( + "tls_certificate", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "run": run, - }; - } + } + ); } diff --git a/source/logic/helpers/sqlite.ts b/source/logic/helpers/sqlite.ts deleted file mode 100644 index 97d7277..0000000 --- a/source/logic/helpers/sqlite.ts +++ /dev/null @@ -1,176 +0,0 @@ -namespace _heimdall.helpers.sqlite -{ - - /** - */ - export type type_query_raw = { - template : string; - arguments : Record; - }; - - - /** - */ - type type_query_transformed = { - template : string; - arguments : Record; - }; - - - /** - */ - function query_transform( - query_raw : type_query_raw - ) : type_query_transformed - { - return { - "template": query_raw.template, - "arguments": lib_plankton.call.convey( - query_raw.arguments, - [ - x => Object.entries(x), - x => ( - x.map( - ([key, value]) => ([ - (":" + key), - value - ]) - ) - ), - x => Object.fromEntries(x), - ] - ), - }; - } - - - /** - */ - function execute( - database_path : string, - action : ((handle) => Promise), - ) : Promise - { - return ( - new Promise( - async (resolve, reject) => { - const nm_sqlite3 = require("sqlite3"); - const handle = new nm_sqlite3.Database(database_path); - try { - await action(handle); - handle.close(); - resolve(undefined); - } - catch (error) { - handle.close(); - reject(error); - } - } - ) - ); - } - - - /** - * for CREATE TABLE, DROP TABLE, etc. - */ - export async function query_set( - database_path : string, - query_raw : type_query_raw - ) : Promise - { - const query_transformed : type_query_transformed = query_transform(query_raw); - await execute( - database_path, - (handle) => new Promise( - (resolve, reject) => { - handle.run( - query_transformed.template, - query_transformed.arguments, - (error) => { - if (error) { - reject(error); - } - else { - resolve(undefined); - } - } - ); - } - ) - ); - return undefined; - } - - - /** - * for INSERT, UPDATE, DELETE - */ - export async function query_put( - database_path : string, - query_raw : type_query_raw - ) : Promise<{id : (null | int); affected : (null | int);}> - { - let result : {id : (null | int); affected : (null | int);}; - const query_transformed : type_query_transformed = query_transform(query_raw); - await execute( - database_path, - (handle) => new Promise( - (resolve, reject) => { - handle.run( - query_transformed.template, - query_transformed.arguments, - function (error) { - if (error) { - reject(error); - } - else { - result = { - "id": this.lastID, - "affected": this.changes, - }; - resolve(undefined); - } - } - ); - } - ) - ); - return result; - } - - - /** - * for SELECT - */ - export async function query_get( - database_path : string, - query_raw : type_query_raw - ) : Promise>> - { - const query_transformed : type_query_transformed = query_transform(query_raw); - let rows : Array> = []; - await execute( - database_path, - (handle) => new Promise( - (resolve, reject) => { - handle.all( - query_transformed.template, - query_transformed.arguments, - (error, row) => { - if (error) { - reject(error); - } - else { - rows.push(row); - resolve(undefined); - } - } - ); - } - ) - ); - return rows; - } - -} diff --git a/source/logic/main.ts b/source/logic/main.ts deleted file mode 100644 index 8aecafc..0000000 --- a/source/logic/main.ts +++ /dev/null @@ -1,569 +0,0 @@ -async function main( -) : Promise -{ - // consts - const version : string = "0.9"; - const workdir : string = __dirname; - - // init translations - // TODO: use env language - await lib_plankton.translate.initialize_promise( - { - "verbosity": 1, - "order": ["de", "en"], - "packages": await Promise.all( - [ - {"identifier": "de", "path": (workdir + "/localization/de.json")}, - {"identifier": "en", "path": (workdir + "/localization/en.json")}, - ] - .map( - (entry) => ( - lib_plankton.file.read(entry.path) - .then(content => Promise.resolve(JSON.parse(content))) - .then(tree => Promise.resolve({"meta": {"identifier": entry.identifier}, "tree": tree})) - ) - ) - ), - } - ); - - // args - const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler( - { - "order_path": new lib_plankton.args.class_argument( - { - "name": "order_path", - "type": lib_plankton.args.enum_type.string, - "kind": lib_plankton.args.enum_kind.positional, - "mode": lib_plankton.args.enum_mode.replace, - "default": "monitoring.hmdl.json", - "parameters": { - "index": 0, - }, - "info": lib_plankton.translate.get("help.args.order_path"), - } - ), - "show_help": new lib_plankton.args.class_argument( - { - "name": "help", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["help"], - "indicators_short": ["h"], - }, - "info": lib_plankton.translate.get("help.args.show_help"), - } - ), - "show_version": new lib_plankton.args.class_argument( - { - "name": "version", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["version"], - "indicators_short": ["r"], - }, - "info": lib_plankton.translate.get("help.args.show_version"), - } - ), - "show_schema": new lib_plankton.args.class_argument( - { - "name": "schema", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["schema"], - "indicators_short": ["s"], - }, - "info": lib_plankton.translate.get("help.args.show_schema"), - } - ), - "expose_full_order": new lib_plankton.args.class_argument( - { - "name": "expose_full_order", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["expose-full-order"], - "indicators_short": ["e"], - }, - "info": lib_plankton.translate.get("help.args.expose_full_order"), - } - ), - "erase_state": new lib_plankton.args.class_argument( - { - "name": "erase_state", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["erase-state"], - "indicators_short": ["x"], - }, - "info": lib_plankton.translate.get("help.args.erase_state"), - } - ), - "database_path": new lib_plankton.args.class_argument( - { - "name": "database_path", - "type": lib_plankton.args.enum_type.string, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": null, - "parameters": { - "indicators_long": ["database-path"], - "indicators_short": ["d"], - }, - "info": lib_plankton.translate.get("help.args.database_path"), - } - ), - "mutex_path": new lib_plankton.args.class_argument( - { - "name": "mutex_path", - "type": lib_plankton.args.enum_type.string, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": "/tmp/heimdall.lock", - "parameters": { - "indicators_long": ["mutex-path"], - "indicators_short": ["m"], - }, - "info": lib_plankton.translate.get("help.args.mutex_path"), - } - ), - "send_ok_notifications": new lib_plankton.args.class_argument( - { - "name": "send_ok_notifications", - "type": lib_plankton.args.enum_type.boolean, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": false, - "parameters": { - "indicators_long": ["send-ok-notifications"], - "indicators_short": ["y"], - }, - "info": lib_plankton.translate.get( - "help.args.send_ok_notifications", - { - "condition_name": lib_plankton.translate.get("conditions.ok"), - } - ), - } - ), - "language": new lib_plankton.args.class_argument( - { - "name": "language", - "type": lib_plankton.args.enum_type.string, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": null, - "parameters": { - "indicators_long": ["language"], - "indicators_short": ["l"], - }, - "info": lib_plankton.translate.get("help.args.language"), - } - ), - "time_to_live": new lib_plankton.args.class_argument( - { - "name": "time_to_live", - "type": lib_plankton.args.enum_type.float, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": (60 * 60 * 24 * 7), - "parameters": { - "indicators_long": ["time-to-live"], - "indicators_short": ["t"], - }, - "info": lib_plankton.translate.get("help.args.time_to_live"), - } - ), - "verbosity": new lib_plankton.args.class_argument( - { - "name": "verbosity", - "type": lib_plankton.args.enum_type.integer, - "kind": lib_plankton.args.enum_kind.volatile, - "mode": lib_plankton.args.enum_mode.replace, - "default": 1, - "parameters": { - "indicators_long": ["verbosity"], - "indicators_short": ["v"], - }, - "info": lib_plankton.translate.get("help.args.verbosity"), - } - ), - } - ); - const args : Record = arg_handler.read( - lib_plankton.args.enum_environment.cli, - process.argv.slice(2).join(" ") - ); - - // setup logging - lib_plankton.log.conf_push( - [ - new lib_plankton.log.class_channel_minlevel( - new lib_plankton.log.class_channel_stdout(), - [ - lib_plankton.log.enum_level.debug, - lib_plankton.log.enum_level.info, - lib_plankton.log.enum_level.notice, - lib_plankton.log.enum_level.warning, - lib_plankton.log.enum_level.error, - ][Math.min(4, args["verbosity"])] - ), - ] - ); - - // exec - if (args.show_help) { - process.stdout.write( - arg_handler.generate_help( - { - "programname": "heimdall", - "description": lib_plankton.translate.get("help.title"), - "executable": "heimdall", - } - ) - ); - } - else { - if (args["show_version"]) { - process.stdout.write(version + "\n"); - } - else { - const notification_kind_implementations : Record = { - "console": _heimdall.notification_kinds.console.notification_kind_implementation(), - "email": _heimdall.notification_kinds.email.notification_kind_implementation(), - "libnotify": _heimdall.notification_kinds.libnotify.notification_kind_implementation(), - }; - const check_kind_implementations : Record = { - "script": _heimdall.check_kinds.script.check_kind_implementation(), - "http_request": _heimdall.check_kinds.http_request.check_kind_implementation(), - "file_state": _heimdall.check_kinds.file_state.check_kind_implementation(), - "tls_certificate": _heimdall.check_kinds.tls_certificate.check_kind_implementation(), - "generic_remote": _heimdall.check_kinds.generic_remote.check_kind_implementation(), - }; - if (args["show_schema"]) { - process.stdout.write( - lib_plankton.json.encode( - _heimdall.order.schema_root( - check_kind_implementations, - notification_kind_implementations - ), - true - ) - + - "\n" - ) - } - else { - const nm_path = require("path"); - const nm_fs = require("fs"); - if (! nm_fs.existsSync(args["order_path"])) { - process.stderr.write( - lib_plankton.translate.get( - "misc.order_file_not_found", - { - "path": args["order_path"], - } - ) - + - "\n" - ); - } - else { - // get order data - const order = await _heimdall.order.load( - check_kind_implementations, - notification_kind_implementations, - nm_path.normalize(args["order_path"]) - ); - - if (args["expose_full_order"]) { - process.stdout.write(lib_plankton.json.encode(order, true) + "\n"); - } - else { - const database_path : string = ( - (args["database_path"] !== null) - ? args["database_path"] - : nm_path.join( - // TODO get path from fs or path module? - "/tmp", - lib_plankton.string.coin( - "monitoring-state-{{id}}.sqlite", - { - "id": lib_plankton.call.convey( - args["order_path"], - [ - nm_path.normalize, - // encode(ascii), - x => lib_plankton.sha256.get(x), - x => x.slice(0, 8), - ] - ), - } - ) - ) - ); - lib_plankton.log.info( - lib_plankton.translate.get("misc.state_file_path"), - { - "database_path": database_path, - } - ); - - // mutex check - if (nm_fs.existsSync(args["mutex_path"])) { - lib_plankton.log.error( - lib_plankton.translate.get("misc.still_running"), - { - "mutex_path": args["mutex_path"], - } - ); - process.exit(2); - } - else { - await _heimdall.state_repository.setup(database_path); - const clean_count : int = await _heimdall.state_repository.clean( - database_path, - args["time_to_live"], - args["erase_state"] - ); - - // create mutex file - await lib_plankton.file.write(args["mutex_path"], ""); - - for await (const check of order.checks) { - if (! check.active) { - // do nothing - } - else { - let old_item_state : (null | _heimdall.type_item_state); - - const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp( - database_path, - check.name - ); - - const rows : Array< - { - timestamp : int; - condition : _heimdall.enum_condition; - notification_sent : boolean; - } - > = await _heimdall.state_repository.probe( - database_path, - check.name, - (check.threshold + 1), - ); - if (rows.length <= 0) { - old_item_state = null; - } - else { - let count : int = 1; - rows.slice(1).some( - (row) => { - if (row.condition === rows[0].condition) { - count += 1; - return true; - } - else { - return false; - } - } - ); - if (count > check.threshold) { - count = null; - } - else { - // do nothing - } - old_item_state = { - "timestamp": rows[0].timestamp, - "condition": rows[0].condition, - "count": count, - "last_notification_timestamp": last_notification_timestamp, - } - - const timestamp : int = _heimdall.get_current_timestamp(); - const due : boolean = ( - (old_item_state === null) - || - (old_item_state.condition !== _heimdall.enum_condition.ok) - || - ((timestamp - old_item_state.timestamp) >= check.schedule.regular_interval) - || - ( - (! (old_item_state.count === null)) - && - ((timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval) - ) - ); - if (! due) { - // do nothing - } - else { - process.stderr.write( - lib_plankton.string.coin( - "-- {{check_name}}\n", - { - "check_name": check.name, - } - ) - ); - - // execute check and set new state - let result; - try { - result = await check_kind_implementations[check.kind].run(check.parameters); - } - catch (error) { - result = { - "condition": _heimdall.enum_condition.unknown, - "info": { - "cause": lib_plankton.translate.get("misc.check_procedure_failed"), - "error": error.toString(), - }, - } - } - const count : (null | int) = ( - ( - (old_item_state === null) - || - (old_item_state.condition !== result.condition) - ) - ? 1 - : ( - ( - (! (old_item_state.count === null)) - && - ((old_item_state.count + 1) <= check.threshold) - ) - ? (old_item_state.count + 1) - : null - ) - ) - const shall_send_notification : boolean = ( - ( - ( - (! (count === null)) - && - (count == check.threshold) - ) - || - ( - (count === null) - && - check.annoy - ) - || - ( - (count === null) - && - ( - (! (old_item_state === null)) - && - (! (old_item_state.last_notification_timestamp === null)) - && - (! (check.schedule.reminding_interval === null)) - && - ( - (timestamp - old_item_state.last_notification_timestamp) - >= - check.schedule.reminding_interval - ) - ) - ) - ) - && - ( - (result.condition !== _heimdall.enum_condition.ok) - || - args["send_ok_notifications"] - ) - ) - const new_item_state : _heimdall.type_item_state = { - "timestamp": timestamp, - "condition": result.condition, - "count": count, - "last_notification_timestamp": ( - shall_send_notification - ? timestamp - : ( - (old_item_state === null) - ? null - : old_item_state.last_notification_timestamp - ) - ), - } - await _heimdall.state_repository.feed( - database_path, - check.name, - timestamp, - result.condition, - shall_send_notification, - result.info - ); - - // send notifications - if (! shall_send_notification) { - // do nothing - } - else { - check.notifications.forEach( - notification => { - notification_kind_implementations[notification.kind].notify( - notification.parameters, - check.name, - check, - new_item_state, - Object.assign( - ( - (check.custom === null) - ? {} - : {"custom": check.custom} - ), - result.info - ) - ) - } - ); - } - } - } - } - } - - // drop mutex file - await ( - new Promise( - (resolve, reject) => { - nm_fs.unlink( - args["mutex_path"], - () => { - resolve(undefined); - } - ); - } - ) - ); - } - } - } - } - } - } -} - -main(); diff --git a/source/logic/master.ts b/source/logic/master.ts new file mode 100644 index 0000000..6eff637 --- /dev/null +++ b/source/logic/master.ts @@ -0,0 +1,365 @@ +namespace _heimdall.master +{ + + /** + * @todo automated tests + */ + export function determine_due( + check : _heimdall.type_check, + timestamp : int, + old_item_state : (null | _heimdall.type_item_state), + ) : boolean + { + return ( + (old_item_state === null) + || + (old_item_state.condition !== _heimdall.enum_condition.ok) + || + ((timestamp - old_item_state.timestamp) >= check.schedule.regular_interval) + || + ( + (! (old_item_state.count === null)) + && + ((timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval) + ) + ); + } + + + /** + * @todo automated tests + */ + function determine_shall_send_notification( + check : _heimdall.type_check, + send_ok_notifications : boolean, + count : (null | int), + timestamp : int, + old_item_state : (null | _heimdall.type_item_state), + result : _heimdall.type_result + ) : boolean + { + return ( + ( + ( + (! (count === null)) + && + (count == check.threshold) + ) + || + ( + (count === null) + && + check.annoy + ) + || + ( + (count === null) + && + ( + (! (old_item_state === null)) + && + (! (old_item_state.last_notification_timestamp === null)) + && + (! (check.schedule.reminding_interval === null)) + && + ( + (timestamp - old_item_state.last_notification_timestamp) + >= + check.schedule.reminding_interval + ) + ) + ) + ) + && + ( + (result.condition !== _heimdall.enum_condition.ok) + || + send_ok_notifications + ) + ); + } + + + /** + */ + export async function clean( + options : { + time_to_live ?: int; + erase_state ?: boolean; + } = {} + ) : Promise + { + options = Object.assign( + { + "time_to_live": (7 * 24 * 60 * 60), + "erase_state": false, + }, + options + ); + + const count : int = await _heimdall.state_repository.clean( + options.time_to_live, + options.erase_state + ); + lib_plankton.log.info( + lib_plankton.translate.get( + "cleanup_info", + { + "count": count.toFixed(0), + } + ), + { + } + ); + } + + + /** + */ + export async function wrap_database( + database_path_raw : (null | string), + order_path : string, + procedure : (() => Promise) + ) : Promise + { + const nm_path = require("path"); + const database_path : string = ( + (database_path_raw !== null) + ? database_path_raw + : nm_path.join( + // TODO get path from fs or path module? + "/tmp", + lib_plankton.string.coin( + "monitoring-state-{{id}}.sqlite", + { + "id": lib_plankton.call.convey( + order_path, + [ + nm_path.normalize, + // encode(ascii), + x => lib_plankton.sha256.get(x), + x => x.slice(0, 8), + ] + ), + } + ) + ) + ); + lib_plankton.log.info( + lib_plankton.translate.get("misc.state_file_path"), + { + "database_path": database_path, + } + ); + await _heimdall.state_repository.init(database_path); + await procedure(); + await _heimdall.state_repository.kill(); + } + + + /** + * @todo outsource? + */ + export async function wrap_mutex( + mutex_path : string, + procedure : (() => Promise) + ) : Promise + { + const nm_fs = require("fs"); + if (nm_fs.existsSync(mutex_path)) { + lib_plankton.log.error( + lib_plankton.translate.get("misc.still_running"), + { + "mutex_path": mutex_path, + } + ); + return Promise.reject(); + } + else { + await lib_plankton.file.write(mutex_path, ""); + await procedure(); + await lib_plankton.file.delete_(mutex_path); + } + } + + + /** + * central processing procedure + */ + export async function run( + order : _heimdall.type_order, + options : { + send_ok_notifications ?: boolean; + } = {} + ) : Promise + { + options = Object.assign( + { + "send_ok_notifications": false + }, + options + ); + + const notification_kind_implementations : Record = _heimdall.notification_kinds.get_implementations(); + const check_kind_implementations : Record = _heimdall.check_kinds.get_implementations(); + for await (const check of order.checks) { + if (! check.active) { + // do nothing + } + else { + let old_item_state : (null | _heimdall.type_item_state); + + const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp( + check.name + ); + + const rows : Array< + { + timestamp : int; + condition : _heimdall.enum_condition; + notification_sent : boolean; + } + > = await _heimdall.state_repository.probe( + check.name, + (check.threshold + 1), + ); + if (rows.length <= 0) { + old_item_state = null; + } + else { + let count : int = 1; + rows.slice(1).some( + (row) => { + if (row.condition === rows[0].condition) { + count += 1; + return true; + } + else { + return false; + } + } + ); + if (count > check.threshold) { + count = null; + } + else { + // do nothing + } + old_item_state = { + "timestamp": rows[0].timestamp, + "condition": rows[0].condition, + "count": count, + "last_notification_timestamp": last_notification_timestamp, + } + + const timestamp : int = _heimdall.get_current_timestamp(); + const due : boolean = determine_due( + check, + timestamp, + old_item_state + ); + if (! due) { + // do nothing + } + else { + process.stderr.write( + lib_plankton.string.coin( + "-- {{check_name}}\n", + { + "check_name": check.name, + } + ) + ); + + // execute check and set new state + let result : _heimdall.type_result; + try { + result = await check_kind_implementations[check.kind].run(check.parameters); + } + catch (error) { + result = { + "condition": _heimdall.enum_condition.unknown, + "info": { + "cause": lib_plankton.translate.get("misc.check_procedure_failed"), + "error": error.toString(), + }, + }; + } + const count : (null | int) = ( + ( + (old_item_state === null) + || + (old_item_state.condition !== result.condition) + ) + ? 1 + : ( + ( + (! (old_item_state.count === null)) + && + ((old_item_state.count + 1) <= check.threshold) + ) + ? (old_item_state.count + 1) + : null + ) + ); + const shall_send_notification : boolean = determine_shall_send_notification( + check, + options.send_ok_notifications, + count, + timestamp, + old_item_state, + result + ); + const new_item_state : _heimdall.type_item_state = { + "timestamp": timestamp, + "condition": result.condition, + "count": count, + "last_notification_timestamp": ( + shall_send_notification + ? timestamp + : ( + (old_item_state === null) + ? null + : old_item_state.last_notification_timestamp + ) + ), + } + await _heimdall.state_repository.feed( + check.name, + timestamp, + result.condition, + shall_send_notification, + result.info + ); + + // send notifications + if (! shall_send_notification) { + // do nothing + } + else { + check.notifications.forEach( + notification => { + notification_kind_implementations[notification.kind].notify( + notification.parameters, + check.name, + check, + new_item_state, + Object.assign( + ( + (check.custom === null) + ? {} + : {"custom": check.custom} + ), + result.info + ) + ) + } + ); + } + } + } + } + } + } + +} diff --git a/source/logic/notification_kinds/_abstract.ts b/source/logic/notification_kinds/_abstract.ts deleted file mode 100644 index 3c4fae8..0000000 --- a/source/logic/notification_kinds/_abstract.ts +++ /dev/null @@ -1,18 +0,0 @@ -namespace _heimdall.notification_kinds -{ - - /** - */ - export type type_notification_kind = { - parameters_schema : (() => _heimdall.helpers.json_schema.type_schema); - normalize_order_node : ((node : any) => any); - notify : ( - parameters : any, - name : string, - data : type_check, - state : type_item_state, - info : any - ) => Promise; - }; - -} diff --git a/source/logic/notification_kinds/_base.ts b/source/logic/notification_kinds/_base.ts new file mode 100644 index 0000000..5739c71 --- /dev/null +++ b/source/logic/notification_kinds/_base.ts @@ -0,0 +1,65 @@ +namespace _heimdall.notification_kinds +{ + + /** + */ + export type type_notification_kind = { + parameters_schema : ( + () + => + _heimdall.helpers.json_schema.type_schema + ); + normalize_order_node : ( + (node : any) + => + any + ); + notify : ( + ( + parameters : any, + name : string, + data : type_check, + state : type_item_state, + info : any + ) + => + Promise + ); + }; + + + /** + */ + var _implementations : Record = {}; + + + /** + */ + export function register_implementation( + name : string, + notification_kind : type_notification_kind + ) : void + { + _implementations[name] = notification_kind; + } + + + /** + */ + export function get_implementation( + name : string + ) : type_notification_kind + { + return _implementations[name]; + } + + + /** + */ + export function get_implementations( + ) : Record + { + return _implementations; + } + +} diff --git a/source/logic/notification_kinds/console.ts b/source/logic/notification_kinds/console.ts index a026340..aabafb1 100644 --- a/source/logic/notification_kinds/console.ts +++ b/source/logic/notification_kinds/console.ts @@ -57,14 +57,13 @@ namespace _heimdall.notification_kinds.console /** */ - export function notification_kind_implementation( - ) : type_notification_kind - { - return { + register_implementation( + "console", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "notify": notify, - }; - } + } + ); } diff --git a/source/logic/notification_kinds/email.ts b/source/logic/notification_kinds/email.ts index 3d3c635..93bae02 100644 --- a/source/logic/notification_kinds/email.ts +++ b/source/logic/notification_kinds/email.ts @@ -128,14 +128,13 @@ namespace _heimdall.notification_kinds.email /** */ - export function notification_kind_implementation( - ) : type_notification_kind - { - return { + register_implementation( + "email", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "notify": notify, - }; - } + } + ); } diff --git a/source/logic/notification_kinds/libnotify.ts b/source/logic/notification_kinds/libnotify.ts index 01be855..90fdf78 100644 --- a/source/logic/notification_kinds/libnotify.ts +++ b/source/logic/notification_kinds/libnotify.ts @@ -114,14 +114,13 @@ namespace _heimdall.notification_kinds.libnotify /** */ - export function notification_kind_implementation( - ) : type_notification_kind - { - return { + register_implementation( + "libnotify", + { "parameters_schema": parameters_schema, "normalize_order_node": normalize_order_node, "notify": notify, - }; - } + } + ); } diff --git a/source/logic/order.ts b/source/logic/order.ts index dd83c1a..5e1928f 100644 --- a/source/logic/order.ts +++ b/source/logic/order.ts @@ -107,9 +107,9 @@ namespace _heimdall.order /** */ function schema_notifications( - notification_kind_implementations : Record ) : _heimdall.helpers.json_schema.type_schema { + const notification_kind_implementations = _heimdall.notification_kinds.get_implementations(); return { "type": "array", "items": { @@ -149,10 +149,9 @@ namespace _heimdall.order /** */ export function schema_root( - check_kind_implementations : Record, - notification_kind_implementations : Record, ) : _heimdall.helpers.json_schema.type_schema { + const check_kind_implementations = _heimdall.check_kinds.get_implementations(); return { "type": "object", "additionalProperties": false, @@ -166,7 +165,7 @@ namespace _heimdall.order "threshold": schema_threshold(), "annoy": schema_annoy(), "schedule": schema_schedule(), - "notifications": schema_notifications(notification_kind_implementations), + "notifications": schema_notifications(), }, "required": [ ], @@ -198,7 +197,7 @@ namespace _heimdall.order "threshold": schema_threshold(), "annoy": schema_annoy(), "schedule": schema_schedule(), - "notifications": schema_notifications(notification_kind_implementations), + "notifications": schema_notifications(), }, "required": [ "name", @@ -301,10 +300,10 @@ namespace _heimdall.order /** */ function normalize_notification( - notification_kind_implementations : Record, 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"]); } @@ -320,7 +319,6 @@ namespace _heimdall.order /** */ function normalize_defaults( - notification_kind_implementations : Record, node : any ) : type_check_common { @@ -346,7 +344,7 @@ namespace _heimdall.order "schedule": node_["schedule"], "notifications": ( node_["notifications"] - .map(x => normalize_notification(notification_kind_implementations, x)) + .map(x => normalize_notification(x)) ), }; } @@ -355,8 +353,6 @@ namespace _heimdall.order /** */ function normalize_check( - check_kind_implementations : Record, - notification_kind_implementations : Record, defaults : type_check_common, node : { name : string; @@ -364,6 +360,8 @@ namespace _heimdall.order } ) : 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'"); } @@ -410,7 +408,7 @@ namespace _heimdall.order check.notifications = ( node_["notifications"] .map( - x => normalize_notification(notification_kind_implementations, x), + x => normalize_notification(x), ) ); } @@ -427,8 +425,6 @@ namespace _heimdall.order /** */ function normalize_root( - check_kind_implementations : Record, - notification_kind_implementations : Record, node : any, options : { use_implicit_default_values ?: boolean; @@ -440,7 +436,8 @@ namespace _heimdall.order "use_implicit_default_values": true, }, options - ) + ); + let counts : Record = {}; const checks_raw : Array = ( ("checks" in node) @@ -480,7 +477,6 @@ namespace _heimdall.order const defaults : type_check_common = ( options.use_implicit_default_values ? normalize_defaults( - notification_kind_implementations, defaults_raw ) : defaults_raw @@ -497,8 +493,6 @@ namespace _heimdall.order checks_raw .map( node_ => normalize_check( - check_kind_implementations, - notification_kind_implementations, defaults, node_ ) @@ -512,8 +506,6 @@ namespace _heimdall.order /** */ export async function load( - check_kind_implementations : Record, - notification_kind_implementations : Record, path : string, options : { root ?: boolean; @@ -529,7 +521,8 @@ namespace _heimdall.order "already_included": [], }, options - ) + ); + if (options.already_included.includes(path)) { throw new Error("circular dependency detected") } @@ -546,8 +539,6 @@ namespace _heimdall.order for (let index = 0; index < includes.length; index += 1) { const path_ : string = includes[index]; const sub_order : type_order = await load( - check_kind_implementations, - notification_kind_implementations, ( nm_path.isAbsolute(path_) ? path_ @@ -582,8 +573,6 @@ namespace _heimdall.order order_raw.includes = []; } return normalize_root( - check_kind_implementations, - notification_kind_implementations, order_raw, { "use_implicit_default_values": options["root"], diff --git a/source/logic/state_repository.ts b/source/logic/state_repository.ts index 63b502b..2851230 100644 --- a/source/logic/state_repository.ts +++ b/source/logic/state_repository.ts @@ -1,6 +1,11 @@ namespace _heimdall.state_repository { + /** + */ + var _connection : (null | lib_plankton.sqlite.type_connection) = null; + + /** */ function condition_decode( @@ -33,12 +38,32 @@ namespace _heimdall.state_repository /** */ - export async function setup( + export async function init( database_path : string ) : Promise { - const result = await _heimdall.helpers.sqlite.query_set( - database_path, + _connection = await lib_plankton.sqlite.get_connection(database_path); + await setup(); + } + + + /** + */ + export async function kill( + ) : Promise + { + await lib_plankton.sqlite.drop_connection(_connection); + _connection = null; + } + + + /** + */ + export async function setup( + ) : Promise + { + const result = await lib_plankton.sqlite.query_set( + _connection, { "template": "CREATE TABLE IF NOT EXISTS results(check_name TEXT NOT NULL, timestamp INTEGER NOT NULL, condition TEXT NOT NULL, notification_sent BOOLEAN NOT NULL, info TEXT NOT NULL);", "arguments": { @@ -51,13 +76,12 @@ namespace _heimdall.state_repository /** */ export async function clean( - database_path : string, time_to_live : int, erase_state : boolean ) : Promise { - const result = await _heimdall.helpers.sqlite.query_put( - database_path, + const result = await lib_plankton.sqlite.query_put( + _connection, { "template": "DELETE FROM results WHERE ((timestamp < :timestamp_min) OR :erase_state);", "arguments": { @@ -73,7 +97,6 @@ namespace _heimdall.state_repository /** */ export async function probe( - database_path : string, check_name : string, threshold : int ) : Promise< @@ -86,8 +109,8 @@ namespace _heimdall.state_repository > > { - const rows : Array> = await _heimdall.helpers.sqlite.query_get( - database_path, + const rows : Array> = await lib_plankton.sqlite.query_get( + _connection, { "template": "SELECT timestamp, condition, notification_sent FROM results WHERE (check_name = :check_name) ORDER BY timestamp DESC LIMIT :limit;", "arguments": { @@ -100,9 +123,9 @@ namespace _heimdall.state_repository rows .map( row => ({ - "timestamp": row[0], - "condition": condition_decode(row[1]), - "notification_sent": row[2], + "timestamp": row["timestamp"], + "condition": condition_decode(row["condition"]), + "notification_sent": row["notification_sent"], }) ) ); @@ -112,27 +135,25 @@ namespace _heimdall.state_repository /** */ export async function get_last_notification_timestamp( - database_path : string, check_name : string ) : Promise<(null | int)> { - const rows : Array> = await _heimdall.helpers.sqlite.query_get( - database_path, + const rows : Array> = await lib_plankton.sqlite.query_get( + _connection, { - "template": "SELECT MAX(timestamp) FROM results WHERE (check_name = :check_name) AND (notification_sent = TRUE);", + "template": "SELECT MAX(timestamp) AS max_timestamp FROM results WHERE (check_name = :check_name) AND (notification_sent = TRUE);", "arguments": { "check_name": check_name, }, } ); - return rows[0][0]; + return rows[0]["max_timestamp"]; } /** */ export async function feed( - database_path : string, check_name : string, timestamp : int, condition : _heimdall.enum_condition, @@ -140,8 +161,8 @@ namespace _heimdall.state_repository info : any ) : Promise { - await _heimdall.helpers.sqlite.query_put( - database_path, + await lib_plankton.sqlite.query_put( + _connection, { "template": "INSERT INTO results(check_name, timestamp, condition, notification_sent, info) VALUES (:check_name, :timestamp, :condition, :notification_sent, :info);", "arguments": { diff --git a/source/test/test.mocha.ts b/source/test/test.mocha.ts new file mode 100644 index 0000000..188b261 --- /dev/null +++ b/source/test/test.mocha.ts @@ -0,0 +1,102 @@ +const nm_assert = require("assert"); + +declare var describe; +declare var it; + +_heimdall.init(); +describe( + "heimdall", + () => { + function datetimestring_to_timestamp(datetimestring) { + return Math.floor((new Date(datetimestring)).getTime() / 1000); + }; + describe( + "master.determine_due", + () => { + const cases = [ + { + "name": "regular interval hit", + "input": { + "check": { + "active": true, + "threshold": 1, + "annoy": false, + "schedule": { + "regular_interval": 3600, + "attentive_interval": 120, + "reminding_interval": 86400, + }, + "notifications": [], + "name": "test", + "title": "Test", + "kind": "BOGUS", + "parameters": {}, + "custom": null, + }, + "timestamp": "2023-01-15T12:30:00", + "old_item_state": { + "timestamp": "2023-01-15T11:00:00", + "condition": _heimdall.enum_condition.ok, + "count": 0, + "last_notification_timestamp": "2023-01-15T10:00:00" + } + }, + "output": true, + }, + { + "name": "regular interval not hit", + "input": { + "check": { + "active": true, + "threshold": 1, + "annoy": false, + "schedule": { + "regular_interval": 3600, + "attentive_interval": 120, + "reminding_interval": 86400, + }, + "notifications": [], + "name": "test", + "title": "Test", + "kind": "BOGUS", + "parameters": {}, + "custom": null, + }, + "timestamp": "2023-01-15T11:30:00", + "old_item_state": { + "timestamp": "2023-01-15T11:00:00", + "condition": _heimdall.enum_condition.ok, + "count": 0, + "last_notification_timestamp": "2023-01-15T10:00:00" + } + }, + "output": false, + }, + ]; + cases.forEach( + case_ => { + it( + case_.name, + () => { + // execution + const result : boolean = _heimdall.master.determine_due( + case_.input["check"], + datetimestring_to_timestamp(case_.input["timestamp"]), + { + "timestamp": datetimestring_to_timestamp(case_.input["old_item_state"]["timestamp"]), + "condition": case_.input["old_item_state"]["condition"], + "count": case_.input["old_item_state"]["count"], + "last_notification_timestamp": datetimestring_to_timestamp(case_.input["old_item_state"]["last_notification_timestamp"]), + } + ); + + // assertions + nm_assert.equal(result, case_.output); + } + ); + } + ); + } + ); + } +); diff --git a/source/test/test.py b/source/test/test.py deleted file mode 100644 index 218100d..0000000 --- a/source/test/test.py +++ /dev/null @@ -1,105 +0,0 @@ -definitions = [ - { - "name": "lib.string_coin", - "procedure": lambda input_: string_coin(input_["template"], input_["arguments"]), - "cases": [ - { - "input": { - "template": "{{sachen}} sind {{farbe}}", - "arguments": { - "farbe": "rot", - }, - }, - "output": "{{sachen}} sind rot" - }, - { - "input": { - "template": "{{sachen}} sind {{farbe}}", - "arguments": { - }, - }, - "output": "{{sachen}} sind {{farbe}}" - }, - { - "input": { - "template": "{{sachen}} sind {{farbe}}", - "arguments": { - "sachen": "rosen", - "farbe": "rot", - }, - }, - "output": "rosen sind rot" - }, - { - "input": { - "template": "{{sachen}} sind {{farbe}}", - "arguments": { - "sachen": "rosen", - "farbe": "rot", - "ort": "frankreich", - }, - }, - "output": "rosen sind rot" - }, - ], - }, - { - "name": "lib.format_bytes", - "procedure": lambda input_: format_bytes(input_), - "cases": [ - { - "input": 999, - "output": "999 B", - }, - { - "input": 1000, - "output": "1.0 KB", - }, - { - "input": 1000000, - "output": "1.0 MB", - }, - { - "input": 1000000000, - "output": "1.0 GB", - }, - { - "input": 1000000000000, - "output": "1.0 TB", - }, - { - "input": 1000000000000000, - "output": "1.0 PB", - }, - { - "input": 1000000000000000000, - "output": "1000.0 PB", - }, - ], - }, -] - - -for definition in definitions: - fails = 0 - for index in range(len(definition["cases"])): - case = definition["cases"][index] - output_actual = definition["procedure"](case["input"]) - output_expected = case["output"] - passed = (output_actual == output_expected) - fails += (0 if passed else 1) - info = { - "input": case["input"], - "output_expected": output_expected, - "output_actual": output_actual, - } - _sys.stderr.write( - "[%s] %s.%u%s\n" - % ( - ("+" if passed else "x"), - definition["name"], - index, - ("" if passed else (": " + _json.dumps(info))), - ) - ) -_sys.exit(1 if (fails > 0) else 0) diff --git a/todo.md b/todo.md index d97667e..9641e76 100644 --- a/todo.md +++ b/todo.md @@ -1,4 +1,3 @@ -- neu schreiben in TypeScript (und plankton dafür nutzen?) - Benachrichtigungen versenden, wenn ein Zustand sich wieder normalisiert hat (aber vorher über dem Schwellwert oft nicht OK war) - Selbst-Test - Benachrichtigungs-Kanäle: diff --git a/tools/build b/tools/build index a5c44cb..6386005 100755 --- a/tools/build +++ b/tools/build @@ -2,13 +2,24 @@ ## vars -path_prj_from=tools/heimdall.prj.json -path_prj_to=heimdall.prj.json +path_prj_core_from=tools/heimdall-core.prj.json +path_prj_core_to=heimdall-core.prj.json +path_prj_app_from=tools/heimdall-app.prj.json +path_prj_app_to=heimdall-app.prj.json +path_prj_test_from=tools/heimdall-test.prj.json +path_prj_test_to=heimdall-test.prj.json ## exec -cp ${path_prj_from} ${path_prj_to} -koralle --execute ${path_prj_to} +cp ${path_prj_core_from} ${path_prj_core_to} +cp ${path_prj_app_from} ${path_prj_app_to} +cp ${path_prj_test_from} ${path_prj_test_to} + +koralle --execute ${path_prj_app_to} chmod +r build/heimdall -rm ${path_prj_to} +koralle --execute ${path_prj_test_to} + +rm ${path_prj_test_to} +rm ${path_prj_app_to} +rm ${path_prj_core_to} diff --git a/tools/heimdall-app.prj.json b/tools/heimdall-app.prj.json new file mode 100644 index 0000000..876306c --- /dev/null +++ b/tools/heimdall-app.prj.json @@ -0,0 +1,57 @@ +{ + "name": "heimdall-app", + "dependencies": [ + "heimdall-core.prj.json" + ], + "roottask": { + "name": "all", + "type": "group", + "sub": [ + { + "name": "postprocess", + "sub": [ + { + "name": "link", + "sub": [ + { + "name": "compile", + "type": "typescript", + "parameters": { + "inputs": [ + "lib/plankton/plankton.d.ts", + "temp/heimdall-core.d.ts", + "source/app/cli.ts" + ], + "target": "es2020", + "strict": true, + "output": "temp/heimdall-app.raw.js" + } + } + ], + "type": "concat", + "parameters": { + "inputs": [ + "source/misc/head.js", + "lib/plankton/plankton.js", + "temp/heimdall-core.js", + "temp/heimdall-app.raw.js" + ], + "output": "temp/heimdall-app.js" + } + } + ], + "type": "script", + "parameters": { + "path": "tools/postprocess", + "interpreter": null, + "inputs": [ + "temp/heimdall-app.js" + ], + "outputs": [ + "build/heimdall" + ] + } + } + ] + } +} diff --git a/tools/heimdall-core.prj.json b/tools/heimdall-core.prj.json new file mode 100644 index 0000000..e83bb1b --- /dev/null +++ b/tools/heimdall-core.prj.json @@ -0,0 +1,59 @@ +{ + "name": "heimdall-core", + "dependencies": [ + ], + "roottask": { + "name": "all", + "type": "group", + "sub": [ + { + "name": "compile", + "type": "typescript", + "parameters": { + "inputs": [ + "lib/plankton/plankton.d.ts", + "source/base.ts", + "source/helpers/json_schema.ts", + "source/helpers/misc.ts", + "source/logic/state_repository.ts", + "source/logic/notification_kinds/_base.ts", + "source/logic/notification_kinds/console.ts", + "source/logic/notification_kinds/email.ts", + "source/logic/notification_kinds/libnotify.ts", + "source/logic/check_kinds/_base.ts", + "source/logic/check_kinds/script.ts", + "source/logic/check_kinds/http_request.ts", + "source/logic/check_kinds/file_state.ts", + "source/logic/check_kinds/tls_certificate.ts", + "source/logic/check_kinds/generic_remote.ts", + "source/logic/order.ts", + "source/logic/master.ts", + "source/init.ts" + ], + "target": "es2020", + "strict": true, + "output": "temp/heimdall-core.js", + "declaration": "temp/heimdall-core.d.ts" + } + }, + { + "name": "node_modules", + "type": "copy", + "parameters": { + "folder": true, + "input": "lib/node/node_modules/", + "output": "build/node_modules/" + } + }, + { + "name": "localization", + "type": "copy", + "parameters": { + "folder": true, + "input": "source/localization/", + "output": "build/localization/" + } + } + ] + } +} diff --git a/tools/heimdall-test.prj.json b/tools/heimdall-test.prj.json new file mode 100644 index 0000000..edcffca --- /dev/null +++ b/tools/heimdall-test.prj.json @@ -0,0 +1,34 @@ +{ + "name": "heimdall-test", + "dependencies": [ + "heimdall-core.prj.json" + ], + "roottask": { + "name": "link", + "sub": [ + { + "name": "compile", + "type": "typescript", + "parameters": { + "inputs": [ + "lib/plankton/plankton.d.ts", + "temp/heimdall-core.d.ts", + "source/test/test.mocha.ts" + ], + "target": "es2020", + "strict": true, + "output": "temp/heimdall-test.mocha.raw.js" + } + } + ], + "type": "concat", + "parameters": { + "inputs": [ + "lib/plankton/plankton.js", + "temp/heimdall-core.js", + "temp/heimdall-test.mocha.raw.js" + ], + "output": "build/heimdall-test.mocha.js" + } + } +} diff --git a/tools/heimdall.prj.json b/tools/heimdall.prj.json deleted file mode 100644 index 0c954d2..0000000 --- a/tools/heimdall.prj.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "name": "heimdall", - "version": "0.8", - "dependencies": [ - ], - "roottask": { - "name": "logic", - "type": "group", - "sub": [ - { - "name": "postprocess", - "sub": [ - { - "name": "link", - "sub": [ - { - "name": "compile", - "type": "typescript", - "parameters": { - "inputs": [ - "lib/plankton/plankton.d.ts", - "source/logic/base.ts", - "source/logic/helpers/json_schema.ts", - "source/logic/helpers/sqlite.ts", - "source/logic/helpers/misc.ts", - "source/logic/notification_kinds/_abstract.ts", - "source/logic/notification_kinds/console.ts", - "source/logic/notification_kinds/email.ts", - "source/logic/notification_kinds/libnotify.ts", - "source/logic/check_kinds/_abstract.ts", - "source/logic/check_kinds/script.ts", - "source/logic/check_kinds/http_request.ts", - "source/logic/check_kinds/file_state.ts", - "source/logic/check_kinds/tls_certificate.ts", - "source/logic/check_kinds/generic_remote.ts", - "source/logic/state_repository.ts", - "source/logic/order.ts", - "source/logic/main.ts" - ], - "target": "es2020", - "strict": true, - "output": "temp/heimdall-unlinked" - } - } - ], - "type": "concat", - "parameters": { - "inputs": [ - "source/misc/head.js", - "lib/plankton/plankton.js", - "temp/heimdall-unlinked" - ], - "output": "temp/heimdall-raw" - } - } - ], - "type": "script", - "parameters": { - "path": "tools/postprocess", - "interpreter": null, - "inputs": [ - "temp/heimdall-raw" - ], - "outputs": [ - "build/heimdall" - ] - } - }, - { - "active": true, - "name": "node_modules", - "type": "copy", - "parameters": { - "folder": true, - "input": "lib/node/node_modules/", - "output": "build/node_modules/" - } - }, - { - "name": "localization", - "type": "copy", - "parameters": { - "folder": true, - "input": "source/localization/", - "output": "build/localization/" - } - } - ] - } -} diff --git a/tools/update-plankton b/tools/update-plankton index 6b18da0..468a2e2 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -19,6 +19,7 @@ modules="${modules} file" modules="${modules} translate" modules="${modules} args" modules="${modules} http" +modules="${modules} sqlite" ## exec