Compare commits

...

7 Commits

3
.gitignore vendored

@ -138,3 +138,6 @@ dmypy.json
# Cython debug symbols
cython_debug/
# Project specific:
.sync_token
config.yaml

@ -1,5 +1,3 @@
# Kassandra
# Kassandra
Kassandra is a matrix bot receiving alert from [alermanager](https://www.prometheus.io/docs/alerting/latest/alertmanager/) end send them to a [matrix](matrix.org/) room.
Kassandra is a matrix bot receiving alert from [alermanager](https://www.prometheus.io/docs/alerting/latest/alertmanager/) end send them to a [matrix](matrix.org/) room.

@ -0,0 +1,10 @@
---
username: kassandra
homeserver: https://matrix.org
password: beware of greeks bearing gifts
port: 8000
host: 127.0.0.1
alert_rooms:
- "#troy:matrix.org"
- "#ithaca:matrix.org"
...

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=42.0", "wheel"]
build-backend = "setuptools.build_meta"

@ -0,0 +1,19 @@
[metadata]
name = kassandra
author = Jean-Marie 'Histausse' Mineau
author_email = histausse@protonmail
description = A bot sending alert from alertmanager to matrix
long_description = file:README.md
long_description_content_type = text/markdown
url = https://gitea.auro.re/histausse/kassandra
[options]
packages = kassandra
python_requires = >=3.9.2
package_dir = =src
install_requires =
PyYAML>=5.4.1
matrix-bot @ git+https://gitea.auro.re/histausse/matrix-bot.git
[options.package_data]
matrix_bot = py.typed

@ -0,0 +1,5 @@
from setuptools import setup
if __name__ == "__main__":
setup()

@ -0,0 +1,43 @@
import argparse
import asyncio
from .config import load_config
from .bot import send_messages
from .format import format_alerts
from .webhook import run_webhook
async def main():
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config", default="config.yaml")
args = parser.parse_args()
config = load_config(args.config)
alert_queue = asyncio.Queue()
message_queue = asyncio.Queue()
bot_corout = send_messages(
message_queue,
config.username,
config.homeserver,
config.password,
config.alert_rooms
)
format_corout = format_alerts(
alert_queue,
message_queue
)
webhook_corout = run_webhook(
alert_queue,
config.host,
config.port
)
await asyncio.gather(
bot_corout,
format_corout,
webhook_corout
)
if __name__ == "__main__":
asyncio.run(main())

@ -0,0 +1,57 @@
"""
The bot that send messages.
"""
import asyncio
from typing import (
Any,
NoReturn
)
from matrix_bot.client import Client
from matrix_bot.invite_policy import WhiteList
from .format import Message
async def __send_messsages(
message_queue: asyncio.Queue[Message],
bot: Client,
rooms: list[str]
)->NoReturn:
"""
Actually send the messages from the queue.
"""
while True:
message = await message_queue.get()
for room in rooms:
await bot.send_formated_message(
room,
message.formated_body,
unformated_message=message.body
)
message_queue.task_done()
async def send_messages(
message_queue: asyncio.Queue[dict[str, Any]], # For now, type will change in the futur
username: str,
homeserver: str,
password: str,
alert_rooms: list[str]
):
"""
Initialize the bot and send messages added to the queue to the alert_rooms.
"""
bot = await Client(
username,
homeserver,
password
)
invite_policy = await WhiteList(bot, alert_rooms)
bot.set_invite_policy(invite_policy)
await asyncio.gather(
bot.run(),
__send_messsages(
message_queue,
bot,
alert_rooms
)
)

@ -0,0 +1,24 @@
"""
Config related tools.
"""
import dataclasses
import yaml
@dataclasses.dataclass
class Config:
username: str
homeserver: str
password: str
port: int
host: str
alert_rooms: list[str]
def load_config(file:str)->Config:
"""
Load the config from the config file.
"""
with open(file, 'r') as f:
data = yaml.load(f, Loader=yaml.loader.SafeLoader)
return Config(**data)

@ -0,0 +1,41 @@
"""
Format the alert message.
"""
import asyncio
import dataclasses
import json
from typing import (
Any,
NoReturn,
Optional
)
@dataclasses.dataclass
class Message:
body: str
formated_body: Optional[str]
def format_alert(
alert: dict[str, Any]
)->Message:
"""
Format an alert in json format to a nice string.
"""
body = json.dumps(alert, indent=4)
formated_body = f"<pre><code>{body}</code></pre>\n"
return Message(body, formated_body)
async def format_alerts(
alert_queue: asyncio.Queue[dict[str,Any]],
message_queue: asyncio.Queue[Message]
)->NoReturn:
"""
Read alerts from alert_queue, format them, and put them in message_queue.
"""
while True:
alert = await alert_queue.get()
message = format_alert(alert)
await message_queue.put(message)
alert_queue.task_done()

@ -0,0 +1,35 @@
"""
The webhook receiving the alerts from alertmanager.
"""
import asyncio
import aiohttp.web
import aiohttp.web_request
from typing import (
Any,
NoReturn
)
ENDPOINT = "/webhook"
async def run_webhook(
alert_queue: asyncio.Queue[dict[str, Any]],
host: str,
port: int
)->NoReturn:
"""
Run the webhook endpoint and put the alerts in the queue.
"""
async def handler(request:aiohttp.web_request.Request):
alert = await request.json()
await alert_queue.put(alert)
return aiohttp.web.Response()
app = aiohttp.web.Application()
app.add_routes([aiohttp.web.post(ENDPOINT, handler)])
runner = aiohttp.web.AppRunner(app)
await runner.setup()
site = aiohttp.web.TCPSite(runner, host, port)
await site.start()
Loading…
Cancel
Save