docs(spec): ADR-059 Gitea Webhook 遷移設計規格
從 GitHub Webhook (Phase 13.1) 遷移至 Gitea Webhook 最少改動策略:Header 常數替換,業務邏輯層不動 廢棄 workflow_run CI 診斷(CD pipeline 已有 TG 通知覆蓋) 整合首席架構師護欄:防禦性 payload 解析 + Content-Type 設定 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Gitea Webhook 整合設計規格
|
||||
|
||||
**日期**: 2026-04-05 (台北時區)
|
||||
**作者**: Claude Code + 首席架構師 Review
|
||||
**狀態**: 已批准,待實作
|
||||
**ADR**: ADR-059 (待建立)
|
||||
**關聯**: ADR-039 (Gitea CI/CD 遷移), ADR-029 (CI/CD AI 整合架構)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
Phase 13.1 實作了 GitHub Webhook → OpenClaw AI 代碼審查整合,但:
|
||||
1. CI/CD 已於 ADR-039 全面遷移至 Gitea,GitHub 只剩唯讀備份
|
||||
2. `GITHUB_WEBHOOK_SECRET` 從未注入 K8s Secrets,功能實際上處於死路狀態
|
||||
3. 代碼審查的觸發源應改為 Gitea(實際開發倉庫)
|
||||
|
||||
**決策:遷移至 Gitea Webhook,廢棄 GitHub Webhook 整合。**
|
||||
|
||||
---
|
||||
|
||||
## 設計原則
|
||||
|
||||
**最少改動策略 (Minimal Viable Change)**
|
||||
|
||||
Gitea 與 GitHub 的 webhook payload 欄位幾乎相同,差異只在 HTTP header 名稱。
|
||||
因此策略是:重命名 + Header 常數替換,業務邏輯層(OpenClaw 呼叫、Redis 儲存、Telegram 通知、Approval 建立)完全不動。
|
||||
|
||||
---
|
||||
|
||||
## Header 差異對照
|
||||
|
||||
| 項目 | GitHub (廢棄) | Gitea (新) |
|
||||
|------|-------------|-----------|
|
||||
| 事件類型 Header | `X-GitHub-Event` | `X-Gitea-Event` |
|
||||
| 簽章 Header | `X-Hub-Signature-256` | `X-Gitea-Signature` |
|
||||
| 投遞 ID Header | `X-GitHub-Delivery` | `X-Gitea-Delivery` |
|
||||
| 簽章格式 | `sha256=<hex>` | `sha256=<hex>` ← **相同** |
|
||||
| Payload 欄位 | — | — ← **幾乎相同** |
|
||||
|
||||
> Gitea 會同時送出 `X-GitHub-Event` 相容 header,但我們應讀取原生的 `X-Gitea-Event`,
|
||||
> 語意清晰,未來不依賴相容層。
|
||||
|
||||
---
|
||||
|
||||
## 遷移範圍
|
||||
|
||||
### 支援事件
|
||||
|
||||
| 事件 | GitHub (舊) | Gitea (新) | 說明 |
|
||||
|------|------------|-----------|------|
|
||||
| `pull_request` | ✅ | ✅ 遷移 | PR 代碼審查 |
|
||||
| `push` | ✅ | ✅ 遷移 | 主分支推送審查 |
|
||||
| `workflow_run` | ✅ | ❌ 廢棄 | CD pipeline 已有 TG 通知,重複覆蓋無必要 |
|
||||
| `ping` | ✅ | ✅ 遷移 | 連線測試 |
|
||||
|
||||
### 檔案變更清單
|
||||
|
||||
| 檔案 | 動作 | 說明 |
|
||||
|------|------|------|
|
||||
| `src/api/v1/github_webhook.py` | 重命名 → `gitea_webhook.py` | Header 常數改 Gitea,移除 `workflow_run` handler |
|
||||
| `src/services/github_webhook_service.py` | 重命名 → `gitea_webhook_service.py` | 移除 `diagnose_ci_failure()`,class 改名 |
|
||||
| `src/main.py` | 修改 | 更新 import,掛載路徑改 `/webhooks/gitea` |
|
||||
| `src/core/config.py` | 修改 | `GITHUB_WEBHOOK_SECRET` → `GITEA_WEBHOOK_SECRET`,`GITHUB_ALLOWED_REPOS` → `GITEA_ALLOWED_REPOS` |
|
||||
| `k8s/awoooi-prod/03-secrets.yaml` | 修改 | 新增 `GITEA_WEBHOOK_SECRET`,移除 `GITHUB_WEBHOOK_SECRET` |
|
||||
| `tests/conftest.py` | 修改 | 更新 env var 名稱 |
|
||||
| `tests/test_github_webhook.py` | 重命名 → `test_gitea_webhook.py` | Header 常數改 Gitea |
|
||||
| `docs/adr/ADR-059-gitea-webhook-integration.md` | 新增 | 決策記錄 |
|
||||
|
||||
---
|
||||
|
||||
## API 端點
|
||||
|
||||
```
|
||||
舊: POST /api/v1/webhooks/github
|
||||
新: POST /api/v1/webhooks/gitea
|
||||
|
||||
舊: GET /api/v1/webhooks/github/reviews/{review_id}
|
||||
新: GET /api/v1/webhooks/gitea/reviews/{review_id}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config 變更
|
||||
|
||||
```python
|
||||
# 舊 (廢棄)
|
||||
GITHUB_WEBHOOK_SECRET: str = Field(default="")
|
||||
GITHUB_ALLOWED_REPOS: str = Field(default="")
|
||||
|
||||
# 新
|
||||
GITEA_WEBHOOK_SECRET: str = Field(
|
||||
default="",
|
||||
description="Gitea Webhook secret for HMAC-SHA256 signature verification (X-Gitea-Signature)",
|
||||
)
|
||||
GITEA_ALLOWED_REPOS: str = Field(
|
||||
default="wooo/awoooi",
|
||||
description="Comma-separated list of allowed Gitea repositories (e.g., 'wooo/awoooi')",
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 防禦性 Payload 解析(首席架構師護欄 #1)
|
||||
|
||||
Gitea 部分欄位為 nullable 或可能缺失。所有欄位存取使用 `.get()` chain,禁止直接 key access:
|
||||
|
||||
```python
|
||||
# ❌ 禁止
|
||||
repo_name = payload["repository"]["full_name"]
|
||||
|
||||
# ✅ 正確
|
||||
repo_name = payload.get("repository", {}).get("full_name", "")
|
||||
pr_number = payload.get("pull_request", {}).get("number", 0)
|
||||
head_sha = payload.get("pull_request", {}).get("head", {}).get("sha", "")
|
||||
```
|
||||
|
||||
現有 `github_webhook_service.py` 中已部分採用此模式,遷移時需全面審查確認。
|
||||
|
||||
---
|
||||
|
||||
## Gitea UI 設定步驟(首席架構師護欄 #2)
|
||||
|
||||
部署完成後在 Gitea 設定 Webhook:
|
||||
|
||||
1. 進入 `http://192.168.0.110:3001/wooo/awoooi` → Settings → Webhooks → Add Webhook → Gitea
|
||||
2. **Target URL**: `https://awoooi.wooo.work/api/v1/webhooks/gitea`
|
||||
3. **HTTP Method**: POST
|
||||
4. **Content Type**: `application/json` ← **必須明確選擇,否則 FastAPI 無法解析**
|
||||
5. **Secret**: 與 `GITEA_WEBHOOK_SECRET` K8s Secret 值相同
|
||||
6. **Trigger On**: Pull Request events + Push events
|
||||
7. **Active**: 勾選
|
||||
|
||||
> ⚠️ Content-Type 若選 `application/x-www-form-urlencoded`,FastAPI 的 `Request.json()`
|
||||
> 將無法解析,導致所有 webhook 請求失敗。
|
||||
|
||||
---
|
||||
|
||||
## 簽章驗證邏輯
|
||||
|
||||
驗證邏輯與 GitHub 完全相同,只需更換 header 名稱:
|
||||
|
||||
```python
|
||||
# 舊 header
|
||||
x_hub_signature_256: str | None = Header(default=None, alias="X-Hub-Signature-256")
|
||||
|
||||
# 新 header
|
||||
x_gitea_signature: str | None = Header(default=None, alias="X-Gitea-Signature")
|
||||
```
|
||||
|
||||
HMAC-SHA256 計算方式不變:`sha256=<hmac_sha256(secret, body).hexdigest()>`
|
||||
|
||||
---
|
||||
|
||||
## K8s Secrets 注入
|
||||
|
||||
`k8s/awoooi-prod/03-secrets.yaml` 新增:
|
||||
```yaml
|
||||
GITEA_WEBHOOK_SECRET: "CHANGE_ME" # CD 自動注入
|
||||
```
|
||||
|
||||
`cd.yaml` 的 `Inject K8s Secrets` step 需新增注入:
|
||||
```bash
|
||||
kubectl patch secret awoooi-secrets \
|
||||
--patch "{\"stringData\": {\"GITEA_WEBHOOK_SECRET\": \"${{ secrets.GITEA_WEBHOOK_SECRET }}\"}}"
|
||||
```
|
||||
|
||||
Gitea Actions secrets 需新增 `GITEA_WEBHOOK_SECRET`。
|
||||
|
||||
---
|
||||
|
||||
## 測試策略
|
||||
|
||||
`test_gitea_webhook.py` 複用現有 8 個測試案例,更新:
|
||||
1. Header 名稱:`X-Hub-Signature-256` → `X-Gitea-Signature`、`X-GitHub-Event` → `X-Gitea-Event`
|
||||
2. Router import:`from src.api.v1.gitea_webhook import router as gitea_webhook_router`
|
||||
3. `conftest.py`:`GITHUB_WEBHOOK_SECRET` → `GITEA_WEBHOOK_SECRET`、`GITHUB_ALLOWED_REPOS` → `GITEA_ALLOWED_REPOS`
|
||||
|
||||
---
|
||||
|
||||
## 不在此 Scope
|
||||
|
||||
- Gitea 端實際 webhook secret 的生成與管理(統帥決定)
|
||||
- `workflow_run` 等值替代方案(未來 Phase 規劃)
|
||||
- GitHub mirror repo 的 webhook 設定清除(可手動處理)
|
||||
Reference in New Issue
Block a user