Files
awoooi/apps/api/src/services/playbook_seed_service.py
Your Name de2d34d4cd
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
fix(playbook): C1-C4 全流程串接 — evolver保護+seeder復活+規則即時建立+watchdog W-4
C1: playbook_evolver — yaml_rule source playbooks 加 YAML_RULE guard,
    evolver 不再封存 seeder 建立的 APPROVED playbook,保護自動修復鏈路

C2: playbook_seed_service — idempotency SQL 排除 DEPRECATED 記錄,
    evolver 封存後重啟可復活 yaml_rule playbooks

C3: alert_rule_engine — AI 自動生成規則成功後立即呼叫 seed_playbooks_from_rules(),
    不等下次重啟即可建立對應 APPROVED Playbook

C4: ai_slo_watchdog_job — 新增 W-4 APPROVED playbook 數量為 0 告警,
    鏈路斷裂立即 TYPE-8M;total checks 由 3 升為 4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:18:11 +08:00

115 lines
4.1 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.
"""
Playbook Seed Service — 從 alert_rules.yaml 初始化 Playbook 資料
=================================================================
職責:
- 啟動時讀取 alert_rules.yaml
- 將每條規則轉換為 APPROVED Playbook 寫入 DB冪等已存在則跳過
- 確保自動修復鏈路有資料可用
呼叫方: main.py lifespan (asyncio.create_task — 非阻塞)
2026-04-10 Claude Sonnet 4.6 Asia/Taipei
"""
from __future__ import annotations
from pathlib import Path
import structlog
import yaml
logger = structlog.get_logger(__name__)
_RULES_PATH = Path(__file__).parent.parent.parent / "alert_rules.yaml"
async def seed_playbooks_from_rules() -> None:
"""從 alert_rules.yaml 匯入 APPROVED Playbook冪等"""
try:
if not _RULES_PATH.exists():
logger.warning("playbook_seed_rules_not_found", path=str(_RULES_PATH))
return
data = yaml.safe_load(_RULES_PATH.read_text())
rules = data.get("rules", [])
if not rules:
return
from src.models.playbook import (
ActionType, Playbook, PlaybookSource, PlaybookStatus,
RepairStep, RiskLevel, SymptomPattern,
)
from src.repositories.playbook_repository import get_playbook_repository
repo = get_playbook_repository()
# 取得現有 YAML_RULE playbook依 name 去重避免重複建立
# 2026-04-15 ogt: 不再用 list_playbooks舊格式 repair_steps 會 validation error
# 改用 raw SQL 只撈 name 欄位,更穩健
from src.db.base import get_db_context
from sqlalchemy import text as sa_text
async with get_db_context() as db:
rows = await db.execute(
sa_text(
"SELECT name FROM playbooks WHERE source = 'yaml_rule'"
" AND status != 'deprecated'"
)
)
existing_names = {r[0] for r in rows.fetchall()}
seeded = 0
for rule in rules:
rule_id = rule.get("id", "")
rule_name = rule.get("description", rule_id)
if rule_name in existing_names:
continue
resp = rule.get("response", {})
kubectl_cmd = resp.get("kubectl_command", "").strip()
if not kubectl_cmd:
continue
risk_str = resp.get("risk", "medium").lower()
risk_map = {"low": RiskLevel.LOW, "medium": RiskLevel.MEDIUM, "critical": RiskLevel.HIGH}
risk = risk_map.get(risk_str, RiskLevel.MEDIUM)
alertnames = rule.get("match", {}).get("alertname", [])
action_type = ActionType.KUBECTL
if kubectl_cmd.startswith("ssh"):
action_type = ActionType.SSH_COMMAND
playbook = Playbook(
name=rule.get("description", rule_id),
description=resp.get("description", rule.get("description", "")),
status=PlaybookStatus.APPROVED,
source=PlaybookSource.YAML_RULE,
symptom_pattern=SymptomPattern(
alert_names=alertnames,
affected_services=[],
severity_range=["P2", "P3"],
),
repair_steps=[
RepairStep(
step_number=1,
action_type=action_type,
command=kubectl_cmd,
expected_result=resp.get("action_title", ""),
risk_level=risk,
requires_approval=False,
)
],
ai_confidence=1.0,
approved_by="alert_rules_yaml",
)
try:
await repo.create(playbook)
seeded += 1
logger.info("playbook_seeded", rule_id=rule_id, name=playbook.name)
except Exception as e:
logger.warning("playbook_seed_failed", rule_id=rule_id, error=str(e))
logger.info("playbook_seed_complete", seeded=seeded, total=len(rules))
except Exception as e:
logger.error("playbook_seed_error", error=str(e))