diff --git a/CHANGELOG.md b/CHANGELOG.md index d046883..0518bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - 💄 — Improve email and gotify notifications - ✨ — Add command to test gotify configuration - ✨ — Add nagios command to use as a Nagios probe +- ✨ — Add Apprise as notification way (#50) ## 0.3.1 diff --git a/argos/commands.py b/argos/commands.py index 31af2b9..ac1309f 100644 --- a/argos/commands.py +++ b/argos/commands.py @@ -678,6 +678,73 @@ async def test_gotify(config, domain, severity): ) +@server.command() +@click.option( + "--config", + default="argos-config.yaml", + help="Path of the configuration file. " + "If ARGOS_YAML_FILE environment variable is set, its value will be used instead.", + envvar="ARGOS_YAML_FILE", + callback=validate_config_access, +) +@click.option("--domain", help="Domain for the notification", default="example.org") +@click.option("--severity", help="Severity", default="CRITICAL") +@click.option( + "--apprise-group", help="Apprise group for the notification", required=True +) +@coroutine +async def test_apprise(config, domain, severity, apprise_group): + """Send a test apprise notification""" + os.environ["ARGOS_YAML_FILE"] = config + + from datetime import datetime + + from argos.logging import set_log_level + from argos.server.alerting import notify_with_apprise + from argos.server.main import read_config + from argos.server.models import Result, Task + + conf = read_config(config) + + if not conf.general.apprise: + click.echo("Apprise notifications are not configured, cannot test", err=True) + sysexit(1) + else: + now = datetime.now() + task = Task( + url=f"https://{domain}", + domain=domain, + check="body-contains", + expected="foo", + frequency=1, + selected_by="test", + selected_at=now, + ) + + result = Result( + submitted_at=now, + status="success", + context={"foo": "bar"}, + task=task, + agent_id="test", + severity="ok", + ) + + class _FalseRequest: + def url_for(*args, **kwargs): + return "/url" + + set_log_level("debug") + notify_with_apprise( + result, + task, + severity=severity, + old_severity="OLD SEVERITY", + group=conf.general.apprise[apprise_group], + request=_FalseRequest(), + ) + + @server.command(short_help="Nagios compatible severities report") @click.option( "--config", diff --git a/argos/config-example.yaml b/argos/config-example.yaml index a84cb89..763367c 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -23,7 +23,10 @@ general: frequency: "1m" # Which way do you want to be warned when a check goes to that severity? # "local" emits a message in the server log - # You’ll need to configure mail and gotify below to be able to use them here. + # You’ll need to configure mail, gotify or apprise below to be able to use + # them here. + # Use "apprise:john", "apprise:team" (with the quotes!) to use apprise + # notification groups. alerts: ok: - local @@ -58,6 +61,17 @@ general: # tokens: # - foo # - bar + # See https://github.com/caronc/apprise#productivity-based-notifications + # for apprise’s URLs syntax. + # You need to surround the URLs with quotes like in the examples below. + # Use "apprise:john", "apprise:team" (with the quotes!) in "alerts" settings. + # apprise: + # john: + # - "mastodon://access_key@hostname/@user" + # - "matrixs://token@hostname:port/?webhook=matrix" + # team: + # - "mmosts://user@hostname/authkey" + # - "nctalks://user:pass@host/RoomId1/RoomId2/RoomIdN" service: secrets: diff --git a/argos/schemas/config.py b/argos/schemas/config.py index f4887ca..520613a 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -168,6 +168,7 @@ class General(BaseModel): alerts: Alert mail: Optional[Mail] = None gotify: Optional[List[GotifyUrl]] = None + apprise: Optional[Dict[str, List[str]]] = None @field_validator("frequency", mode="before") def parse_frequency(cls, value): diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 94ec13e..64e6bc9 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -4,14 +4,24 @@ import smtplib from typing import List from urllib.parse import urlparse +import apprise import httpx from argos.checks.base import Severity from argos.logging import logger from argos.schemas.config import Config, Mail, GotifyUrl -# XXX Implement mail alerts https://framagit.org/framasoft/framaspace/argos/-/issues/15 -# XXX Implement gotify alerts https://framagit.org/framasoft/framaspace/argos/-/issues/16 + +def get_icon_from_severity(severity: str) -> str: + icon = "❌" + if severity == Severity.OK: + icon = "✅" + elif severity == Severity.WARNING: + icon = "⚠️" + elif severity == Severity.UNKNOWN: + icon = "❔" + + return icon def handle_alert(config: Config, result, task, severity, old_severity, request): @@ -39,20 +49,52 @@ def handle_alert(config: Config, result, task, severity, old_severity, request): result, task, severity, old_severity, config.general.gotify, request ) + if config.general.apprise is not None: + for notif_way in getattr(config.general.alerts, severity): + if notif_way.startswith("apprise:"): + group = notif_way[8:] + notify_with_apprise( + result, + task, + severity, + old_severity, + config.general.apprise[group], + request, + ) + + +def notify_with_apprise( + result, task, severity: str, old_severity: str, group: List[str], request +) -> None: + logger.debug("Will send apprise notification") + + apobj = apprise.Apprise() + for channel in group: + apobj.add(channel) + + icon = get_icon_from_severity(severity) + title = f"[Argos] {icon} {urlparse(task.url).netloc}: status {severity}" + msg = f"""\ +URL: {task.url} +Check: {task.check} +Status: {severity} +Time: {result.submitted_at} +Previous status: {old_severity} + +See result on {request.url_for('get_result_view', result_id=result.id)} + +See results of task on {request.url_for('get_task_results_view', task_id=task.id)}#{result.id} +""" + + apobj.notify(title=title, body=msg) + def notify_by_mail( result, task, severity: str, old_severity: str, config: Mail, request ) -> None: logger.debug("Will send mail notification") - icon = "❌" - if severity == Severity.OK: - icon = "✅" - elif severity == Severity.WARNING: - icon = "⚠️" - elif severity == Severity.UNKNOWN: - icon = "❔" - + icon = get_icon_from_severity(severity) msg = f"""\ URL: {task.url} Check: {task.check} @@ -100,17 +142,14 @@ def notify_with_gotify( logger.debug("Will send gotify notification") headers = {"accept": "application/json", "content-type": "application/json"} + icon = get_icon_from_severity(severity) priority = 9 - icon = "❌" if severity == Severity.OK: priority = 1 - icon = "✅" elif severity == Severity.WARNING: priority = 5 - icon = "⚠️" elif severity == Severity.UNKNOWN: priority = 5 - icon = "❔" subject = f"{icon} {urlparse(task.url).netloc}: status {severity}" msg = f"""\ diff --git a/docs/cli.md b/docs/cli.md index 58b4610..cd17663 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -86,6 +86,7 @@ Commands: nagios Nagios compatible severities report reload-config Load or reload tasks’ configuration start Starts the server (use only for testing or development!) + test-apprise Send a test apprise notification test-gotify Send a test gotify notification test-mail Send a test email user User management @@ -542,3 +543,30 @@ Options: + +#### Test the Apprise settings + +You can verify that your Apprise settings are ok by sending a test notification. + + + +```man +Usage: argos server test-apprise [OPTIONS] + + Send a test apprise notification + +Options: + --config TEXT Path of the configuration file. If ARGOS_YAML_FILE + environment variable is set, its value will be used + instead. + --domain TEXT Domain for the notification + --severity TEXT Severity + --apprise-group TEXT Apprise group for the notification [required] + --help Show this message and exit. +``` + + diff --git a/pyproject.toml b/pyproject.toml index 5c94171..dbb231e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ classifiers = [ dependencies = [ "alembic>=1.13.0,<1.14", + "apprise>=1.9.0,<2", "bcrypt>=4.1.3,<5", "click>=8.1,<9", "fastapi>=0.103,<0.104",