Skip to content

chore(docker): trigger rebuild of latest and dev images #429

chore(docker): trigger rebuild of latest and dev images

chore(docker): trigger rebuild of latest and dev images #429

# ===================================
# API Monitor Docker Image CI/CD (Trigger Rebuild)
# ===================================
# 触发条件:
# - 推送到 main 分支 → latest 标签
# - 推送到其他分支 → dev 标签
# - 创建版本标签(如 v1.0.0)
# - 手动触发(workflow_dispatch)
#
# 构建策略:
# - amd64 和 arm64 分别在原生 runner 上构建(避免 QEMU 问题)
# - 使用 docker manifest 合并多架构镜像
name: Build and Publish Docker Image
on:
push:
branches: ["**"] # 所有分支
tags: ["v*"]
paths-ignore:
- "**.md"
- "docs/**"
- ".gitignore"
- "LICENSE"
workflow_dispatch:
inputs:
tag:
description: "镜像标签(可选)"
required: false
default: "latest"
platforms:
description: "构建平台(可选,逗号分隔)"
required: false
default: "linux/amd64,linux/arm64"
permissions:
contents: read
packages: write
security-events: write
env:
IMAGE_NAME: api-monitor
REGISTRY: ghcr.io
DOCKERHUB_REPO: ${{ github.repository_owner }}/api-monitor
jobs:
# ==========================================
# 阶段 1: 分架构构建(原生 runner,避免 QEMU)
# ==========================================
build:
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
suffix: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
suffix: arm64
runs-on: ${{ matrix.runner }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download prebuilt agent binaries
shell: bash
run: |
VERSION=$(grep '^version' agent-rust/Cargo.toml | head -n1 | cut -d'"' -f2)
echo "Target version: v$VERSION"
mkdir -p agent-rust
download_binary() {
local file=$1
local url="https://github.com/iwvw/API-Monitor/releases/download/v${VERSION}/${file}"
local fallback_url="https://github.com/iwvw/API-Monitor/releases/latest/download/${file}"
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
echo "This is a release tag build (${{ github.ref }}). Waiting for ${file} to be uploaded to release v${VERSION}..."
# Poll for up to 20 minutes (40 attempts * 30 seconds)
for i in {1..40}; do
if curl -fsSL -o "agent-rust/${file}" "$url"; then
echo "Successfully downloaded ${file} from release v${VERSION}."
return 0
fi
rm -f "agent-rust/${file}"
echo "Asset ${file} not available yet, waiting 30s ($i/40)..."
sleep 30
done
echo "Timeout waiting for ${file} on release v${VERSION}."
echo "Attempting fallback to latest release for ${file}..."
if curl -fsSL -o "agent-rust/${file}" "$fallback_url"; then
echo "Successfully downloaded ${file} from latest release (fallback)."
return 0
fi
echo "ERROR: Failed to download ${file} for release build!"
return 1
else
echo "This is a branch build. Downloading ${file}..."
if curl -fsSL -o "agent-rust/${file}" "$url"; then
echo "Successfully downloaded ${file}."
return 0
elif curl -fsSL -o "agent-rust/${file}" "$fallback_url"; then
echo "Successfully downloaded ${file} from latest release (fallback)."
return 0
else
echo "WARNING: Failed to download ${file}. Will proceed with placeholder/local compilation."
return 0
fi
fi
}
download_binary "agent-linux-amd64" || exit 1
download_binary "agent-linux-arm64" || exit 1
download_binary "agent-windows-amd64.exe" || exit 1
ls -lh agent-rust/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 提取元数据(用于生成标签)
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REPO }}
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=dev,enable=${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') }}
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }}
type=sha,prefix=,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
labels: |
org.opencontainers.image.title=API Monitor
org.opencontainers.image.description=API聚合监控面板
org.opencontainers.image.vendor=iwvw
maintainer=iwvw
# 构建并推送单架构镜像(带后缀)
- name: Build and push by digest
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: ${{ matrix.platform }}
no-cache: true
push: true
# 同时推送到两个 Registry 的临时架构标签,确保层已就绪,避免合并时跨 Registry 复制层
tags: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:build-${{ github.run_id }}-${{ matrix.suffix }}
${{ (github.event_name != 'pull_request' && secrets.DOCKERHUB_TOKEN != '') && format('{0}:build-{1}-{2}', env.DOCKERHUB_REPO, github.run_id, matrix.suffix) || '' }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
build-args: |
NODE_ENV=production
- name: Export digest
run: |
mkdir -p /tmp/digests
echo "${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.suffix }}
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.suffix }}
path: /tmp/digests/*
retention-days: 1
# ==========================================
# 阶段 2: 合并多架构 manifest
# ==========================================
merge:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REPO }}
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=dev,enable=${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') }}
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }}
type=sha,prefix=,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
# 创建并推送多架构 manifest
- name: Create and push manifest
run: |
# 读取各架构 digest
AMD64_DIGEST=$(cat /tmp/digests/amd64)
ARM64_DIGEST=$(cat /tmp/digests/arm64)
echo "AMD64 Digest: $AMD64_DIGEST"
echo "ARM64 Digest: $ARM64_DIGEST"
# 为每个目标标签创建 manifest
TAGS="${{ steps.meta.outputs.tags }}"
echo "$TAGS" | while IFS= read -r TAG; do
[ -z "$TAG" ] && continue
# 确定当前标签所属的 Registry 以选择同源的源镜像进行合并(避免跨 Registry 复制 Blob 导致 400 错误)
if [[ "$TAG" == "${{ env.REGISTRY }}"* ]]; then
SRC_PREFIX="${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}"
else
SRC_PREFIX="${{ env.DOCKERHUB_REPO }}"
fi
echo "Creating manifest for: $TAG using source: $SRC_PREFIX"
# 增加重试机制,应对 Registry 极端情况下的抖动
for i in {1..3}; do
docker buildx imagetools create -t "$TAG" \
"${SRC_PREFIX}@${AMD64_DIGEST}" \
"${SRC_PREFIX}@${ARM64_DIGEST}" && break || {
if [ $i -lt 3 ]; then
echo "Push failed, retrying in 10s ($i/3)..."
sleep 10
else
echo "Failed to create manifest for $TAG after 3 attempts."
exit 1
fi
}
done
done
- name: Image digest
run: |
echo "## 🐳 Docker 镜像构建成功" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**镜像标签:**" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**支持架构:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**拉取命令:**" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "# 从 GHCR 拉取" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# 从 Docker Hub 拉取" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.DOCKERHUB_REPO }}:latest" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
# ==========================================
# 阶段 3: 清理临时镜像(可选)
# ==========================================
cleanup:
runs-on: ubuntu-latest
needs: merge
if: always()
continue-on-error: true
steps:
- name: Delete temporary build images
uses: actions/delete-package-versions@v5
with:
package-name: ${{ env.IMAGE_NAME }}
package-type: container
min-versions-to-keep: 0
delete-only-untagged-versions: false
# 只删除临时构建标签
ignore-versions: "^(?!build-${{ github.run_id }}).*$"
continue-on-error: true
# ==========================================
# 阶段 4: 安全扫描(可选)
# ==========================================
security-scan:
runs-on: ubuntu-latest
needs: merge
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
continue-on-error: true
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest
format: "sarif"
output: "trivy-results.sarif"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: "trivy-results.sarif"