chore(ci): enable Gitea Actions workflows
This commit is contained in:
414
.github/workflows/ci.yaml
vendored
Normal file
414
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
Reference in New Issue
Block a user