From 85c97bcf125a581fa9446161bc0ec9d9d1c0b23a Mon Sep 17 00:00:00 2001 From: Fredrik Eriksson Date: Sun, 2 Aug 2026 07:09:30 +0300 Subject: [PATCH] add cli to list/clear failures and fix if-alerted alerting --- src/sysalert/db.py | 16 ++++++++++++- src/sysalert/util.py | 56 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/sysalert/db.py b/src/sysalert/db.py index 63ca3f8..be659a6 100644 --- a/src/sysalert/db.py +++ b/src/sysalert/db.py @@ -2,7 +2,7 @@ import datetime import os import sqlite3 -def register_success(name, db): +def clear_failure(name, db): cur = db.cursor() cur.execute(''' DELETE FROM alert @@ -86,6 +86,20 @@ def get_failures(name, db): failures.sort(key=lambda x: x['timestamp']) return failures +def get_services(db): + cur = db.cursor() + cur.execute('''SELECT + service.name, COUNT(*) + FROM service + JOIN failure ON + failure.service=service.id + GROUP BY + service.name; + ''') + db.commit() + ret = {name: failures for (name, failures) in cur.fetchall()} + return ret + def init(path): con = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) diff --git a/src/sysalert/util.py b/src/sysalert/util.py index c7fdcc1..0835cee 100644 --- a/src/sysalert/util.py +++ b/src/sysalert/util.py @@ -1,4 +1,4 @@ - +import argparse import configparser import datetime import importlib @@ -37,11 +37,12 @@ def register_exit(config, db): if os.environ['MONITOR_SERVICE_RESULT'] == 'success': # exit with success status failures = sysalert.db.get_failures(service_name, db) - sysalert.db.register_success(service_name, db) + sysalert.db.clear_failure(service_name, db) try: do_alert = config.getboolean(section_name, 'recovery_alert') except ValueError: - if config.get(section_name, 'recovery_alert') == 'if-alerted' and failures: + if config.get(section_name, 'recovery_alert') == 'if-alerted' and \ + any([f['alert_method'] for f in failures]): do_alert = True else: do_alert = False @@ -75,6 +76,27 @@ def register_exit(config, db): alert.failure(service_name, failures, alert_config) return 0 +def cli_args(): + parser = argparse.ArgumentParser( + description='Manage active sysalerts', + epilog='''Note that when called from systemd this argument parser +is not used. Use the provided sysalert-* services and see +documentation for details.''') + parser.add_argument('--service', '-s', + help='Only operate on specific service') + subparsers = parser.add_subparsers() + + parser_list = subparsers.add_parser('list') + parser_list.set_defaults(cmd='list') + + parser_clear = subparsers.add_parser('clear') + parser_clear.set_defaults(cmd='clear') + parser_clear.add_argument('--force', '-f', + action='store_true', + help='''Force clear for all services if service is + not specified''') + + return parser.parse_args() def cli(): config = configparser.ConfigParser() @@ -85,9 +107,37 @@ def cli(): if _test_env(): # invoked by systemd ret = register_exit(config, db) + else: + args = cli_args() + if args.service and not args.service.endswith('.service'): + args.service = f'{args.service}.service' + + if args.cmd == 'list': + cli_list(args, db) + elif args.cmd == 'clear': + cli_clear(args, db) sysalert.db.close(db) return ret +def cli_list(args, db): + for service, failures in sysalert.db.get_services(db).items(): + if args.service and args.service == service: + print(f'{service}: {failures} failures') + return 0 + +def cli_clear(args, db): + if not args.service and not args.force: + print(f'No service specified, use "--force" to clear all services', sys.stderr) + return 1 + if args.service: + sysalert.db.clear_failure(args.service, db) + print(f'Cleared failures for {args.service}') + else: + for service, failures in sysalert.db.get_services(db).items(): + sysalert.db.clear_failure(service, db) + print(f'Cleared all failures') + return 0 + if __name__ == '__main__': sys.exit(cli())