Files
MessageGateway/app/main.py
Sergey Antropov b90def35ed Initial commit: Message Gateway project
- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
2025-11-12 20:25:11 +03:00

187 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Главный модуль приложения Telegram Gateway.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import logging
from fastapi import FastAPI
from app.common.cors import add as add_cors
from app.common.telemetry import add as add_telemetry
from app.api.v1.router import api_router
# Настройка логирования (базовая настройка, детальная настройка в app.common.logger)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Создаем приложение FastAPI
app = FastAPI(
title="Message Gateway",
summary="Отправляем мониторинговые алерты в телеграм/MAX и создаем тикеты в Jira",
description=(
"Приложение для оповещений, приходящих из Grafana/Zabbix/AlertManager "
"посредством вебхука, в телеграм/MAX. С возможностью отправок в треды и создания тикетов в Jira. "
"<br><br><b>Что бы начать отправлять сообщения</b>, добавьте бота "
"<b>@CismGlobalMonitoring_bot</b> в чат и <b>внесите изменения в группы</b>"
),
version="0.2.0",
contact={
"name": "Сергей Антропов",
"url": "https://devops.org.ru/contact/",
"email": "sergey@antropoff.ru",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
debug=False,
swagger_ui_init_oauth={
"clientId": "api-key",
"appName": "Message Gateway API",
"usePkceWithAuthorizationCodeGrant": False,
}
)
# Добавляем схему безопасности в Swagger
from fastapi.openapi.utils import get_openapi
from fastapi.routing import APIRoute
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
# Собираем пути, которые должны быть скрыты от API
hidden_paths = set()
# Перебираем все routes и находим те, которые помечены как скрытые
for route in app.routes:
# Проверяем только APIRoute
if not isinstance(route, APIRoute):
continue
# Получаем endpoint функцию
endpoint = route.endpoint
# Проверяем, помечен ли endpoint как скрытый от API
if hasattr(endpoint, "__hide_from_api__") and endpoint.__hide_from_api__:
# Получаем полный путь (с учетом префиксов)
path = route.path
# Нормализуем путь (убираем параметры типа {param} для сопоставления)
# Но нам нужно точное сопоставление, поэтому используем полный путь
hidden_paths.add(path)
logger.debug(f"Эндпоинт {path} помечен как скрытый от API")
# Генерируем схему как обычно
openapi_schema = get_openapi(
title=app.title,
version=app.version,
description=app.description,
routes=app.routes,
)
# Удаляем скрытые пути из схемы
if hidden_paths:
paths = openapi_schema.get("paths", {})
# Создаем новый словарь paths без скрытых путей
filtered_paths = {}
for path, path_item in paths.items():
# Проверяем, должен ли путь быть скрыт
# Путь в схеме должен точно совпадать с путем в route.path
# route.path уже содержит все префиксы (router prefix + route path)
should_hide = path in hidden_paths
if not should_hide:
filtered_paths[path] = path_item
else:
logger.debug(f"Удаляем путь {path} из OpenAPI схемы")
openapi_schema["paths"] = filtered_paths
# Добавляем схему безопасности API Key
if "components" not in openapi_schema:
openapi_schema["components"] = {}
if "securitySchemes" not in openapi_schema["components"]:
openapi_schema["components"]["securitySchemes"] = {}
# Определяем имя схемы безопасности, которое использует FastAPI
# FastAPI автоматически генерирует имя на основе класса Security
# Обычно это имя класса в camelCase (например, "APIKeyHeader")
api_key_scheme_name = None
# Перебираем существующие схемы безопасности и находим API Key схему
for scheme_name, scheme_def in openapi_schema["components"].get("securitySchemes", {}).items():
if scheme_def.get("type") == "apiKey" and scheme_def.get("name") == "X-API-Key":
api_key_scheme_name = scheme_name
break
# Если схема не найдена, создаем новую с именем "ApiKeyAuth"
if not api_key_scheme_name:
api_key_scheme_name = "ApiKeyAuth"
openapi_schema["components"]["securitySchemes"][api_key_scheme_name] = {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API ключ для авторизации. Получите его из переменной окружения API_KEY."
}
else:
# Обновляем существующую схему, если она уже есть
openapi_schema["components"]["securitySchemes"][api_key_scheme_name] = {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API ключ для авторизации. Получите его из переменной окружения API_KEY."
}
# Если имя схемы не "ApiKeyAuth", переименовываем ее
if api_key_scheme_name != "ApiKeyAuth":
# Сохраняем старую схему
old_scheme = openapi_schema["components"]["securitySchemes"].pop(api_key_scheme_name)
# Создаем новую схему с именем "ApiKeyAuth"
openapi_schema["components"]["securitySchemes"]["ApiKeyAuth"] = old_scheme
# Заменяем все использования старого имени на новое в security эндпоинтов
for path, path_item in openapi_schema.get("paths", {}).items():
for method, operation in path_item.items():
if isinstance(operation, dict) and "security" in operation:
security_list = operation["security"]
for security_item in security_list:
if api_key_scheme_name in security_item:
# Заменяем старое имя на новое
security_item["ApiKeyAuth"] = security_item.pop(api_key_scheme_name)
# Убеждаемся, что схема "ApiKeyAuth" существует
if "ApiKeyAuth" not in openapi_schema["components"]["securitySchemes"]:
openapi_schema["components"]["securitySchemes"]["ApiKeyAuth"] = {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API ключ для авторизации. Получите его из переменной окружения API_KEY."
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
# Добавляем CORS
add_cors(app)
# Добавляем телеметрию
add_telemetry(app)
# Подключаем роутер API v1
app.include_router(api_router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=False
)