- Add verify-image job in release.yml with: - Headless environment verification (DISPLAY, Xvfb, LibreOffice, Inkscape, Calibre) - Model download verification (HuggingFace cache, ONNX models, offline mode) - Basic functionality verification (container startup, tool availability) - Add Test 3 and Test 4 in upstream-sync.yml for headless/model verification - Update auto-upstream-sync.yml to use git merge --no-ff for better history tracking
495 lines
16 KiB
YAML
495 lines
16 KiB
YAML
# ==============================================================================
|
||
# Upstream Sync Build + Smoke Test
|
||
# 功能:
|
||
# 1. dev 分支有 push 時自動觸發
|
||
# 2. 建構 Docker image(僅 amd64 用於測試)
|
||
# 3. 執行 smoke test(10 分鐘內完成)
|
||
# 4. 測試通過後推送 multi-arch image
|
||
# ==============================================================================
|
||
|
||
name: Upstream Sync Build
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- dev
|
||
|
||
env:
|
||
DOCKER_IMAGE: convertx/convertx-cn
|
||
TEST_IMAGE: convertx-cn-test
|
||
# Smoke test 超時時間(秒)
|
||
SMOKE_TEST_TIMEOUT: 600
|
||
|
||
concurrency:
|
||
group: upstream-sync-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
# ==========================================================================
|
||
# Job 1: 快速建構測試用 image(僅 amd64)
|
||
# ==========================================================================
|
||
build-test-image:
|
||
name: Build Test Image
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
image_tag: ${{ steps.date.outputs.date }}
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
# ========================================
|
||
# 清理磁碟空間(解決 no space left on device)
|
||
# ========================================
|
||
- name: Free disk space
|
||
run: |
|
||
echo "🧹 Cleaning up disk space..."
|
||
echo "Before cleanup:"
|
||
df -h
|
||
|
||
# 移除不需要的預裝軟體
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /usr/local/lib/android
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||
sudo rm -rf /usr/local/share/boost
|
||
sudo rm -rf /usr/share/swift
|
||
|
||
# 清理 apt cache
|
||
sudo apt-get clean
|
||
sudo apt-get autoremove -y
|
||
|
||
# 清理 Docker 舊資料
|
||
docker system prune -af --volumes || true
|
||
|
||
echo ""
|
||
echo "After cleanup:"
|
||
df -h
|
||
|
||
- name: Get current date
|
||
id: date
|
||
run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: Build test image (amd64 only)
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
push: false
|
||
load: true
|
||
tags: ${{ env.TEST_IMAGE }}:test
|
||
platforms: linux/amd64
|
||
cache-from: type=gha
|
||
cache-to: type=gha,mode=max
|
||
|
||
- name: Save Docker image
|
||
run: |
|
||
docker save ${{ env.TEST_IMAGE }}:test -o /tmp/test-image.tar
|
||
|
||
- name: Upload test image
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: test-image
|
||
path: /tmp/test-image.tar
|
||
retention-days: 1
|
||
|
||
# ==========================================================================
|
||
# Job 2: Smoke Test(必須在 10 分鐘內完成)
|
||
# ==========================================================================
|
||
smoke-test:
|
||
name: Smoke Test
|
||
runs-on: ubuntu-latest
|
||
needs: build-test-image
|
||
timeout-minutes: 10
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Download test image
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
name: test-image
|
||
path: /tmp
|
||
|
||
- name: Load Docker image
|
||
run: |
|
||
docker load -i /tmp/test-image.tar
|
||
|
||
- name: Create test files
|
||
run: |
|
||
mkdir -p /tmp/smoke-test
|
||
|
||
# 建立極小的測試檔案 (<1KB)
|
||
echo "Hello, ConvertX Smoke Test!" > /tmp/smoke-test/test.txt
|
||
echo "這是中文測試檔案" >> /tmp/smoke-test/test.txt
|
||
|
||
# ========================================
|
||
# Test 1: Container 啟動測試
|
||
# ========================================
|
||
- name: "Test 1: Container startup"
|
||
id: test_startup
|
||
run: |
|
||
echo "🚀 Testing container startup..."
|
||
|
||
# 啟動 container(背景執行)
|
||
docker run -d \
|
||
--name convertx-test \
|
||
-p 3000:3000 \
|
||
-v /tmp/smoke-test:/data \
|
||
-e DATA_DIR=/data \
|
||
${{ env.TEST_IMAGE }}:test
|
||
|
||
# 等待啟動(最多 60 秒)
|
||
echo "⏳ Waiting for container to start..."
|
||
for i in {1..60}; do
|
||
if docker exec convertx-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
|
||
echo "✅ Container started successfully in ${i}s"
|
||
echo "startup_time=${i}" >> $GITHUB_OUTPUT
|
||
exit 0
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
echo "❌ Container failed to start within 60s"
|
||
docker logs convertx-test
|
||
exit 1
|
||
|
||
# ========================================
|
||
# Test 2: 關鍵工具存在性檢查
|
||
# ========================================
|
||
- name: "Test 2: Tool availability check"
|
||
id: test_tools
|
||
run: |
|
||
echo "🔧 Checking critical tools..."
|
||
|
||
FAILED=0
|
||
|
||
# LibreOffice
|
||
echo -n " libreoffice: "
|
||
if docker exec convertx-test libreoffice --version 2>/dev/null; then
|
||
echo "✅"
|
||
else
|
||
echo "❌ FAILED"
|
||
FAILED=1
|
||
fi
|
||
|
||
# Pandoc
|
||
echo -n " pandoc: "
|
||
if docker exec convertx-test pandoc --version 2>/dev/null | head -1; then
|
||
echo "✅"
|
||
else
|
||
echo "❌ FAILED"
|
||
FAILED=1
|
||
fi
|
||
|
||
# FFmpeg
|
||
echo -n " ffmpeg: "
|
||
if docker exec convertx-test ffmpeg -version 2>/dev/null | head -1; then
|
||
echo "✅"
|
||
else
|
||
echo "❌ FAILED"
|
||
FAILED=1
|
||
fi
|
||
|
||
# Tesseract
|
||
echo -n " tesseract: "
|
||
if docker exec convertx-test tesseract --version 2>/dev/null | head -1; then
|
||
echo "✅"
|
||
else
|
||
echo "❌ FAILED"
|
||
FAILED=1
|
||
fi
|
||
|
||
# 額外檢查更多工具
|
||
echo ""
|
||
echo "🔧 Checking additional tools..."
|
||
|
||
# ImageMagick
|
||
echo -n " imagemagick: "
|
||
if docker exec convertx-test magick -version 2>/dev/null | head -1; then
|
||
echo "✅"
|
||
else
|
||
echo "⚠️ Warning (optional)"
|
||
fi
|
||
|
||
# Inkscape
|
||
echo -n " inkscape: "
|
||
if docker exec convertx-test inkscape --version 2>/dev/null; then
|
||
echo "✅"
|
||
else
|
||
echo "⚠️ Warning (optional)"
|
||
fi
|
||
|
||
if [ $FAILED -eq 1 ]; then
|
||
echo ""
|
||
echo "❌ Critical tool check failed!"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "✅ All critical tools available"
|
||
|
||
# ========================================
|
||
# Test 3: Headless 環境驗證
|
||
# ========================================
|
||
- name: "Test 3: Headless environment verification"
|
||
run: |
|
||
echo "🖥️ Verifying headless environment..."
|
||
|
||
# 檢查 DISPLAY 環境變數
|
||
echo ""
|
||
echo "📋 檢查 DISPLAY 環境變數..."
|
||
DISPLAY_VAR=$(docker exec convertx-test printenv DISPLAY 2>/dev/null || echo "")
|
||
if [ -z "$DISPLAY_VAR" ]; then
|
||
echo "✅ DISPLAY 未設定(headless 模式正確)"
|
||
else
|
||
echo "⚠️ DISPLAY 已設定: ${DISPLAY_VAR}"
|
||
fi
|
||
|
||
# 檢查 Xvfb
|
||
echo ""
|
||
echo "📋 檢查 Xvfb..."
|
||
if docker exec convertx-test which Xvfb 2>/dev/null; then
|
||
echo "⚠️ Xvfb 存在(但應不需要)"
|
||
else
|
||
echo "✅ Xvfb 不存在(headless 模式正確)"
|
||
fi
|
||
|
||
# 驗證 LibreOffice headless
|
||
echo ""
|
||
echo "📋 驗證 LibreOffice headless..."
|
||
if docker exec convertx-test libreoffice --headless --version 2>/dev/null | head -1; then
|
||
echo "✅ LibreOffice headless 模式可用"
|
||
else
|
||
echo "❌ LibreOffice headless 模式失敗"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "✅ Headless environment verification passed"
|
||
|
||
# ========================================
|
||
# Test 4: 模型下載驗證
|
||
# ========================================
|
||
- name: "Test 4: Model download verification"
|
||
run: |
|
||
echo "🤖 Verifying model downloads..."
|
||
|
||
# 檢查 HuggingFace cache
|
||
echo ""
|
||
echo "📋 檢查 HuggingFace cache..."
|
||
HF_CACHE=$(docker exec convertx-test ls -la /root/.cache/huggingface/ 2>/dev/null || echo "NOT_FOUND")
|
||
if [ "$HF_CACHE" != "NOT_FOUND" ]; then
|
||
echo "✅ HuggingFace cache 存在"
|
||
else
|
||
echo "⚠️ HuggingFace cache 不存在(模型可能在其他位置)"
|
||
fi
|
||
|
||
# 檢查 ONNX 模型
|
||
echo ""
|
||
echo "📋 檢查 ONNX 模型..."
|
||
ONNX_COUNT=$(docker exec convertx-test find /root -name "*.onnx" -type f 2>/dev/null | wc -l || echo "0")
|
||
if [ "$ONNX_COUNT" -gt 0 ]; then
|
||
echo "✅ 找到 ${ONNX_COUNT} 個 ONNX 模型"
|
||
else
|
||
echo "⚠️ 未找到 ONNX 模型(可能動態下載)"
|
||
fi
|
||
|
||
# 檢查離線模式環境變數
|
||
echo ""
|
||
echo "📋 檢查離線模式環境變數..."
|
||
HF_OFFLINE=$(docker exec convertx-test printenv HF_HUB_OFFLINE 2>/dev/null || echo "")
|
||
if [ "$HF_OFFLINE" = "1" ]; then
|
||
echo "✅ HF_HUB_OFFLINE=1(離線模式已啟用)"
|
||
else
|
||
echo "⚠️ HF_HUB_OFFLINE 未設定"
|
||
fi
|
||
|
||
echo ""
|
||
echo "✅ Model verification completed"
|
||
|
||
# ========================================
|
||
# Test 5: 最小轉換測試 (txt → pdf)
|
||
# ========================================
|
||
- name: "Test 5: Minimal conversion test"
|
||
id: test_conversion
|
||
run: |
|
||
echo "📄 Testing minimal conversion (txt → pdf)..."
|
||
|
||
# 使用 API 進行轉換測試
|
||
# 先上傳檔案
|
||
echo " Uploading test file..."
|
||
UPLOAD_RESPONSE=$(curl -sf -X POST \
|
||
-F "file=@/tmp/smoke-test/test.txt" \
|
||
http://localhost:3000/upload 2>&1) || {
|
||
echo "❌ Upload failed"
|
||
echo "Response: $UPLOAD_RESPONSE"
|
||
exit 1
|
||
}
|
||
|
||
echo " Upload response: $UPLOAD_RESPONSE"
|
||
|
||
# 檢查 healthcheck 確認服務正常
|
||
echo " Checking service health..."
|
||
HEALTH=$(curl -sf http://localhost:3000/healthcheck)
|
||
echo " Health: $HEALTH"
|
||
|
||
# 使用 LibreOffice 直接測試轉換能力
|
||
echo " Testing LibreOffice conversion capability..."
|
||
docker exec convertx-test sh -c "
|
||
cd /tmp && \
|
||
echo 'Test document for smoke test' > test.txt && \
|
||
libreoffice --headless --convert-to pdf test.txt && \
|
||
ls -la test.pdf
|
||
" && echo "✅ Conversion test passed" || {
|
||
echo "❌ Conversion test failed"
|
||
exit 1
|
||
}
|
||
|
||
# ========================================
|
||
# Test 6: API 端點檢查
|
||
# ========================================
|
||
- name: "Test 6: API endpoints check"
|
||
run: |
|
||
echo "🌐 Checking API endpoints..."
|
||
|
||
# Healthcheck
|
||
echo -n " /healthcheck: "
|
||
if curl -sf http://localhost:3000/healthcheck > /dev/null; then
|
||
echo "✅"
|
||
else
|
||
echo "❌"
|
||
exit 1
|
||
fi
|
||
|
||
# Root page
|
||
echo -n " / (root): "
|
||
if curl -sf http://localhost:3000/ > /dev/null; then
|
||
echo "✅"
|
||
else
|
||
echo "❌"
|
||
exit 1
|
||
fi
|
||
|
||
# Converters list
|
||
echo -n " /converters: "
|
||
if curl -sf http://localhost:3000/converters > /dev/null; then
|
||
echo "✅"
|
||
else
|
||
echo "⚠️ (may require auth)"
|
||
fi
|
||
|
||
echo ""
|
||
echo "✅ API endpoints check passed"
|
||
|
||
- name: Cleanup test container
|
||
if: always()
|
||
run: |
|
||
docker stop convertx-test || true
|
||
docker rm convertx-test || true
|
||
|
||
- name: Smoke test summary
|
||
if: always()
|
||
run: |
|
||
echo "## 🧪 Smoke Test Results" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 測試項目 | 狀態 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "|----------|------|" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Container 啟動 | ✅ 通過 (${startup_time:-N/A}s) |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 工具檢查 (libreoffice, pandoc, ffmpeg, tesseract) | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Headless 環境驗證 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 模型下載驗證 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 最小轉換測試 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| API 端點檢查 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY
|
||
|
||
# ==========================================================================
|
||
# Job 3: 測試通過後,建構並推送 multi-arch image
|
||
# ==========================================================================
|
||
build-and-push:
|
||
name: Build & Push Multi-arch Image
|
||
runs-on: ubuntu-latest
|
||
needs: [build-test-image, smoke-test]
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
# ========================================
|
||
# 清理磁碟空間(multi-arch build 需要更多空間)
|
||
# ========================================
|
||
- name: Free disk space
|
||
run: |
|
||
echo "🧹 Cleaning up disk space for multi-arch build..."
|
||
echo "Before cleanup:"
|
||
df -h
|
||
|
||
# 移除不需要的預裝軟體
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /usr/local/lib/android
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||
sudo rm -rf /usr/local/share/boost
|
||
sudo rm -rf /usr/share/swift
|
||
sudo rm -rf /opt/hostedtoolcache
|
||
|
||
# 清理 apt cache
|
||
sudo apt-get clean
|
||
sudo apt-get autoremove -y
|
||
|
||
# 清理 Docker 舊資料
|
||
docker system prune -af --volumes || true
|
||
|
||
echo ""
|
||
echo "After cleanup:"
|
||
df -h
|
||
|
||
- name: Get current date
|
||
id: date
|
||
run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
|
||
|
||
- name: Set up QEMU
|
||
uses: docker/setup-qemu-action@v3
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Build and push Docker image
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
push: true
|
||
tags: ${{ env.DOCKER_IMAGE }}:upstream-${{ steps.date.outputs.date }}
|
||
platforms: linux/amd64,linux/arm64
|
||
cache-from: type=gha
|
||
cache-to: type=gha,mode=max
|
||
|
||
- name: Summary
|
||
run: |
|
||
echo "## 🐳 Docker Image Published" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Image:** \`${{ env.DOCKER_IMAGE }}:upstream-${{ steps.date.outputs.date }}\`" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### ✅ Smoke Test 已通過" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 測試項目 | 狀態 |" >> $GITHUB_STEP_SUMMARY
|
||
echo "|----------|------|" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Container 啟動 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 工具存在性檢查 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| Headless 環境驗證 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 模型下載驗證 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| 最小轉換測試 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "| API 端點檢查 | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||
echo "⚠️ 這是 upstream 同步版本。請使用 version tags 取得穩定版本。" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "❌ **不會更新 \`latest\` tag**" >> $GITHUB_STEP_SUMMARY
|