- FastAPI приложение для отправки мониторинговых алертов в мессенджеры - Поддержка Telegram и MAX/VK - Интеграция с Grafana, Zabbix, AlertManager - Автоматическое создание тикетов в Jira - Управление группами мессенджеров через API - Декораторы для авторизации и скрытия эндпоинтов - Подробная документация в папке docs/ Автор: Сергей Антропов Сайт: https://devops.org.ru
187 lines
8.3 KiB
Python
187 lines
8.3 KiB
Python
"""
|
||
Главный модуль приложения 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
|
||
) |