diff --git a/source/localization/en.json b/source/localization/en.json index fa43aa6..73698f4 100644 --- a/source/localization/en.json +++ b/source/localization/en.json @@ -28,7 +28,7 @@ "checks.http_request.request_failed": "HTTP request failed", "checks.http_request.status_code_mismatch": "actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}", "checks.http_request.header_missing": "header '{{key}}' is unset and hence does not match the expected value '{{value_expected}}'", - "checks.http_request.header_value_mismatch": "actual header value for key '{{key}}' '{{value_actual}}' and does not match the expected value {{value_expected}}", + "checks.http_request.header_value_mismatch": "actual header value for key '{{key}}' '{{value_actual}}' does not match the expected value {{value_expected}}", "checks.http_request.body_misses_part": "body does not contain the expected part '{{part}}'", "misc.state_file_path": "state file path", "misc.check_procedure_failed": "check procedure failed", diff --git a/source/logic.old/main.py b/source/logic.old/main.py index 5f7c354..1f89705 100644 --- a/source/logic.old/main.py +++ b/source/logic.old/main.py @@ -1,7 +1,4 @@ def main(): - ## const - version = "0.8" - ## setup translation for the first time translation_initialize("en", env_get_language()) @@ -11,7 +8,6 @@ def main(): formatter_class = _argparse.ArgumentDefaultsHelpFormatter ) argumentparser.add_argument( - nargs = "?", type = str, default = "monitoring.hmdl.json", dest = "order_path", @@ -19,12 +15,12 @@ def main(): help = translation_get("help.args.order_path"), ) argumentparser.add_argument( - "-v", - "--version", + "-x", + "--erase-state", action = "store_true", default = False, - dest = "show_version", - help = translation_get("help.args.show_version"), + dest = "erase_state", + help = translation_get("help.args.erase_state"), ) argumentparser.add_argument( "-s", @@ -42,14 +38,6 @@ def main(): dest = "expose_full_order", help = translation_get("help.args.expose_full_order"), ) - argumentparser.add_argument( - "-x", - "--erase-state", - action = "store_true", - default = False, - dest = "erase_state", - help = translation_get("help.args.erase_state"), - ) ### v conf stuff v argumentparser.add_argument( "-d", @@ -98,324 +86,302 @@ def main(): ) args = argumentparser.parse_args() + ## vars + id_ = _hashlib.sha256(_os.path.abspath(args.order_path).encode("ascii")).hexdigest()[:8] + database_path = ( + args.database_path + if (args.database_path is not None) else + _os.path.join( + _tempfile.gettempdir(), + string_coin("monitoring-state-{{id}}.sqlite", {"id": id_}) + ) + ) + ## exec - if (args.show_version): - _sys.stdout.write(version + "\n") - else: - ### setup translation for the second time - if (args.language is not None): - translation_initialize("en", args.language) - - ### load check kind implementations - check_kind_implementations = { - "script": implementation_check_kind_script(), - "file_state": implementation_check_kind_file_state(), - "tls_certificate": implementation_check_kind_tls_certificate(), - "http_request": implementation_check_kind_http_request(), - "generic_remote" : implementation_check_kind_generic_remote(), - } - - ### load notification channel implementations - notification_channel_implementations = { - "console": implementation_notification_channel_console(), - "email": implementation_notification_channel_email(), - "libnotify": implementation_notification_channel_libnotify(), - } - - if (args.show_schema): - _sys.stdout.write( - _json.dumps( - order_schema_root( - check_kind_implementations, - notification_channel_implementations - ), - indent = "\t" - ) - + - "\n" + + ### setup translation for the second time + if (args.language is not None): + translation_initialize("en", args.language) + + ### load check kind implementations + check_kind_implementations = { + "script": implementation_check_kind_script(), + "file_state": implementation_check_kind_file_state(), + "tls_certificate": implementation_check_kind_tls_certificate(), + "http_request": implementation_check_kind_http_request(), + "generic_remote" : implementation_check_kind_generic_remote(), + } + + ### load notification channel implementations + notification_channel_implementations = { + "console": implementation_notification_channel_console(), + "email": implementation_notification_channel_email(), + "libnotify": implementation_notification_channel_libnotify(), + } + + if (args.show_schema): + _sys.stdout.write( + _json.dumps( + order_schema_root( + check_kind_implementations, + notification_channel_implementations + ), + indent = "\t" ) + + + "\n" + ) + else: + ### get order data + order = order_load( + check_kind_implementations, + notification_channel_implementations, + _os.path.abspath(args.order_path) + ) + + if (args.expose_full_order): + _sys.stdout.write(_json.dumps(order, indent = "\t") + "\n") + _sys.exit(1) else: - ### vars - if (not _os.path.exists(args.order_path)): + _sys.stderr.write( + string_coin( + "[info] {{label}}: {{path}}\n", + { + "label": translation_get("misc.state_file_path"), + "path": database_path, + } + ) + ) + + ### mutex check + if (_os.path.exists(args.mutex_path)): _sys.stderr.write( string_coin( - "{{message}}\n", + "[error] {{message}} ({{path}})\n", { - "message": translation_get( - "misc.order_file_not_found", + "message": translation_get("misc.still_running"), + "path": args.mutex_path, + } + ) + ) + _sys.exit(2) + else: + ### setup database + sqlite_query_set( + database_path, + "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);", + {} + ) + + ### clean database + result = sqlite_query_put( + database_path, + "DELETE FROM results WHERE ((timestamp < :timestamp_min) OR :erase_state);", + { + "timestamp_min": (get_current_timestamp() - args.time_to_live), + "erase_state": args.erase_state, + } + ) + _sys.stderr.write( + string_coin( + "[info] {{text}}\n", + { + "text": translation_get( + "misc.cleanup_info", { - "path": args.order_path, + "count": ("%u" % result.rowcount), } ), } ) ) - _sys.exit(1) - else: - database_path = ( - args.database_path - if (args.database_path is not None) else - _os.path.join( - _tempfile.gettempdir(), - string_coin( - "monitoring-state-{{id}}.sqlite", - { - "id": _hashlib.sha256(_os.path.abspath(args.order_path).encode("ascii")).hexdigest()[:8] - } - ) - ) - ) - ### get order data - order = order_load( - check_kind_implementations, - notification_channel_implementations, - _os.path.abspath(args.order_path) - ) + file_write(args.mutex_path, "", {"append": True}) - if (args.expose_full_order): - _sys.stdout.write(_json.dumps(order, indent = "\t") + "\n") - _sys.exit(1) - else: - _sys.stderr.write( - string_coin( - "[info] {{label}}: {{path}}\n", - { - "label": translation_get("misc.state_file_path"), - "path": database_path, - } - ) - ) - - ### mutex check - if (_os.path.exists(args.mutex_path)): - _sys.stderr.write( - string_coin( - "[error] {{message}} ({{path}})\n", - { - "message": translation_get("misc.still_running"), - "path": args.mutex_path, - } - ) - ) - _sys.exit(2) + ### iterate through checks + for check_data in order["checks"]: + if (not check_data["active"]): + pass else: - ### setup database - sqlite_query_set( + ### get old state and examine whether the check shall be executed + rows1 = sqlite_query_get( database_path, - "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);", - {} - ) - - ### clean database - result = sqlite_query_put( - database_path, - "DELETE FROM results WHERE ((timestamp < :timestamp_min) OR :erase_state);", + "SELECT MAX(timestamp) FROM results WHERE (check_name = :check_name) AND (notification_sent = TRUE);", { - "timestamp_min": (get_current_timestamp() - args.time_to_live), - "erase_state": args.erase_state, + "check_name": check_data["name"], } ) - _sys.stderr.write( - string_coin( - "[info] {{text}}\n", - { - "text": translation_get( - "misc.cleanup_info", - { - "count": ("%u" % result.rowcount), - } - ), - } + rows2 = sqlite_query_get( + database_path, + "SELECT timestamp, condition, notification_sent FROM results WHERE (check_name = :check_name) ORDER BY timestamp DESC LIMIT :limit;", + { + "check_name": check_data["name"], + "limit": (check_data["threshold"] + 1), + } + ) + if (len(rows2) <= 0): + old_item_state = None + else: + count = 1 + for row in rows2[1:]: + if (row[1] == rows2[0][1]): + count += 1 + else: + break + if (count > check_data["threshold"]): + count = None + else: + pass + old_item_state = { + "timestamp": rows2[0][0], + "condition": condition_decode(rows2[0][1]), + "count": count, + "last_notification_timestamp": rows1[0][0], + } + + timestamp = get_current_timestamp() + due = ( + (old_item_state is None) + or + (old_item_state["condition"] != enum_condition.ok) + or + ((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"]) + or + ( + (old_item_state["count"] is not None) + and + ((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"]) ) ) - - file_write(args.mutex_path, "", {"append": True}) - - ### iterate through checks - for check_data in order["checks"]: - if (not check_data["active"]): - pass - else: - ### get old state and examine whether the check shall be executed - rows = sqlite_query_get( - database_path, - "SELECT timestamp, condition, notification_sent FROM results WHERE (check_name = :check_name) ORDER BY timestamp DESC LIMIT :limit;", - { - "check_name": check_data["name"], - "limit": (check_data["threshold"] + 1), - } - ) - if (len(rows) <= 0): - old_item_state = None - else: - last_notification_timestamp = None - count = 1 - for row in rows[1:]: - if (row[1] == rows[0][1]): - count += 1 - else: - break - if (count > check_data["threshold"]): - count = None - else: - pass - for row in rows: - if (row[2]): - last_notification_timestamp = row[0] - break - else: - pass - old_item_state = { - "timestamp": rows[0][0], - "condition": condition_decode(rows[0][1]), - "count": count, - "last_notification_timestamp": last_notification_timestamp, - } - - timestamp = get_current_timestamp() - due = ( - (old_item_state is None) - or - (old_item_state["condition"] != enum_condition.ok) - or - ((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"]) - or - ( - (old_item_state["count"] is not None) - and - ((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"]) - ) - ) - if (not due): - pass - else: - _sys.stderr.write( - string_coin( - "-- {{check_name}}\n", - { - "check_name": check_data["name"], - } - ) - ) - - ### execute check and set new state - try: - result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"]) - except Exception as error: - result = { - "condition": enum_condition.unknown, - "info": { - "cause": translation_get("misc.check_procedure_failed"), - "error": str(error), - }, - } - count = ( - 1 - if ( - (old_item_state is None) - or - (old_item_state["condition"] != result["condition"]) - ) else - ( - (old_item_state["count"] + 1) - if ( - (old_item_state["count"] is not None) - and - ((old_item_state["count"] + 1) <= check_data["threshold"]) - ) else - None - ) - ) - shall_send_notification = ( - ( - ( - (count is not None) - and - (count == check_data["threshold"]) - ) - or - ( - (count is None) - and - check_data["annoy"] - ) - or - ( - (count is None) - and - ( - (old_item_state is not None) - and - (old_item_state["last_notification_timestamp"] is not None) - and - (check_data["schedule"]["reminding_interval"] is not None) - and - ( - (timestamp - old_item_state["last_notification_timestamp"]) - >= - check_data["schedule"]["reminding_interval"] - ) - ) - ) - ) - and - ( - (result["condition"] != enum_condition.ok) - or - args.send_ok_notifications - ) - ) - new_item_state = { - "timestamp": timestamp, - "condition": result["condition"], - "count": count, - "last_notification_timestamp": ( - timestamp - if shall_send_notification else - ( - None - if (old_item_state is None) else - old_item_state["last_notification_timestamp"] - ) - ), - } - sqlite_query_put( - database_path, - "INSERT INTO results(check_name, timestamp, condition, notification_sent, info) VALUES (:check_name, :timestamp, :condition, :notification_sent, :info);", - { - "check_name": check_data["name"], - "timestamp": timestamp, - "condition": condition_encode(result["condition"]), - "notification_sent": shall_send_notification, - "info": _json.dumps(result["info"]), - } - ) - - ### send notifications - if (not shall_send_notification): - pass - else: - for notification in check_data["notifications"]: - notification_channel_implementations[notification["kind"]].notify( - notification["parameters"], - check_data["name"], - check_data, - new_item_state, - dict_merge( - ( - {} - if (check_data["custom"] is None) else - {"custom": check_data["custom"]} - ), - result["info"] - ) - ) - - if (not _os.exists(args.mutex_path)): + if (not due): pass else: - _os.remove(args.mutex_path) + _sys.stderr.write( + string_coin( + "-- {{check_name}}\n", + { + "check_name": check_data["name"], + } + ) + ) + + ### execute check and set new state + try: + result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"]) + except Exception as error: + result = { + "condition": enum_condition.unknown, + "info": { + "cause": translation_get("misc.check_procedure_failed"), + "error": str(error), + }, + } + count = ( + 1 + if ( + (old_item_state is None) + or + (old_item_state["condition"] != result["condition"]) + ) else + ( + (old_item_state["count"] + 1) + if ( + (old_item_state["count"] is not None) + and + ((old_item_state["count"] + 1) <= check_data["threshold"]) + ) else + None + ) + ) + shall_send_notification = ( + ( + ( + (count is not None) + and + (count == check_data["threshold"]) + ) + or + ( + (count is None) + and + check_data["annoy"] + ) + or + ( + (count is None) + and + ( + (old_item_state is not None) + and + (old_item_state["last_notification_timestamp"] is not None) + and + (check_data["schedule"]["reminding_interval"] is not None) + and + ( + (timestamp - old_item_state["last_notification_timestamp"]) + >= + check_data["schedule"]["reminding_interval"] + ) + ) + ) + ) + and + ( + (result["condition"] != enum_condition.ok) + or + args.send_ok_notifications + ) + ) + new_item_state = { + "timestamp": timestamp, + "condition": result["condition"], + "count": count, + "last_notification_timestamp": ( + timestamp + if shall_send_notification else + ( + None + if (old_item_state is None) else + old_item_state["last_notification_timestamp"] + ) + ), + } + sqlite_query_put( + database_path, + "INSERT INTO results(check_name, timestamp, condition, notification_sent, info) VALUES (:check_name, :timestamp, :condition, :notification_sent, :info);", + { + "check_name": check_data["name"], + "timestamp": timestamp, + "condition": condition_encode(result["condition"]), + "notification_sent": shall_send_notification, + "info": _json.dumps(result["info"]), + } + ) + + ### send notifications + if (not shall_send_notification): + pass + else: + for notification in check_data["notifications"]: + notification_channel_implementations[notification["kind"]].notify( + notification["parameters"], + check_data["name"], + check_data, + new_item_state, + dict_merge( + ( + {} + if (check_data["custom"] is None) else + {"custom": check_data["custom"]} + ), + result["info"] + ) + ) + + if (not _os.path.exists(args.mutex_path)): + pass + else: + _os.remove(args.mutex_path) main()