55347f962c
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 15s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 16s
AI Code Review / AI Code Review (pull_request) Failing after 23s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 49s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m55s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m52s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 3m17s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m31s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 3m56s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 20s
新增 scripts/ci/chatops/ 目录,Python实现,纯工具链扩展不动业务代码。 3个核心模块: - ci_query.py - 查询CI状态(分支/PR/失败详情) - ci_trigger.py - 触发重跑(rerun失败job/整个workflow/取消) - feishu_notify.py - CI事件飞书通知(失败/恢复/E2E摘要) 配套基础设施: - config.py - 配置化,所有token/webhook走环境变量 - gitea_client.py - Gitea API统一封装(urllib实现,无第三方依赖) - webhook_server.py - FastAPI webhook接收服务(可选依赖) 通知规则(最高频使用场景): - main/develop分支CI失败 → 飞书告警卡片(含失败摘要+一键重跑) - 分支从失败恢复 → 绿色恢复通知(含故障时长) - E2E测试失败 → 单独摘要通知 - 连续失败不重复告警(状态缓存去重) 飞书交互命令(第一版已实现解析+执行): - /ci status [branch] - 查询分支CI状态 - /ci rerun <run-id> - 重跑失败jobs - /ci rerun latest [branch] - 重跑最近一次失败的run - /ci help - 帮助 black+ruff全绿,与现有ci脚本风格一致。
170 lines
5.1 KiB
Python
Executable File
170 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
CI 触发模块 - 重新运行失败 job、重跑整个 workflow、取消 run
|
|
|
|
支持操作:
|
|
- rerun_failed: 重跑失败的 jobs
|
|
- rerun_all: 重跑整个 workflow
|
|
- cancel: 取消运行中的 run
|
|
|
|
用法:
|
|
python3 scripts/ci/chatops/ci_trigger.py --run-id 456 --action rerun_failed
|
|
python3 scripts/ci/chatops/ci_trigger.py --run-id 456 --action rerun_all
|
|
python3 scripts/ci/chatops/ci_trigger.py --run-id 456 --action cancel
|
|
|
|
设计:
|
|
- 与飞书机器人 /ci rerun 命令对接
|
|
- 操作前自动校验 run 状态,避免无效操作
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from . import config
|
|
from .gitea_client import GiteaClient
|
|
|
|
|
|
class CITrigger:
|
|
"""CI 操作触发器"""
|
|
|
|
def __init__(self, gitea_client=None):
|
|
self.gitea = gitea_client or GiteaClient()
|
|
|
|
# ── 触发操作 ─────────────────────────────────────
|
|
|
|
def rerun_failed(self, run_id):
|
|
"""重跑失败的 jobs
|
|
|
|
Returns:
|
|
dict: {success, message, new_run_id?}
|
|
"""
|
|
run = self.gitea.get_run(run_id)
|
|
if not run:
|
|
return {"success": False, "message": f"Run {run_id} 不存在"}
|
|
|
|
status = run.get("status", "")
|
|
if status != "completed":
|
|
return {
|
|
"success": False,
|
|
"message": f"Run {run_id} 当前状态为 {status},仅 completed 状态才能重跑",
|
|
}
|
|
|
|
result = self.gitea.rerun_failed_jobs(run_id)
|
|
if result is None:
|
|
return {"success": False, "message": "重跑请求失败"}
|
|
|
|
# Gitea rerun 后返回的 run id 通常不变(复用原 run)
|
|
return {
|
|
"success": True,
|
|
"message": f"已触发重跑失败 jobs: Run #{run_id}",
|
|
"run_id": run_id,
|
|
"run_url": f"{config.GITEA_URL}/{config.GITEA_REPO}/actions/runs/{run_id}",
|
|
}
|
|
|
|
def rerun_all(self, run_id):
|
|
"""重跑整个 workflow run
|
|
|
|
Returns:
|
|
dict: {success, message, run_id, run_url}
|
|
"""
|
|
run = self.gitea.get_run(run_id)
|
|
if not run:
|
|
return {"success": False, "message": f"Run {run_id} 不存在"}
|
|
|
|
status = run.get("status", "")
|
|
if status == "running" or status == "pending":
|
|
return {
|
|
"success": False,
|
|
"message": f"Run {run_id} 正在运行中,无需重跑",
|
|
}
|
|
|
|
result = self.gitea.rerun_run(run_id)
|
|
if result is None:
|
|
return {"success": False, "message": "重跑请求失败"}
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"已触发完整重跑: Run #{run_id}",
|
|
"run_id": run_id,
|
|
"run_url": f"{config.GITEA_URL}/{config.GITEA_REPO}/actions/runs/{run_id}",
|
|
}
|
|
|
|
def cancel_run(self, run_id):
|
|
"""取消运行中的 run
|
|
|
|
Returns:
|
|
dict: {success, message}
|
|
"""
|
|
run = self.gitea.get_run(run_id)
|
|
if not run:
|
|
return {"success": False, "message": f"Run {run_id} 不存在"}
|
|
|
|
status = run.get("status", "")
|
|
if status == "completed":
|
|
return {
|
|
"success": False,
|
|
"message": f"Run {run_id} 已完成,无需取消",
|
|
}
|
|
|
|
result = self.gitea.cancel_run(run_id)
|
|
if result is None:
|
|
return {"success": False, "message": "取消请求失败"}
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"已取消 Run #{run_id}",
|
|
"run_id": run_id,
|
|
}
|
|
|
|
def rerun_latest_failed(self, branch="develop", workflow_id=None):
|
|
"""重跑指定分支最近一次失败的 run
|
|
|
|
用于快速恢复场景,不需要先查 run_id
|
|
"""
|
|
runs, _ = self.gitea.list_runs(branch=branch, workflow_id=workflow_id, status="failure", limit=5)
|
|
if not runs:
|
|
return {"success": False, "message": f"{branch} 分支没有失败的 run"}
|
|
|
|
latest = runs[0]
|
|
run_id = latest.get("id")
|
|
return self.rerun_failed(run_id)
|
|
|
|
|
|
# ── CLI 入口 ──────────────────────────────────────────
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="CI 触发操作")
|
|
parser.add_argument("--run-id", required=True, help="Workflow Run ID")
|
|
parser.add_argument(
|
|
"--action",
|
|
required=True,
|
|
choices=["rerun_failed", "rerun_all", "cancel"],
|
|
help="操作类型",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
trigger = CITrigger()
|
|
|
|
if args.action == "rerun_failed":
|
|
result = trigger.rerun_failed(args.run_id)
|
|
elif args.action == "rerun_all":
|
|
result = trigger.rerun_all(args.run_id)
|
|
elif args.action == "cancel":
|
|
result = trigger.cancel_run(args.run_id)
|
|
else:
|
|
print(f"未知操作: {args.action}")
|
|
return 1
|
|
|
|
status = "✅" if result["success"] else "❌"
|
|
print(f"{status} {result['message']}")
|
|
if result.get("run_url"):
|
|
print(f" {result['run_url']}")
|
|
|
|
return 0 if result["success"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|