ci: 增强CI失败通知,自动分类失败原因并给出修复建议
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 50s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m10s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m27s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m42s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m43s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m56s
AI Code Review / AI Code Review (pull_request) Successful in 3m40s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m15s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m12s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 7m31s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 50s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m10s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m27s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m42s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m43s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m56s
AI Code Review / AI Code Review (pull_request) Successful in 3m40s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m15s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m12s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 7m31s
This commit is contained in:
+124
-31
@@ -1,17 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""发送 CI 失败通知到飞书/项目群 webhook。"""
|
||||
"""发送 CI 失败通知到飞书/项目群 webhook(增强版:带失败诊断)。
|
||||
|
||||
诊断功能:自动分析失败原因,给出分类和修复建议。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
import subprocess
|
||||
|
||||
|
||||
def run_diagnosis() -> dict:
|
||||
"""运行失败诊断脚本,返回诊断结果"""
|
||||
diag_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ci/ci_failure_diagnosis.py")
|
||||
if not os.path.exists(diag_script):
|
||||
diag_script = "scripts/ci/ci_failure_diagnosis.py"
|
||||
|
||||
result = {"category": "unknown", "category_cn": "未知", "severity": "medium",
|
||||
"summary": "", "error_lines": [], "suggestions": [], "auto_fixable": False}
|
||||
|
||||
try:
|
||||
# 运行诊断脚本
|
||||
env = os.environ.copy()
|
||||
env["DIAGNOSIS_OUTPUT"] = "/tmp/ci_diagnosis_result.json"
|
||||
|
||||
proc = subprocess.run(
|
||||
[sys.executable, diag_script],
|
||||
capture_output=True, text=True, timeout=30, env=env
|
||||
)
|
||||
|
||||
# 尝试读取结果文件
|
||||
output_file = "/tmp/ci_diagnosis_result.json"
|
||||
if os.path.exists(output_file):
|
||||
with open(output_file) as f:
|
||||
result = json.load(f)
|
||||
elif proc.stdout:
|
||||
# 从stdout解析
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"诊断脚本执行失败: {e}", file=sys.stderr)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
webhook = os.environ.get("CI_NOTIFY_WEBHOOK", "")
|
||||
if not webhook:
|
||||
print("未配置 CI_NOTIFY_WEBHOOK,跳过通知")
|
||||
print("如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK")
|
||||
return 0
|
||||
|
||||
failed_job = os.environ.get("FAILED_JOB", "Unknown Job")
|
||||
@@ -21,6 +57,88 @@ def main() -> int:
|
||||
run_id = os.environ.get("GITHUB_RUN_ID", "unknown")
|
||||
repo = os.environ.get("GITHUB_REPOSITORY", "unknown")
|
||||
run_url = f"https://git.xiaoxiajianji.com/{repo}/actions/runs/{run_id}"
|
||||
pr_number = os.environ.get("PR_NUMBER", "")
|
||||
|
||||
# 运行诊断
|
||||
diagnosis = run_diagnosis()
|
||||
|
||||
# 构建卡片内容
|
||||
severity_color = {"high": "red", "medium": "orange", "low": "blue"}
|
||||
card_status = severity_color.get(diagnosis.get("severity", "medium"), "red")
|
||||
|
||||
# 标题
|
||||
title = f"❌ CI失败 - {diagnosis.get('category_cn', '未知')}"
|
||||
|
||||
# 诊断部分
|
||||
diag_lines = []
|
||||
diag_lines.append(f"**任务**: {failed_job}")
|
||||
diag_lines.append(f"**分类**: {diagnosis.get('category_cn', '未知')}")
|
||||
if diagnosis.get("summary"):
|
||||
diag_lines.append(f"**问题**: {diagnosis['summary']}")
|
||||
|
||||
# 错误行
|
||||
error_lines = diagnosis.get("error_lines", [])
|
||||
if error_lines:
|
||||
diag_lines.append("")
|
||||
diag_lines.append("**关键错误**:")
|
||||
for err in error_lines[:3]:
|
||||
if len(err) > 100:
|
||||
err = err[:97] + "..."
|
||||
diag_lines.append(f"`{err}`")
|
||||
|
||||
# 修复建议
|
||||
suggestions = diagnosis.get("suggestions", [])
|
||||
if suggestions:
|
||||
diag_lines.append("")
|
||||
diag_lines.append("**修复建议**:")
|
||||
for i, s in enumerate(suggestions[:3], 1):
|
||||
diag_lines.append(f"{i}. {s}")
|
||||
|
||||
if diagnosis.get("auto_fixable"):
|
||||
diag_lines.append("")
|
||||
diag_lines.append("💡 *可自动修复的问题,试试Rerun*")
|
||||
|
||||
# 基本信息
|
||||
info_lines = [
|
||||
f"**分支**: {branch}",
|
||||
f"**提交**: `{commit}`",
|
||||
f"**提交者**: {actor}",
|
||||
]
|
||||
if pr_number:
|
||||
info_lines.append(f"**PR**: #{pr_number}")
|
||||
|
||||
elements = [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "lark_md",
|
||||
"content": "
|
||||
".join(diag_lines),
|
||||
},
|
||||
},
|
||||
{
|
||||
"tag": "hr",
|
||||
},
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "lark_md",
|
||||
"content": "
|
||||
".join(info_lines),
|
||||
},
|
||||
},
|
||||
{
|
||||
"tag": "action",
|
||||
"actions": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {"tag": "plain_text", "content": "查看失败日志"},
|
||||
"url": run_url,
|
||||
"type": "danger",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
payload = {
|
||||
"msg_type": "interactive",
|
||||
@@ -28,36 +146,11 @@ def main() -> int:
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "❌ CI 构建失败",
|
||||
"content": title,
|
||||
},
|
||||
"status": "red",
|
||||
"status": card_status,
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "lark_md",
|
||||
"content": (
|
||||
f"**任务**: {failed_job}\n"
|
||||
f"**分支**: {branch}\n"
|
||||
f"**提交**: {commit}\n"
|
||||
f"**提交者**: {actor}\n"
|
||||
f"**Run ID**: {run_id}"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"tag": "action",
|
||||
"actions": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {"tag": "plain_text", "content": "查看失败日志"},
|
||||
"url": run_url,
|
||||
"type": "danger",
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
"elements": elements,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -71,7 +164,7 @@ def main() -> int:
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
resp.read()
|
||||
print("通知已发送")
|
||||
print("通知已发送(带诊断信息)")
|
||||
except Exception as e:
|
||||
print(f"通知发送失败: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user