diff --git a/config/__init__.py b/config/__init__.py index 1d61cd6..22d3ed3 100755 --- a/config/__init__.py +++ b/config/__init__.py @@ -1,7 +1,8 @@ -from .config import conf, STATIC_DIR +from .config import conf, STATIC_DIR, icon_dict __all__ = [ "conf", STATIC_DIR, + icon_dict, ] diff --git a/config/config.py b/config/config.py index 4841705..9c0c501 100755 --- a/config/config.py +++ b/config/config.py @@ -12,6 +12,15 @@ import logging BASE_DIR = Path(__file__).parent.parent TEMPLATES_DIR = BASE_DIR / "web" / "templates" STATIC_DIR = BASE_DIR / "web" / "static" +icon_dict = { + "0": "", + "1": "🟦", + "2": "🟨", + "3": "🟧", + "4": "🟥", + "5": "🟫", + "10": "✅", +} class LogConfig(BaseModel): diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..56384f4 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-slim +WORKDIR /app +ADD config /app/config +ADD redis_db /app/redis_db +ADD telegram /app/telegram +ADD zabbix /app/zabbix +ADD main.py /app/main.py +ADD pyproject.toml /app/pyproject.toml +ADD uv.lock /app/uv.lock +RUN pip install --upgrade pip +RUN pip install uv +RUN uv sync --no-dev +CMD ["uv", "run", "main.py"] \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..852a418 --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,34 @@ +version: '3.3' + +services: + redis: + image: redis:latest + restart: always + volumes: + - ./local_redis_file/data:/data + command: [redis-server, --protected-mode yes, --port 6379, --requirepass, P@ssw0rd!] + + tg-bot: + image: git.sm8255082.ru/osnova/zbx-tg-bot:1.0.0 + restart: always + depends_on: + - redis + ports: + - "8000:8000" + environment: + - OAA_CFG__LOG__LEVEL=30 + - OAA_CFG__LOG__LEVEL_TO_FILE=30 + + - CFG__ZABBIX__URL=https://zabbix.example.com + - CFG__ZABBIX__TOKEN=string + - CFG__ZABBIX__MIN_SEVERITY=2 + - CFG__ZABBIX__UPD_INTERVAL=30 + + - CFG__TGBOT__TOKEN=string + - CFG__TGBOT__CHAT_ID=000000 + - CFG__TGBOT__TREAD_ID=0 + + - CFG__REDIS__HOST=redis + - CFG__REDIS__PORT=6379 + - CFG__REDIS__PWD=P@ssw0rd! + diff --git a/docker/only_redis-compose.yaml b/docker/only_redis-compose.yaml new file mode 100644 index 0000000..f17cb73 --- /dev/null +++ b/docker/only_redis-compose.yaml @@ -0,0 +1,12 @@ +version: '3.3' + +services: + redis: + image: redis:latest + restart: always + ports: + - "6379:6379" + volumes: + - ./local_redis_file/data:/data + #command: ["redis-server", --port 6379] + command: [redis-server, --protected-mode yes, --port 6379, --requirepass, P@ssw0rd!] \ No newline at end of file diff --git a/main.py b/main.py index 9bb5fe9..77699b7 100644 --- a/main.py +++ b/main.py @@ -1,21 +1,61 @@ import logging as log from zabbix import get_active_problems -from config import conf +from config import conf, icon_dict from time import sleep +from redis_db import ( + get_all_keys, + get_value, + set_value, + del_value, +) +import asyncio +from telegram import del_message, send_message -def main_loop(): +async def main_loop(): active_alerts = get_active_problems() - for i in active_alerts: - print(i) - print(len(active_alerts)) + telegram_alerts = await get_all_keys() + + new_alerts_id = [] + closed_alerts_id = [] + + for active_alert in active_alerts["event_ids"]: + if active_alert not in telegram_alerts: + new_alerts_id.append(active_alert) + + for telegram_alert in telegram_alerts: + if telegram_alert not in active_alerts["event_ids"]: + closed_alerts_id.append(telegram_alert) + + log.info("new " + str(new_alerts_id)) + log.info("closed " + str(closed_alerts_id)) + + for new_alert in new_alerts_id: + message = ( + icon_dict[active_alerts[new_alert]["severity"]] + + f"{active_alerts[new_alert]['host']}\n" + + f"{active_alerts[new_alert]['name']}" + ) + msg_id = await send_message(message) + if msg_id["status"] == 200: + await set_value(key=new_alert, value=msg_id["msg_id"]) + + for closed_alert in closed_alerts_id: + msg_id = await get_value(closed_alert) + if msg_id: + resp = await del_message(int(msg_id)) + if resp["status"] == 200: + await del_value(closed_alert) + if resp["status"] == 400: + log.warning(f"remove olg message {msg_id} from reddis") + await del_value(closed_alert) if __name__ == "__main__": log.info("Starting app") try: while True: - main_loop() + asyncio.run(main_loop()) sleep(conf.zabbix.upd_interval) except KeyboardInterrupt: log.info("Manual app stopped") diff --git a/redis_db/__init__.py b/redis_db/__init__.py index 54cc2e2..d885515 100644 --- a/redis_db/__init__.py +++ b/redis_db/__init__.py @@ -3,6 +3,9 @@ from .crud import ( set_value, ping, pop_value, + get_all, + get_all_keys, + del_value, ) __all__ = [ @@ -10,4 +13,7 @@ __all__ = [ "set_value", "ping", "pop_value", + "get_all", + "get_all_keys", + "del_value", ] diff --git a/redis_db/crud.py b/redis_db/crud.py index bd76476..96a55ab 100644 --- a/redis_db/crud.py +++ b/redis_db/crud.py @@ -31,3 +31,29 @@ async def pop_value(key): value = await redis_connect.client.getdel(key) log.info("Get and delete %s = %s", key, value) return value + + +async def del_value(key): + async with RedisManager() as redis_connect: + if redis_connect: + value = await redis_connect.client.delete(key) + log.info("Delete %s = %s", key, value) + + +async def get_all(): + async with RedisManager() as redis_connect: + if redis_connect: + all_data = {} + async for key in redis_connect.client.scan_iter(): + value = await redis_connect.client.get(key) + all_data[key] = value if value else None + return all_data + + +async def get_all_keys(): + async with RedisManager() as redis_connect: + if redis_connect: + all_keys = [] + async for key in redis_connect.client.scan_iter(): + all_keys.append(key) + return all_keys diff --git a/redis_db/r_helper.py b/redis_db/r_helper.py index 9442a6c..3ccb4e1 100644 --- a/redis_db/r_helper.py +++ b/redis_db/r_helper.py @@ -10,6 +10,7 @@ class RedisManager: self.connect_params = { "host": conf.redis.host, "port": conf.redis.port, + "decode_responses": True, } if conf.redis.pwd: self.connect_params["password"] = conf.redis.pwd diff --git a/telegram/__init__.py b/telegram/__init__.py index e69de29..e82d7ff 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -0,0 +1,9 @@ +from .message import ( + send_message, + del_message, +) + +__all__ = [ + "send_message", + "del_message", +] diff --git a/telegram/message.py b/telegram/message.py index 946f7b7..a2908d9 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -6,8 +6,7 @@ from config import conf async def send_message( message: str, - -) -> dict | None: +) -> dict: url = f"https://api.telegram.org/bot{conf.tgbot.token}/sendMessage" params = { "chat_id": conf.tgbot.chat_id, @@ -16,37 +15,52 @@ async def send_message( } async with aiohttp.ClientSession() as session: - async with session.post( - url, - json=params, - ) as response: - log.info(f"Response status: {response.status}") - resp = await response.json() - if response.status == 200: - log.info(f"Message with ID: {resp['result']['message_id']} send") - return { - "status": response.status, - "msg_id": resp["result"]["message_id"], - } - log.warning(f"Message not send. Response status: {response.status}") + try: + async with session.post( + url, + json=params, + ) as response: + log.info(f"Response status: {response.status}") + resp = await response.json() + if response.status == 200: + log.info(f"Message with ID: {resp['result']['message_id']} send") + return { + "status": response.status, + "msg_id": resp["result"]["message_id"], + } + else: + log.warning(f"Message not send. Response status: {response.status}") + return {"status": response.status} + except Exception as e: + log.warning(f"Exception: {e}") + return {"status": e} async def del_message( message_id: int, -) -> dict | None: +) -> dict: url = f"https://api.telegram.org/bot{conf.tgbot.token}/deleteMessage" async with aiohttp.ClientSession() as session: - async with session.post( - url, - json={ - "chat_id": conf.tgbot.chat_id, - "message_id": message_id, - }, - ) as response: - if response.status == 200: - log.info(f"Message ID {message_id} deleted") - return { - "status": response.status, - } - else: - log.warning(f"Response status: {response.status}") + try: + async with session.post( + url, + json={ + "chat_id": conf.tgbot.chat_id, + "message_id": message_id, + }, + ) as response: + if response.status == 200: + log.info(f"Message ID {message_id} deleted") + return { + "status": response.status, + } + else: + log.warning( + f"Message ID {message_id} NOT deleted. Response status: {response.status}" + ) + return { + "status": response.status, + } + except Exception as e: + log.warning(f"Exception: {e}") + return {"status": e} diff --git a/zabbix/zabbix_api.py b/zabbix/zabbix_api.py index aa747a8..0036abd 100644 --- a/zabbix/zabbix_api.py +++ b/zabbix/zabbix_api.py @@ -5,7 +5,7 @@ from zabbix_utils import ZabbixAPI from config import conf -def get_active_problems(): +def get_active_problems() -> dict: api = ZabbixAPI(url=conf.zabbix.url, token=conf.zabbix.token) try: problems = api.problem.get( @@ -32,8 +32,11 @@ def get_active_problems(): "severity", ], ) + events_dict = {"event_ids": []} for event in events: event["host"] = event.pop("hosts", None)[0]["host"] - return events + events_dict[event["eventid"]] = event + events_dict["event_ids"].append(event["eventid"]) + return events_dict except: log.warning("Get event from zabbix error")