""" Утилиты для работы с Jira тикетами. Автор: Сергей Антропов Сайт: https://devops.org.ru """ import logging from typing import Optional, Dict, Any from jinja2 import Environment, FileSystemLoader from app.models.alertmanager import PrometheusAlert from app.models.grafana import GrafanaAlert from app.models.zabbix import ZabbixAlert from app.models.jira import JiraMapping from app.core.jira_client import JiraClient from app.core.jira_mapping import jira_mapping_manager from app.core.config import get_settings from app.core.utils import add_spaces_to_alert_name logger = logging.getLogger(__name__) async def create_jira_ticket_from_alert( alert: Any, source: str, k8s_cluster: Optional[str] = None, mapping: Optional[JiraMapping] = None ) -> Optional[str]: """ Создать Jira тикет на основе алерта. Args: alert: Данные алерта (PrometheusAlert, GrafanaAlert, ZabbixAlert). source: Источник алерта (alertmanager, grafana, zabbix). k8s_cluster: Имя Kubernetes кластера (опционально). mapping: Маппинг для создания тикета (опционально). Returns: Ключ созданного тикета или None в случае ошибки. """ from app.core.config import get_settings settings = get_settings() if not settings.jira_enabled: logger.debug("Jira отключен, тикет не создается") return None if not settings.jira_url or not settings.jira_email or not settings.jira_api_token: logger.warning("Jira не настроен (отсутствуют URL, email или API token)") return None # Создаем клиент Jira try: jira_client = JiraClient( url=settings.jira_url, email=settings.jira_email, api_token=settings.jira_api_token ) except Exception as e: logger.error(f"Ошибка создания Jira клиента: {e}") return None # Получаем маппинг, если не указан if not mapping: alert_data = _extract_alert_data(alert, source, k8s_cluster) mapping = await jira_mapping_manager.find_mapping(source, alert_data) if not mapping: logger.warning(f"Маппинг не найден для источника {source}") return None # Формируем данные тикета summary = _generate_jira_summary(alert, source, mapping) description = _generate_jira_description(alert, source, k8s_cluster, mapping) # Создаем тикет try: issue_key = jira_client.create_issue( project=mapping.project, summary=summary, description=description, issue_type=mapping.issue_type, assignee=mapping.assignee, priority=mapping.priority, labels=mapping.labels ) if issue_key: logger.info(f"Jira тикет {issue_key} создан для алерта из {source}") # Увеличиваем счетчик созданных тикетов from app.core.metrics import metrics metrics.increment_jira_ticket_created( source=source, project=mapping.project, k8s_cluster=k8s_cluster or "", chat="", thread=0 ) return issue_key except Exception as e: logger.error(f"Ошибка создания Jira тикета: {e}") # Увеличиваем счетчик ошибок from app.core.metrics import metrics metrics.increment_jira_ticket_error( source=source, k8s_cluster=k8s_cluster or "", chat="", thread=0 ) return None def _extract_alert_data( alert: Any, source: str, k8s_cluster: Optional[str] = None ) -> Dict[str, Any]: """ Извлечь данные алерта для маппинга. Args: alert: Данные алерта. source: Источник алерта. k8s_cluster: Имя Kubernetes кластера. Returns: Словарь с данными алерта. """ alert_data = {} if source == "alertmanager" and isinstance(alert, PrometheusAlert): alert_data["status"] = alert.status alert_data.update(alert.commonLabels) alert_data.update(alert.commonAnnotations) if k8s_cluster: alert_data["k8s_cluster"] = k8s_cluster elif source == "grafana" and isinstance(alert, GrafanaAlert): alert_data["state"] = alert.state alert_data["tags"] = alert.tags alert_data["ruleName"] = alert.ruleName elif source == "zabbix" and isinstance(alert, ZabbixAlert): alert_data["status"] = alert.status alert_data["event-severity"] = alert.event_severity or "" alert_data["event-name"] = alert.event_name alert_data["host-name"] = alert.host_name return alert_data def _generate_jira_summary( alert: Any, source: str, mapping: JiraMapping ) -> str: """ Сгенерировать заголовок Jira тикета. Args: alert: Данные алерта. source: Источник алерта. mapping: Маппинг для создания тикета. Returns: Заголовок тикета. """ severity_prefix = "" alert_name = "" if source == "alertmanager" and isinstance(alert, PrometheusAlert): severity = alert.commonLabels.get("severity", "") alert_name = alert.commonLabels.get("alertname", "") if severity: severity_prefix = f"[{severity.upper()}] " if alert_name: alert_name = add_spaces_to_alert_name(alert_name) summary = alert.commonAnnotations.get("summary", alert_name) elif source == "grafana" and isinstance(alert, GrafanaAlert): alert_name = alert.ruleName if alert.state == "alerting": severity_prefix = "[ALERTING] " summary = alert.title or alert_name elif source == "zabbix" and isinstance(alert, ZabbixAlert): severity = alert.event_severity or "" alert_name = alert.event_name if severity: severity_prefix = f"[{severity.upper()}] " summary = alert.alert_subject or alert_name else: summary = "Unknown Alert" return f"{severity_prefix}{alert_name}: {summary}"[:255] # Ограничение Jira def _generate_jira_description( alert: Any, source: str, k8s_cluster: Optional[str] = None, mapping: Optional[JiraMapping] = None ) -> str: """ Сгенерировать описание Jira тикета. Args: alert: Данные алерта. source: Источник алерта. k8s_cluster: Имя Kubernetes кластера. mapping: Маппинг для создания тикета. Returns: Описание тикета в формате Markdown. """ from app.core.config import get_settings settings = get_settings() # Загружаем шаблон описания try: environment = Environment(loader=FileSystemLoader(settings.templates_path)) template_name = f"jira_{source}.tmpl" try: template = environment.get_template(template_name) except Exception: # Если шаблон не найден, используем общий шаблон template = environment.get_template("jira_common.tmpl") except Exception: # Если общий шаблон не найден, используем простое описание return _generate_simple_description(alert, source, k8s_cluster) # Формируем словарь для шаблона template_data = _prepare_template_data(alert, source, k8s_cluster) # Рендерим шаблон description = template.render(template_data) return description def _generate_simple_description( alert: Any, source: str, k8s_cluster: Optional[str] = None ) -> str: """ Сгенерировать простое описание тикета без шаблона. Args: alert: Данные алерта. source: Источник алерта. k8s_cluster: Имя Kubernetes кластера. Returns: Простое описание тикета. """ description = f"**Источник:** {source}\n\n" if source == "alertmanager" and isinstance(alert, PrometheusAlert): description += f"**Статус:** {alert.status}\n\n" description += "**Метки:**\n" for key, value in alert.commonLabels.items(): description += f"- {key}: {value}\n" description += "\n**Аннотации:**\n" for key, value in alert.commonAnnotations.items(): description += f"- {key}: {value}\n" if k8s_cluster: description += f"\n**Kubernetes кластер:** {k8s_cluster}\n" elif source == "grafana" and isinstance(alert, GrafanaAlert): description += f"**Состояние:** {alert.state}\n\n" description += f"**Правило:** {alert.ruleName}\n\n" description += f"**Сообщение:** {alert.message or 'Нет сообщения'}\n\n" if alert.tags: description += "**Теги:**\n" for key, value in alert.tags.items(): description += f"- {key}: {value}\n" elif source == "zabbix" and isinstance(alert, ZabbixAlert): description += f"**Статус:** {alert.status}\n\n" description += f"**Серьезность:** {alert.event_severity or 'Unknown'}\n\n" description += f"**Событие:** {alert.event_name}\n\n" description += f"**Хост:** {alert.host_name} ({alert.host_ip})\n\n" description += f"**Сообщение:** {alert.alert_message}\n" return description def _prepare_template_data( alert: Any, source: str, k8s_cluster: Optional[str] = None ) -> Dict[str, Any]: """ Подготовить данные для шаблона описания тикета. Args: alert: Данные алерта. source: Источник алерта. k8s_cluster: Имя Kubernetes кластера. Returns: Словарь с данными для шаблона. """ template_data = { "source": source, "k8s_cluster": k8s_cluster or "", } if source == "alertmanager" and isinstance(alert, PrometheusAlert): template_data["status"] = alert.status template_data["common_labels"] = alert.commonLabels template_data["common_annotations"] = alert.commonAnnotations template_data["alertname"] = alert.commonLabels.get("alertname", "") template_data["severity"] = alert.commonLabels.get("severity", "") template_data["summary"] = alert.commonAnnotations.get("summary", "") template_data["description"] = alert.commonAnnotations.get("description", "") elif source == "grafana" and isinstance(alert, GrafanaAlert): template_data["state"] = alert.state template_data["title"] = alert.title template_data["ruleName"] = alert.ruleName template_data["message"] = alert.message or "" template_data["tags"] = alert.tags template_data["evalMatches"] = alert.evalMatches elif source == "zabbix" and isinstance(alert, ZabbixAlert): template_data["status"] = alert.status template_data["event_severity"] = alert.event_severity or "" template_data["event_name"] = alert.event_name template_data["alert_subject"] = alert.alert_subject template_data["alert_message"] = alert.alert_message template_data["host_name"] = alert.host_name template_data["host_ip"] = alert.host_ip template_data["host_port"] = alert.host_port return template_data