From 0771c87e41ce70f9febd1998be2f490107e44e03 Mon Sep 17 00:00:00 2001 From: Jeltz Date: Thu, 1 Apr 2021 16:17:54 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Dockerfile | 15 ++++++ README.md | 1 + bot.py | 118 ++++++++++++++++++++++++++++++++++++++++++++ config.example.yaml | 7 +++ docker-compose.yml | 12 +++++ 6 files changed, 154 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 bot.py create mode 100644 config.example.yaml create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12b70c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/config.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..91473bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3-alpine +LABEL description="A Matrix bot for Alertmanager" +# Instal gcc to build wheels +RUN apk add --no-cache gcc musl-dev +# Force the stdout and stderr streams to be unbuffered +ENV PYTHONUNBUFFERED 1 +# Install requirements from PyPI +COPY requirements.txt /var/www/alertbot/ +RUN pip install --no-cache-dir -r /var/www/alertbot/requirements.txt +# Copy the all the necessary files +COPY . /var/www/alertbot/ +# Move the rigth directory +WORKDIR /var/www/alertbot +EXPOSE 8000 +ENTRYPOINT python3 /var/www/alertbot/bot.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..111e425 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Alertbot diff --git a/bot.py b/bot.py new file mode 100755 index 0000000..ed5491d --- /dev/null +++ b/bot.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import argparse +import asyncio +import dataclasses +import logging + +import aiohttp.web +import jinja2 +import markdown +import nio +import yaml + + +@dataclasses.dataclass +class Config: + matrix_homeserver: str + matrix_user: str + matrix_password: str + matrix_room: str + webhook_token: str + + +TEMPLATE = ( + "{% if status == 'resolved' %}" + "RÉSOLU " + "{% elif labels.severity == 'critical' %}" + "@room CRITIQUE " + "{% elif labels.severity == 'warning' %}" + "ATTENTION " + "{% endif %}" + "{{ labels.alertname }} " + "{{ labels.instance }}
" + "
{{ annotations.summary }}
" +) + + +async def format_messages(config, src, dest): + env = jinja2.Environment() + template = env.from_string(TEMPLATE) + + while True: + message = await src.get() + + for alert in message["alerts"]: + rendered = template.render(**alert) + await dest.put((rendered)) + + +async def send_notices(config, messages): + client = nio.AsyncClient(config.matrix_homeserver, config.matrix_user) + await client.login(config.matrix_password) + + while True: + formatted = await messages.get() + + await client.room_send( + config.matrix_room, + message_type="m.room.message", + content={ + "msgtype": "m.text", + "format": "org.matrix.custom.html", + "body": formatted, # à corriger + "formatted_body": formatted, + }, + ) + + +async def run_app(config, out, host, port): + async def webhook(request): + tokens = request.query.getall("token", []) + if config.webhook_token not in tokens: + logging.warning("Invalid tokens: %s", tokens) + raise aiohttp.web.HTTPForbidden("Invalid tokens") + message = await request.json() + logging.debug("Incoming message: %s", message) + await out.put(message) + return aiohttp.web.Response() + + app = aiohttp.web.Application() + app.add_routes([aiohttp.web.post("/webhook", webhook)]) + + runner = aiohttp.web.AppRunner(app) + await runner.setup() + await aiohttp.web.TCPSite(runner, host, port).start() + + +async def main(): + logging.basicConfig(level=logging.INFO) + + parser = argparse.ArgumentParser() + parser.add_argument("-c", "--config", default="config.yaml") + parser.add_argument("--host", default="*") + parser.add_argument("--port", type=int, default=8000) + + args = parser.parse_args() + + with open(args.config) as f: + config = yaml.safe_load(f) + + config = Config(**config) + + messages = asyncio.Queue() + formatted = asyncio.Queue() + + logging.info("Started Alertbot on %s:%d", args.host, args.port) + + tasks = ( + asyncio.create_task(run_app(config, messages, args.host, args.port)), + asyncio.create_task(format_messages(config, messages, formatted)), + asyncio.create_task(send_notices(config, formatted)), + ) + + # on propage les exceptions + await asyncio.gather(*tasks, return_exceptions=False) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/config.example.yaml b/config.example.yaml new file mode 100644 index 0000000..acb8f74 --- /dev/null +++ b/config.example.yaml @@ -0,0 +1,7 @@ +--- +matrix_homeserver: https://example.com +matrix_user: "@alertbot:example.com" +matrix_password: "Ch@ngeMe!?" +matrix_room: "!room:example.com" +webhook_token: "MyVerySecretToken" +... diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e471915 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +--- +version: '3' + +services: + alertbot: + image: alertbot + restart: always + volumes: + - ./config.yaml:/var/www/alertbot/config.yaml:ro + ports: + - 8080:8080 +...