From de1f0cc22aa016a0dd5b2f39761314d94cb4a406 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 23 Nov 2023 17:06:56 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20=E2=80=94=20Implement=20mail?= =?UTF-8?q?=20alerts.=20Fix=20#15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/schemas/config.py | 20 ++++++++++- argos/server/alerting.py | 71 ++++++++++++++++++++++++++++++++++++++ argos/server/routes/api.py | 7 ++-- pyproject.toml | 2 +- 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/argos/schemas/config.py b/argos/schemas/config.py index e7a7825..890ff2d 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -4,7 +4,7 @@ For database models, see argos.server.models. """ from typing import Dict, List, Literal, Optional, Tuple -from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator +from pydantic import BaseModel, ConfigDict, HttpUrl, StrictBool, EmailStr, PositiveInt, field_validator from pydantic.functional_validators import BeforeValidator from typing_extensions import Annotated @@ -93,6 +93,23 @@ class Service(BaseModel): secrets: List[str] +class MailAuth(BaseModel): + """Mail authentication configuration""" + login: str + password: str + + +class Mail(BaseModel): + """Mail configuration""" + mailfrom: EmailStr + host: Optional[str] = None + port: Optional[PositiveInt] = None + ssl: Optional[StrictBool] = None + starttls: Optional[StrictBool] = None + auth: Optional[MailAuth] = None + addresses: List[EmailStr] + + class Alert(BaseModel): """List of way to handle alerts, by severity""" @@ -107,6 +124,7 @@ class General(BaseModel): frequency: int alerts: Alert + mail: Optional[Mail] = None @field_validator("frequency", mode="before") def parse_frequency(cls, value): diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 5f5821a..7fc071b 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -1,3 +1,8 @@ +import ssl +import smtplib + +from urllib.parse import urlparse + from argos.logging import logger # XXX Implement mail alerts https://framagit.org/framasoft/framaspace/argos/-/issues/15 @@ -7,3 +12,69 @@ def handle_alert(config, result, task, severity): """Dispatch alert through configured alert channels""" msg = f"task={task.id}, status={result.status}, {severity=}" logger.error("Alerting stub: %s", msg) + + if 'mail' in config and 'addresses' in config['mail']: + logger.debug('Notify of failure by mail') + + msg = f"""\ +Subject: Argos {urlparse(task.url).netloc} status {severity} + +URL: {task.url} +Check: {task.check} +Status: {severity} +Time: {result.submitted_at} +""" + notify_by_mail(msg=msg, config=config['mail']) + + +def notify_by_mail(msg: str, config: dict) -> None: + """Notify by mail + + Keyword argument: + msg -- string, the mail to send (including subject) + config -- dict, configuration of mail system + """ + if 'mailfrom' not in config: + logger.error('No "mailfrom" address in mail config. ' + 'Not sending mail.') + return None + if 'host' not in config: + config['host'] = '127.0.0.1' + if 'port' not in config: + config['port'] = 25 + if 'ssl' not in config: + config['ssl'] = False + if 'starttls' not in config: + config['starttls'] = False + if config['ssl']: + logger.debug('Mail notification: SSL') + context = ssl.create_default_context() + smtp = smtplib.SMTP_SSL(host=config['host'], + port=config['port'], + context=context) + else: + smtp = smtplib.SMTP(host=config['host'], # type: ignore + port=config['port']) + if config['starttls']: + logger.debug('Mail notification: STARTTLS') + context = ssl.create_default_context() + smtp.starttls(context=context) + + if 'auth' in config: + if 'login' not in config['auth'] \ + or 'password' not in config['auth']: + logger.warning('Mail credentials are incomplete. ' + 'No mail authentication can be done. ' + 'Not sending mail.') + return None + + logger.debug('Mail notification: authentification') + smtp.login(config['auth']['login'], + config['auth']['password']) + + for address in config['addresses']: + logger.debug('Sending mail to %s', address) + logger.debug(msg) + smtp.sendmail(config['from'], address, msg) + + return None diff --git a/argos/server/routes/api.py b/argos/server/routes/api.py index 57001c6..034bc19 100644 --- a/argos/server/routes/api.py +++ b/argos/server/routes/api.py @@ -44,19 +44,22 @@ async def create_results( agent_id = agent_id or request.client.host db_results = [] for agent_result in results: - result = await queries.create_result(db, agent_result, agent_id) # XXX Maybe offload this to a queue. # XXX Get all the tasks at once, to limit the queries on the db task = await queries.get_task(db, agent_result.task_id) if not task: logger.error("Unable to find task %i", agent_result.task_id) else: + last_severity = task.severity + result = await queries.create_result(db, agent_result, agent_id) check = task.get_check() status, severity = await check.finalize(config, result, **result.context) result.set_status(status, severity) task.set_times_and_deselect() - handle_alert(config, result, task, severity) + # Don’t create an alert if the severity has not changed + if last_severity != severity: + handle_alert(config, result, task, severity) db_results.append(result) db.commit() diff --git a/pyproject.toml b/pyproject.toml index f4a47ff..67da05e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "click>=8.1,<9", "fastapi>=0.103,<0.104", "httpx>=0.25,<1", - "pydantic>=2.4,<3", + "pydantic[email]>=2.4,<3", "pyyaml>=6.0,<7", "pyyaml-include>=1.3,<2", "sqlalchemy[asyncio]>=2.0,<3", From 2ffea278dd5cee9741457a2885702dffbd97d33a Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Tue, 28 Nov 2023 12:20:49 +0100 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=8E=A8=20=E2=80=94=20Improve=20mail?= =?UTF-8?q?=20alerting=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/schemas/config.py | 8 +++--- argos/server/alerting.py | 56 ++++++++++++---------------------------- 2 files changed, 20 insertions(+), 44 deletions(-) diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 890ff2d..5fe305b 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -102,10 +102,10 @@ class MailAuth(BaseModel): class Mail(BaseModel): """Mail configuration""" mailfrom: EmailStr - host: Optional[str] = None - port: Optional[PositiveInt] = None - ssl: Optional[StrictBool] = None - starttls: Optional[StrictBool] = None + host: str = '127.0.0.1' + port: PositiveInt = 25 + ssl: StrictBool = False + starttls: StrictBool = False auth: Optional[MailAuth] = None addresses: List[EmailStr] diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 7fc071b..ed9c106 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -4,16 +4,17 @@ import smtplib from urllib.parse import urlparse from argos.logging import logger +from argos.schemas.config import Config, Mail # 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 handle_alert(config, result, task, severity): +def handle_alert(config: Config, result, task, severity): """Dispatch alert through configured alert channels""" msg = f"task={task.id}, status={result.status}, {severity=}" logger.error("Alerting stub: %s", msg) - if 'mail' in config and 'addresses' in config['mail']: + if config.general.mail is not None: logger.debug('Notify of failure by mail') msg = f"""\ @@ -24,57 +25,32 @@ Check: {task.check} Status: {severity} Time: {result.submitted_at} """ - notify_by_mail(msg=msg, config=config['mail']) + notify_by_mail(msg, config.general.mail) -def notify_by_mail(msg: str, config: dict) -> None: - """Notify by mail - - Keyword argument: - msg -- string, the mail to send (including subject) - config -- dict, configuration of mail system - """ - if 'mailfrom' not in config: - logger.error('No "mailfrom" address in mail config. ' - 'Not sending mail.') - return None - if 'host' not in config: - config['host'] = '127.0.0.1' - if 'port' not in config: - config['port'] = 25 - if 'ssl' not in config: - config['ssl'] = False - if 'starttls' not in config: - config['starttls'] = False - if config['ssl']: +def notify_by_mail(msg: str, config: Mail) -> None: + if config.ssl: logger.debug('Mail notification: SSL') context = ssl.create_default_context() - smtp = smtplib.SMTP_SSL(host=config['host'], - port=config['port'], + smtp = smtplib.SMTP_SSL(host=config.host, + port=config.port, context=context) else: - smtp = smtplib.SMTP(host=config['host'], # type: ignore - port=config['port']) - if config['starttls']: + smtp = smtplib.SMTP(host=config.host, # type: ignore + port=config.port) + if config.starttls: logger.debug('Mail notification: STARTTLS') context = ssl.create_default_context() smtp.starttls(context=context) - if 'auth' in config: - if 'login' not in config['auth'] \ - or 'password' not in config['auth']: - logger.warning('Mail credentials are incomplete. ' - 'No mail authentication can be done. ' - 'Not sending mail.') - return None - + if config.auth is not None: logger.debug('Mail notification: authentification') - smtp.login(config['auth']['login'], - config['auth']['password']) + smtp.login(config.auth.login, + config.auth.password) - for address in config['addresses']: + for address in config.addresses: logger.debug('Sending mail to %s', address) logger.debug(msg) - smtp.sendmail(config['from'], address, msg) + smtp.sendmail(config.mailfrom, address, msg) return None From abe4f418b7f7069dba451359fcb7ee005ed6aa71 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Tue, 28 Nov 2023 16:10:29 +0100 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20=E2=80=94=20Implement=20gotif?= =?UTF-8?q?y=20alerts.=20Fix=20#16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/schemas/config.py | 6 +++++ argos/server/alerting.py | 51 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/argos/schemas/config.py b/argos/schemas/config.py index e7a7825..a28b771 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -102,11 +102,17 @@ class Alert(BaseModel): unknown: List[str] +class GotifyUrl(BaseModel): + url: HttpUrl + tokens: List[str] + + class General(BaseModel): """Frequency for the checks and alerts""" frequency: int alerts: Alert + gotify: Optional[List[GotifyUrl]] = None @field_validator("frequency", mode="before") def parse_frequency(cls, value): diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 5f5821a..30ac3ba 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -1,9 +1,58 @@ +from typing import List + +from argos.checks.base import Severity from argos.logging import logger +from argos.schemas.config import Config, GotifyUrl + +import httpx +from urllib.parse import urlparse # 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 handle_alert(config, result, task, severity): +def handle_alert(config: Config, result, task, severity): """Dispatch alert through configured alert channels""" msg = f"task={task.id}, status={result.status}, {severity=}" logger.error("Alerting stub: %s", msg) + + if config.general.gotify is not None: + logger.debug('Will send gotify notification') + + subject = f"{urlparse(task.url).netloc}: status {severity}" + msg = f"""\ +URL: {task.url} +Check: {task.check} +Status: {severity} +Time: {result.submitted_at} +""" + + priority = 9 + if severity == Severity.OK: + priority = 1 + elif severity == Severity.WARNING: + priority = 5 + + notify_with_gotify(subject, msg, priority, config.general.gotify) + + +def notify_with_gotify(subject: str, msg: str, priority: int, config: List[GotifyUrl]) -> None: + headers = {'accept': 'application/json', + 'content-type': 'application/json'} + + payload = {'title': subject, + 'message': msg, + 'priority': priority} + + for url in config: + logger.debug('Sending gotify message(s) to %s', url) + for token in url.tokens: + try: + res = httpx.post(f"{url.url}message", + params={'token': token}, + headers=headers, + json=payload) + res.raise_for_status() + except httpx.RequestError as exc: + logger.error('An error occurred while sending a message to %s with token %s', + exc.request.url, + token) From ec97d982311e9a7872871e0e3ebfb62727df34af Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Tue, 28 Nov 2023 15:13:33 +0100 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=8E=A8=20=E2=80=94=20Split=20mail?= =?UTF-8?q?=20in=20subject=20+=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/server/alerting.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/argos/server/alerting.py b/argos/server/alerting.py index ed9c106..e239885 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -15,20 +15,24 @@ def handle_alert(config: Config, result, task, severity): logger.error("Alerting stub: %s", msg) if config.general.mail is not None: - logger.debug('Notify of failure by mail') + logger.debug('Will send mail notification') + subject = f"[Argos] {urlparse(task.url).netloc}: status {severity}" msg = f"""\ -Subject: Argos {urlparse(task.url).netloc} status {severity} - URL: {task.url} Check: {task.check} Status: {severity} Time: {result.submitted_at} """ - notify_by_mail(msg, config.general.mail) + notify_by_mail(subject, msg, config.general.mail) -def notify_by_mail(msg: str, config: Mail) -> None: +def notify_by_mail(subject: str, msg: str, config: Mail) -> None: + mail = f"""\ +Subject: {subject} + +{msg}""" + if config.ssl: logger.debug('Mail notification: SSL') context = ssl.create_default_context() @@ -51,6 +55,6 @@ def notify_by_mail(msg: str, config: Mail) -> None: for address in config.addresses: logger.debug('Sending mail to %s', address) logger.debug(msg) - smtp.sendmail(config.mailfrom, address, msg) + smtp.sendmail(config.mailfrom, address, mail) return None From 7a3a2d3b2b872cfe78364e3b89792d04bf21313f Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 30 Nov 2023 16:49:08 +0100 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=93=9D=20=E2=80=94=20Add=20an=20exa?= =?UTF-8?q?mple=20of=20gotify=20conf=20in=20config-example.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-example.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config-example.yaml b/config-example.yaml index d2afa2a..9143ff5 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -10,6 +10,11 @@ general: - local unknown: - local +# gotify: +# - url: https://example.org +# tokens: +# - foo +# - bar service: secrets: # Secrets can be generated using `openssl rand -base64 32`. From d629b4bd0cc84c64e67a44cbd2b2b8e80838fd10 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 30 Nov 2023 16:51:45 +0100 Subject: [PATCH 06/13] =?UTF-8?q?=F0=9F=93=9D=20=E2=80=94=20Add=20an=20exa?= =?UTF-8?q?mple=20of=20mail=20conf=20in=20config-example.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-example.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config-example.yaml b/config-example.yaml index d2afa2a..4899989 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -10,6 +10,19 @@ general: - local unknown: - local +# mail: +# mailfrom: no-reply@example.org +# host: 127.0.0.1 +# port: 25 +# ssl: False +# starttls: False +# auth: +# login: foo +# password: bar +# addresses: +# - foo@admin.example.org +# - bar@admin.example.org + service: secrets: # Secrets can be generated using `openssl rand -base64 32`. From 67bacc226ac00707dcb9435831f5e747535c080f Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Tue, 5 Dec 2023 09:39:24 +0100 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=9A=A8=20=E2=80=94=20Fix=20pylint?= =?UTF-8?q?=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/server/alerting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 30ac3ba..0294a35 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -1,12 +1,12 @@ from typing import List +from urllib.parse import urlparse + +import httpx from argos.checks.base import Severity from argos.logging import logger from argos.schemas.config import Config, GotifyUrl -import httpx -from urllib.parse import urlparse - # XXX Implement mail alerts https://framagit.org/framasoft/framaspace/argos/-/issues/15 # XXX Implement gotify alerts https://framagit.org/framasoft/framaspace/argos/-/issues/16 From dc49f0bb3e34ba5d997b3b090eee83b6fa2eb7db Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Tue, 5 Dec 2023 09:38:27 +0100 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=9A=A8=20=E2=80=94=20Fix=20pylint?= =?UTF-8?q?=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/schemas/config.py | 10 +++++++++- argos/server/alerting.py | 2 -- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 5fe305b..c0014ab 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -4,7 +4,15 @@ For database models, see argos.server.models. """ from typing import Dict, List, Literal, Optional, Tuple -from pydantic import BaseModel, ConfigDict, HttpUrl, StrictBool, EmailStr, PositiveInt, field_validator +from pydantic import ( + BaseModel, + ConfigDict, + HttpUrl, + StrictBool, + EmailStr, + PositiveInt, + field_validator, +) from pydantic.functional_validators import BeforeValidator from typing_extensions import Annotated diff --git a/argos/server/alerting.py b/argos/server/alerting.py index e239885..3c5c9c2 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -56,5 +56,3 @@ Subject: {subject} logger.debug('Sending mail to %s', address) logger.debug(msg) smtp.sendmail(config.mailfrom, address, mail) - - return None From 39eace61526b9186dea9a1f5239b0682829df0d9 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 7 Dec 2023 07:33:22 +0100 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=92=84=20=E2=80=94=20Add=20link=20t?= =?UTF-8?q?o=20task=20view=20in=20notifications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/server/alerting.py | 8 ++++++-- argos/server/routes/api.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/argos/server/alerting.py b/argos/server/alerting.py index cb9ef62..31d4006 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -13,18 +13,22 @@ 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 handle_alert(config: Config, result, task, severity): +def handle_alert(config: Config, result, task, severity, request): """Dispatch alert through configured alert channels""" msg = f"task={task.id}, status={result.status}, {severity=}" logger.error("Alerting stub: %s", msg) - if config.general.mail is not None or config.general.gotify is not None: + if config.general.mail is not None or \ + config.general.gotify is not None: + subject = f"{urlparse(task.url).netloc}: status {severity}" msg = f"""\ URL: {task.url} Check: {task.check} Status: {severity} Time: {result.submitted_at} + +See results of task on {request.url_for('get_task_results', task_id=task.id)} """ if config.general.mail is not None: diff --git a/argos/server/routes/api.py b/argos/server/routes/api.py index 034bc19..d618616 100644 --- a/argos/server/routes/api.py +++ b/argos/server/routes/api.py @@ -59,7 +59,7 @@ async def create_results( # Don’t create an alert if the severity has not changed if last_severity != severity: - handle_alert(config, result, task, severity) + handle_alert(config, result, task, severity, request) db_results.append(result) db.commit() From 2ab9afabc96d64e6fa604c084bd74af9b18a4521 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 7 Dec 2023 07:39:31 +0100 Subject: [PATCH 10/13] =?UTF-8?q?=E2=9C=A8=20=E2=80=94=20Use=20alerts=20co?= =?UTF-8?q?nfig=20in=20handle=5Falert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/server/alerting.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 31d4006..6a71cf9 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -15,8 +15,12 @@ from argos.schemas.config import Config, Mail, GotifyUrl def handle_alert(config: Config, result, task, severity, request): """Dispatch alert through configured alert channels""" - msg = f"task={task.id}, status={result.status}, {severity=}" - logger.error("Alerting stub: %s", msg) + + if 'local' in getattr(config.general.alerts, severity): + logger.error("Alerting stub: task=%i, status=%s, severity=%s", + task.id, + result.status, + severity) if config.general.mail is not None or \ config.general.gotify is not None: @@ -31,9 +35,11 @@ Time: {result.submitted_at} See results of task on {request.url_for('get_task_results', task_id=task.id)} """ - if config.general.mail is not None: + if config.general.mail is not None and \ + 'mail' in getattr(config.general.alerts, severity): notify_by_mail(subject, msg, config.general.mail) - if config.general.gotify is not None: + if config.general.gotify is not None and \ + 'gotify' in getattr(config.general.alerts, severity): notify_with_gotify(subject, msg, severity, config.general.gotify) From 9ed58220f464ad4b5149a3be6ec81f4ae235d281 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Mon, 11 Dec 2023 13:24:39 +0100 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=8E=A8=20=E2=80=94=20Make=20each=20?= =?UTF-8?q?handler=20build=20its=20own=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argos/server/alerting.py | 47 +++++++++++++++++++------------ argos/server/routes/api.py | 1 + argos/server/templates/index.html | 2 +- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 6a71cf9..52723df 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -22,11 +22,19 @@ def handle_alert(config: Config, result, task, severity, request): result.status, severity) - if config.general.mail is not None or \ - config.general.gotify is not None: + if config.general.mail is not None and \ + 'mail' in getattr(config.general.alerts, severity): + notify_by_mail(result, task, severity, config.general.mail, request) - subject = f"{urlparse(task.url).netloc}: status {severity}" - msg = f"""\ + if config.general.gotify is not None and \ + 'gotify' in getattr(config.general.alerts, severity): + notify_with_gotify(result, task, severity, config.general.gotify, request) + + +def notify_by_mail(result, task, severity, config: Mail, request) -> None: + logger.debug('Will send mail notification') + + msg = f"""\ URL: {task.url} Check: {task.check} Status: {severity} @@ -35,18 +43,8 @@ Time: {result.submitted_at} See results of task on {request.url_for('get_task_results', task_id=task.id)} """ - if config.general.mail is not None and \ - 'mail' in getattr(config.general.alerts, severity): - notify_by_mail(subject, msg, config.general.mail) - if config.general.gotify is not None and \ - 'gotify' in getattr(config.general.alerts, severity): - notify_with_gotify(subject, msg, severity, config.general.gotify) - - -def notify_by_mail(subject: str, msg: str, config: Mail) -> None: - logger.debug('Will send mail notification') mail = f"""\ -Subject: [Argos] {subject} +Subject: [Argos] {urlparse(task.url).netloc}: status {severity} {msg}""" @@ -75,16 +73,29 @@ Subject: [Argos] {subject} smtp.sendmail(config.mailfrom, address, mail) -def notify_with_gotify(subject: str, msg: str, severity: str, config: List[GotifyUrl]) -> None: +def notify_with_gotify(result, task, severity: str, config: List[GotifyUrl], request) -> None: logger.debug('Will send gotify notification') headers = {'accept': 'application/json', 'content-type': 'application/json'} priority = 9 + icon = '❌' if severity == Severity.OK: priority = 1 + icon = '✅' elif severity == Severity.WARNING: priority = 5 + icon = '⚠️' + + subject = f"{icon} {urlparse(task.url).netloc}: status {severity}" + msg = f"""\ +URL: {task.url} +Check: {task.check} +Status: {severity} +Time: {result.submitted_at} + +See results of task on {request.url_for('get_task_results', task_id=task.id)} +""" payload = {'title': subject, 'message': msg, @@ -99,7 +110,7 @@ def notify_with_gotify(subject: str, msg: str, severity: str, config: List[Gotif headers=headers, json=payload) res.raise_for_status() - except httpx.RequestError as exc: + except httpx.RequestError as err: logger.error('An error occurred while sending a message to %s with token %s', - exc.request.url, + err.request.url, token) diff --git a/argos/server/routes/api.py b/argos/server/routes/api.py index d618616..d43e080 100644 --- a/argos/server/routes/api.py +++ b/argos/server/routes/api.py @@ -59,6 +59,7 @@ async def create_results( # Don’t create an alert if the severity has not changed if last_severity != severity: + # XXX Use a job queue or make it async handle_alert(config, result, task, severity, request) db_results.append(result) diff --git a/argos/server/templates/index.html b/argos/server/templates/index.html index 9d347ef..e1b41bf 100644 --- a/argos/server/templates/index.html +++ b/argos/server/templates/index.html @@ -12,7 +12,7 @@ {{ counts_dict['ok'] }}
-
⚠️ Warning
+
⚠️ Warning
{{ counts_dict['warning'] }}
From a4163b5e25ccc5eb83b6651e0463acdb2bc6d38d Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Wed, 13 Dec 2023 16:28:51 +0100 Subject: [PATCH 12/13] =?UTF-8?q?=F0=9F=92=84=20=E2=80=94=20Add=20previous?= =?UTF-8?q?=20status=20in=20notifications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pylintrc | 4 +++- argos/server/alerting.py | 19 ++++++++++++++----- argos/server/routes/api.py | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.pylintrc b/.pylintrc index 041c69d..c5b3cdd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -441,7 +441,9 @@ disable=raw-checker-failed, singleton-comparison, missing-module-docstring, missing-class-docstring, - missing-function-docstring + missing-function-docstring, + too-many-arguments, + too-many-locals, # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/argos/server/alerting.py b/argos/server/alerting.py index 52723df..7c07533 100644 --- a/argos/server/alerting.py +++ b/argos/server/alerting.py @@ -13,7 +13,7 @@ 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 handle_alert(config: Config, result, task, severity, request): +def handle_alert(config: Config, result, task, severity, old_severity, request): """Dispatch alert through configured alert channels""" if 'local' in getattr(config.general.alerts, severity): @@ -24,14 +24,14 @@ def handle_alert(config: Config, result, task, severity, request): if config.general.mail is not None and \ 'mail' in getattr(config.general.alerts, severity): - notify_by_mail(result, task, severity, config.general.mail, request) + notify_by_mail(result, task, severity, old_severity, config.general.mail, request) if config.general.gotify is not None and \ 'gotify' in getattr(config.general.alerts, severity): - notify_with_gotify(result, task, severity, config.general.gotify, request) + notify_with_gotify(result, task, severity, old_severity, config.general.gotify, request) -def notify_by_mail(result, task, severity, config: Mail, request) -> None: +def notify_by_mail(result, task, severity: str, old_severity: str, config: Mail, request) -> None: logger.debug('Will send mail notification') msg = f"""\ @@ -39,6 +39,7 @@ URL: {task.url} Check: {task.check} Status: {severity} Time: {result.submitted_at} +Previous status: {old_severity} See results of task on {request.url_for('get_task_results', task_id=task.id)} """ @@ -73,7 +74,14 @@ Subject: [Argos] {urlparse(task.url).netloc}: status {severity} smtp.sendmail(config.mailfrom, address, mail) -def notify_with_gotify(result, task, severity: str, config: List[GotifyUrl], request) -> None: +def notify_with_gotify( + result, + task, + severity: str, + old_severity: str, + config: List[GotifyUrl], + request +) -> None: logger.debug('Will send gotify notification') headers = {'accept': 'application/json', 'content-type': 'application/json'} @@ -93,6 +101,7 @@ URL: {task.url} Check: {task.check} Status: {severity} Time: {result.submitted_at} +Previous status: {old_severity} See results of task on {request.url_for('get_task_results', task_id=task.id)} """ diff --git a/argos/server/routes/api.py b/argos/server/routes/api.py index d43e080..244f499 100644 --- a/argos/server/routes/api.py +++ b/argos/server/routes/api.py @@ -60,7 +60,7 @@ async def create_results( # Don’t create an alert if the severity has not changed if last_severity != severity: # XXX Use a job queue or make it async - handle_alert(config, result, task, severity, request) + handle_alert(config, result, task, severity, last_severity, request) db_results.append(result) db.commit() From 2f83adcc4f29a53788bb0143bc2044b287d9eb15 Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Thu, 14 Dec 2023 16:33:45 +0100 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=93=9D=20=E2=80=94=20Update=20TODO?= =?UTF-8?q?=20in=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73464d3..6e48daa 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ Internally, a HTTP API is exposed, and a job queue is used to distribute the che - [X] donner un aperçu rapide de l’état de la supervision. - [ ] Use background tasks for alerting (#23) - [ ] Delete outdated tasks from config (#19, !25) -- [ ] Implement alerting tasks (#15, 16, !13) -- [ ] Handles multiple alerting backends (email, sms, gotify) (!13) +- [X] Implement alerting tasks (#15, 16, !13) +- [X] Handles multiple alerting backends (email, sms, gotify) (!13) - [ ] add an "unknown" severity for check errors (!17) - [ ] Add a command to generate new authentication token (#22) - [ ] Add a way to specify the severity of the alerts in the config - [ ] Allow passing a dict to check -- [ ] Un flag de configuration permet d’ajouter automatiquement un job de vérification de redirection 301 de la version HTTP vers HTTPS +- [ ] A configuration flag can automatically add a check of 301 redirection from HTTP to HTTPS ## License