Initial commit: Message Gateway project
- FastAPI приложение для отправки мониторинговых алертов в мессенджеры - Поддержка Telegram и MAX/VK - Интеграция с Grafana, Zabbix, AlertManager - Автоматическое создание тикетов в Jira - Управление группами мессенджеров через API - Декораторы для авторизации и скрытия эндпоинтов - Подробная документация в папке docs/ Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
187
app/main.py
Normal file
187
app/main.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Главный модуль приложения 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
|
||||
)
|
||||
Reference in New Issue
Block a user