- 停用所有 GitHub Actions workflows (.disabled) - 更新 CLAUDE.md 添加 Gitea CI/CD 章節 - 更新 LOGBOOK.md 記錄遷移狀態 - Gitea 版本: 1.25.5 - Runner 版本: v0.3.1 (host 網絡模式) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
415 lines
14 KiB
Plaintext
415 lines
14 KiB
Plaintext
# =============================================================================
|
||
# AWOOOI CI Pipeline v2.0 (沿用 AIOPS 最佳實踐)
|
||
# =============================================================================
|
||
|
||
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
pull_request:
|
||
branches: [main]
|
||
workflow_dispatch:
|
||
|
||
concurrency:
|
||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
NODE_VERSION: '20'
|
||
PNPM_VERSION: '9'
|
||
PYTHON_VERSION: '3.11'
|
||
# OTEL CI/CD 監控 (2026-03-24 批准, 2026-03-28 修正: SignOz 在 188)
|
||
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.188:24318
|
||
OTEL_SERVICE_NAME: awoooi-ci
|
||
OTEL_RESOURCE_ATTRIBUTES: service.version=${{ github.sha }},deployment.environment=ci
|
||
|
||
jobs:
|
||
# ==================== Pre-flight (10s Fail-Fast) ====================
|
||
pre-flight:
|
||
name: "Pre-flight"
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
timeout-minutes: 1
|
||
steps:
|
||
# 2026-03-29 Claude Code: 修復 _diag/pages 檔案衝突
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
|
||
- name: Quick sanity check
|
||
run: |
|
||
echo "✅ Runner 可用"
|
||
node --version || echo "⚠️ Node not found"
|
||
python3 --version || echo "⚠️ Python not found"
|
||
|
||
# ==================== Lint & Type Check ====================
|
||
lint:
|
||
name: Lint & Type Check
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: pre-flight
|
||
timeout-minutes: 10
|
||
steps:
|
||
# 2026-03-29 Claude Code: 修復 _diag/pages 檔案衝突
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
clean: true
|
||
|
||
# 清理 worktrees (在 checkout 後,確保在 workspace 內)
|
||
- name: Clean worktrees
|
||
run: rm -rf ${{ github.workspace }}/.claude/worktrees 2>/dev/null || true
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v3
|
||
with:
|
||
version: ${{ env.PNPM_VERSION }}
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'pnpm'
|
||
|
||
- name: Install dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Lint
|
||
run: pnpm lint
|
||
|
||
- name: Type check
|
||
run: pnpm typecheck
|
||
|
||
# Phase 14.2: 依賴治理 (2026-03-26)
|
||
- name: Dependency Check
|
||
run: pnpm dep-check
|
||
|
||
- name: ADR Compliance Check
|
||
run: |
|
||
echo "🔍 檢查 ADR 規定..."
|
||
# 檢查 1: 前端禁止直連資料庫 (ADR-005)
|
||
if grep -rE "psycopg2|asyncpg|redis|sqlalchemy|pg|ioredis" apps/web/src/ 2>/dev/null; then
|
||
echo "❌ ADR-005 違規: 前端禁止直連資料庫"
|
||
exit 1
|
||
fi
|
||
# 檢查 2: 禁止 Redux (ADR-004)
|
||
if grep -rE "@reduxjs/toolkit|react-redux" apps/web/package.json 2>/dev/null; then
|
||
echo "❌ ADR-004 違規: 禁止 Redux"
|
||
exit 1
|
||
fi
|
||
# 檢查 3: 禁止 import 舊專案
|
||
if grep -rE "from ['\"].*wooo-aiops" apps/ packages/ 2>/dev/null; then
|
||
echo "❌ 禁止 import 舊專案"
|
||
exit 1
|
||
fi
|
||
echo "✅ ADR 檢查通過"
|
||
|
||
# Phase 14.2 #96: 後端分層檢查 (輕量版 import-linter)
|
||
- name: API Layer Check
|
||
run: |
|
||
echo "🔍 檢查後端分層規則..."
|
||
# 檢查 1: services 層禁止引用 api/routers 層 (反向依賴)
|
||
if grep -rE "from src\.(api|routers)" apps/api/src/services/ 2>/dev/null; then
|
||
echo "❌ 分層違規: services 禁止引用 api/routers 層"
|
||
exit 1
|
||
fi
|
||
# 檢查 2: repositories 層禁止引用 services 層
|
||
if grep -rE "from src\.services" apps/api/src/repositories/ 2>/dev/null; then
|
||
echo "❌ 分層違規: repositories 禁止引用 services 層"
|
||
exit 1
|
||
fi
|
||
# 檢查 3: models 層禁止引用業務層
|
||
if grep -rE "from src\.(services|api|routers)" apps/api/src/models/ 2>/dev/null; then
|
||
echo "❌ 分層違規: models 禁止引用業務層"
|
||
exit 1
|
||
fi
|
||
echo "✅ 後端分層檢查通過"
|
||
|
||
# ==================== Test ====================
|
||
test:
|
||
name: Test
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: lint
|
||
timeout-minutes: 15
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v3
|
||
with:
|
||
version: ${{ env.PNPM_VERSION }}
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'pnpm'
|
||
|
||
- name: Install dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Run tests
|
||
run: pnpm test
|
||
continue-on-error: true
|
||
|
||
- name: Upload coverage
|
||
uses: codecov/codecov-action@v4
|
||
with:
|
||
token: ${{ secrets.CODECOV_TOKEN }}
|
||
fail_ci_if_error: false
|
||
|
||
# ==================== Build ====================
|
||
build:
|
||
name: Build
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: lint
|
||
timeout-minutes: 15
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v3
|
||
with:
|
||
version: ${{ env.PNPM_VERSION }}
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: 'pnpm'
|
||
|
||
- name: Install dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Setup Turborepo Cache
|
||
uses: dtinth/setup-github-actions-caching-for-turbo@v1
|
||
|
||
- name: Build packages
|
||
env:
|
||
NEXT_PUBLIC_API_URL: https://awoooi.wooo.work
|
||
# Sentry DSN (透過 /api/sentry-tunnel 避免區域網路權限問題)
|
||
NEXT_PUBLIC_SENTRY_DSN: http://da02d4e5d6542e4d1ed6b2dd6542efeb@192.168.0.110:9000/2
|
||
run: pnpm turbo build
|
||
|
||
- name: Upload build artifacts
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: build-artifacts
|
||
path: |
|
||
apps/*/dist
|
||
packages/*/dist
|
||
retention-days: 7
|
||
|
||
# ==================== API Lint (Python) ====================
|
||
api-lint:
|
||
name: API Lint
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: pre-flight
|
||
timeout-minutes: 5
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v5
|
||
with:
|
||
python-version: ${{ env.PYTHON_VERSION }}
|
||
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v3
|
||
|
||
- name: Install & Lint
|
||
working-directory: apps/api
|
||
run: |
|
||
uv sync
|
||
uv run ruff check .
|
||
|
||
- name: Type check
|
||
working-directory: apps/api
|
||
run: uv run mypy src/ --exclude 'tests/|scripts/' || true
|
||
continue-on-error: true
|
||
|
||
# ==================== API Test ====================
|
||
api-test:
|
||
name: API Test
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: api-lint
|
||
timeout-minutes: 10
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v5
|
||
with:
|
||
python-version: ${{ env.PYTHON_VERSION }}
|
||
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v3
|
||
|
||
- name: Install & Test
|
||
working-directory: apps/api
|
||
env:
|
||
PYTHONPATH: ${{ github.workspace }}/apps/api
|
||
run: |
|
||
uv sync
|
||
uv run pytest tests/ --cov=src --cov-report=xml -v || true
|
||
continue-on-error: true
|
||
|
||
# ==================== Ollama Model Test (Phase 12.3 #67) ====================
|
||
# 🤖 自動化模型回歸測試 - 確保 OpenClaw 提案品質
|
||
ollama-test:
|
||
name: Ollama Model Test
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: api-lint
|
||
timeout-minutes: 5
|
||
continue-on-error: true # 不阻塞主 Pipeline
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Test Ollama Connectivity
|
||
run: |
|
||
echo "🔗 測試 Ollama 連線..."
|
||
if curl -s --connect-timeout 5 http://192.168.0.188:11434/api/tags > /dev/null; then
|
||
echo "✅ Ollama 可達"
|
||
# 使用 Python 替代 jq (Runner 未安裝 jq)
|
||
curl -s http://192.168.0.188:11434/api/tags | python3 -c "import sys,json; [print(m['name']) for m in json.load(sys.stdin).get('models',[])]"
|
||
else
|
||
echo "⚠️ Ollama 無法連線 (192.168.0.188:11434)"
|
||
exit 0 # 不失敗,只警告
|
||
fi
|
||
|
||
- name: Model Smoke Test
|
||
run: |
|
||
echo "🧪 模型冒煙測試..."
|
||
# 使用 Python 替代 jq
|
||
RESPONSE=$(curl -s --max-time 60 http://192.168.0.188:11434/api/generate -d '{
|
||
"model": "qwen2.5:7b-instruct",
|
||
"prompt": "你是 AIOps 助手。回答:1+1=?",
|
||
"stream": false
|
||
}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('response','ERROR'))" 2>/dev/null || echo "ERROR")
|
||
|
||
if [ "$RESPONSE" != "ERROR" ] && [ -n "$RESPONSE" ]; then
|
||
echo "✅ 模型回應正常"
|
||
echo "回應: $RESPONSE"
|
||
else
|
||
echo "⚠️ 模型回應異常"
|
||
fi
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v5
|
||
with:
|
||
python-version: ${{ env.PYTHON_VERSION }}
|
||
|
||
- name: Install uv
|
||
uses: astral-sh/setup-uv@v3
|
||
|
||
- name: Action Parsing Test
|
||
working-directory: apps/api
|
||
env:
|
||
PYTHONPATH: ${{ github.workspace }}/apps/api
|
||
run: |
|
||
echo "🔍 Action Parsing 回歸測試..."
|
||
uv sync
|
||
uv run pytest tests/test_action_parsing.py -v --tb=short || echo "⚠️ 部分測試失敗"
|
||
|
||
# 🔴 LLM 測試移至 Nightly (2026-03-26)
|
||
# 原因: CPU 推理需 300+ 秒/測試,不適合每次 commit
|
||
# 完整測試: .github/workflows/nightly-llm.yaml
|
||
- name: LLM Test (Skip - See Nightly)
|
||
run: |
|
||
echo "⏭️ LLM 測試已移至 Nightly workflow"
|
||
echo " - test_model_regression.py"
|
||
echo " - test_prompt_validation.py"
|
||
echo " 原因: CPU 推理 ~300s/測試"
|
||
|
||
# ==================== OpenAPI Validation ====================
|
||
openapi-validate:
|
||
name: OpenAPI Validate
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: pre-flight
|
||
timeout-minutes: 3
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
|
||
- name: Validate
|
||
run: |
|
||
# 修復 ENOTEMPTY npm 錯誤 - 先清理舊目錄
|
||
rm -rf $(npm root -g)/@stoplight/spectral-cli 2>/dev/null || true
|
||
npm install -g @stoplight/spectral-cli || npm install -g @stoplight/spectral-cli
|
||
spectral lint docs/api/api-contract.yaml || true
|
||
|
||
# ==================== Docker Build Verify ====================
|
||
# 🚀 優化 (2026-03-24): 只在 PR 時執行,main push 跳過 (CD 會構建)
|
||
docker-build:
|
||
name: Docker Verify
|
||
runs-on: [self-hosted, harbor, k8s]
|
||
needs: [test, api-test, build]
|
||
if: github.event_name == 'pull_request'
|
||
timeout-minutes: 20
|
||
strategy:
|
||
matrix:
|
||
app: [web, api]
|
||
steps:
|
||
- name: "Clean Runner Diagnostics"
|
||
run: |
|
||
RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
|
||
rm -rf "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: Build (no push)
|
||
uses: docker/build-push-action@v5
|
||
with:
|
||
context: .
|
||
file: apps/${{ matrix.app }}/Dockerfile
|
||
push: false
|
||
tags: awoooi-${{ matrix.app }}:test
|
||
build-args: |
|
||
NEXT_PUBLIC_API_URL=https://awoooi.wooo.work
|
||
NEXT_PUBLIC_SENTRY_DSN=http://da02d4e5d6542e4d1ed6b2dd6542efeb@192.168.0.110:9000/2
|
||
cache-from: type=gha
|
||
cache-to: type=gha,mode=max
|