Compare commits
7 Commits
ec66aa40de
...
06afbc187d
Author | SHA1 | Date |
---|---|---|
histausse | 06afbc187d | 3 years ago |
histausse | d9ef6690fc | 3 years ago |
histausse | d8aff91f6f | 3 years ago |
histausse | 80891eeda2 | 3 years ago |
histausse | eb8cdefab4 | 3 years ago |
histausse | 1172db1d95 | 3 years ago |
histausse | f544d8eef9 | 3 years ago |
@ -1,5 +1,3 @@
|
|||||||
# Kassandra
|
# 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…
Reference in New Issue