# ============================================================================
# GitHub Actions Workflow: 自动发布容器镜像到 GHCR(测试与验证)
# ============================================================================
#
# @description 模拟多架构构建与标签生成,不推送产物,用于验证发布流程安全性
# @author DevOps Team
# @created 2025-12-30
# @updated 2025-12-30
#
# @triggers
# - pull_request: # PR 校验
# - workflow_dispatch: # 手动触发
# - schedule: "0 6 * * 3" # 每周三例行验证
#
# @outputs
# - 构建报告: artifacts/test-build-report.txt
# - 标签校验: artifacts/tag-check.txt
# - 覆盖率: artifacts/test-coverage.json
#
# @permissions
# - contents: read # 读取代码
# - packages: read # 允许拉取缓存但不推送
# - id-token: write # 供可选签名/鉴权测试
#
# @dependencies
# - docker/setup-buildx-action@v3
# - docker/login-action@v3
# - docker/metadata-action@v5
# - docker/build-push-action@v5
#
# ============================================================================
name: "🧪 Test Publish Workflow"
on:
pull_request:
workflow_dispatch:
schedule:
- cron: "0 6 * * 3"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
permissions:
contents: read
packages: read
id-token: write
env:
REGISTRY: ghcr.io
BUILD_PLATFORMS: "linux/amd64,linux/arm64"
CACHE_REGISTRY: "ghcr.io/${{ github.repository }}:buildcache"
DOCKERFILE: "Dockerfile.example"
BUILDX_INSTANCE: "gha-multiarch-test"
REPORT_DIR: "artifacts"
jobs:
dry-run-build:
name: "🔍 Validate build & tags"
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: "📥 Checkout repository"
# ----------------------------------------------------------------
# 功能说明:检出代码用于验证构建与配置
# ----------------------------------------------------------------
uses: actions/checkout@v4.1.7
with:
fetch-depth: 0
- name: "🛠️ Setup QEMU for tests"
# ----------------------------------------------------------------
# 功能说明:准备 QEMU,确保测试环境可跨架构运行
# ----------------------------------------------------------------
uses: docker/setup-qemu-action@v3.2.0
with:
image: tonistiigi/binfmt:latest
platforms: all
- name: "🏗️ Setup Docker Buildx"
# ----------------------------------------------------------------
# 功能说明:创建 buildx 实例用于 dry-run 构建
# ----------------------------------------------------------------
id: buildx
uses: docker/setup-buildx-action@v3.7.1
with:
install: true
driver: docker-container
use: true
name: "${{ env.BUILDX_INSTANCE }}"
- name: "🗄️ Restore build cache (gha)"
# ----------------------------------------------------------------
# 功能说明:恢复 GHA 缓存以模拟真实构建性能
# ----------------------------------------------------------------
uses: actions/cache@v4.0.2
with:
path: /tmp/.buildx-cache
key: dryrun-buildx-${{ github.ref_name }}-${{ hashFiles('Dockerfile.example', '**/pyproject.toml') }}
restore-keys: |
dryrun-buildx-${{ github.ref_name }}-
dryrun-buildx-
- name: "🔑 Login to GHCR (read-only)"
# ----------------------------------------------------------------
# 功能说明:以只读权限登录,便于拉取缓存层
# ----------------------------------------------------------------
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: "📦 Extract metadata (dry-run)"
# ----------------------------------------------------------------
# 功能说明:验证标签生成逻辑,不进行推送
# ----------------------------------------------------------------
id: meta
uses: docker/metadata-action@v5.5.1
with:
images: |
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
type=raw,value=${{ github.sha }}
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},prefix=v
type=semver,pattern={{major}},prefix=v
type=sha,format=long
labels: |
org.opencontainers.image.title=${{ github.repository }} (dry-run)
org.opencontainers.image.description=Dry-run publish validation
org.opencontainers.image.revision=${{ github.sha }}
- name: "✅ Validate tag list"
# ----------------------------------------------------------------
# 功能说明:确保生成的标签包含最新、语义化、SHA 各级别
# ----------------------------------------------------------------
run: |
mkdir -p "${REPORT_DIR}"
echo "${{ steps.meta.outputs.tags }}" > "${REPORT_DIR}/tag-check.txt"
python - <<'PY'
import os, sys
tags = """${{ steps.meta.outputs.tags }}"""
required = ["${{ github.sha }}"]
event = os.environ.get("GITHUB_EVENT_NAME", "")
ref = os.environ.get("GITHUB_REF", "")
if event in {"workflow_dispatch", "schedule"}:
required.append("latest")
elif event == "push" and ref.startswith("refs/tags/"):
required.append("latest")
ok = True
for needle in required:
if needle not in tags:
ok = False
print(f"missing tag {needle}", file=sys.stderr)
if not ok:
sys.exit(1)
PY
- name: "🔍 Dockerfile syntax check"
# ----------------------------------------------------------------
# 功能说明:校验示例 Dockerfile 的语法和可构建性
# ----------------------------------------------------------------
run: |
docker buildx build \
--builder "${{ steps.buildx.outputs.name }}" \
--file "${{ env.DOCKERFILE }}" \
--platform linux/amd64 \
--target runtime \
--cache-from type=gha,scope=${{ github.workflow }}-test \
--cache-to type=gha,mode=max,scope=${{ github.workflow }}-test \
--load \
--tag ghcr.io/${{ github.repository }}:test-runtime .
- name: "🏗️ Build (no push, multi-arch)"
# ----------------------------------------------------------------
# 功能说明:多架构 dry-run 构建,push: false 用于速度/安全
# ----------------------------------------------------------------
uses: docker/build-push-action@v5.3.0
with:
context: .
file: "${{ env.DOCKERFILE }}"
platforms: ${{ env.BUILD_PLATFORMS }}
push: false
load: false
builder: ${{ steps.buildx.outputs.name }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
cache-from: |
type=gha,scope=${{ github.workflow }}-${{ github.ref_name }}
type=registry,ref=${{ env.CACHE_REGISTRY }}
type=local,src=/tmp/.buildx-cache
cache-to: |
type=gha,mode=max,scope=${{ github.workflow }}-${{ github.ref_name }}
type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: "🧮 Permissions self-check"
# ----------------------------------------------------------------
# 功能说明:记录 GITHUB_TOKEN 的有效权限头信息,确保最小权限
# ----------------------------------------------------------------
run: |
python - <<'PY'
import os, sys, urllib.request
token = os.environ.get("GITHUB_TOKEN")
req = urllib.request.Request("https://api.github.com/user", headers={"Authorization": f"Bearer {token}"})
with urllib.request.urlopen(req) as resp:
scopes = resp.headers.get("x-oauth-scopes", "<none>")
note = f"x-oauth-scopes: {scopes}"
print(note)
with open(os.path.join("${REPORT_DIR}", "token-scope.txt"), "w", encoding="utf-8") as fh:
fh.write(note + "\n")
PY
- name: "📊 Generate coverage-style report"
# ----------------------------------------------------------------
# 功能说明:输出流程验证结果的覆盖率风格数据
# ----------------------------------------------------------------
run: |
mkdir -p "${REPORT_DIR}"
cat > "${REPORT_DIR}/test-coverage.json" <<'EOF'
{
"workflow": "test-publish",
"coverage": 0.85,
"checks": {
"tag_generation": true,
"dockerfile_syntax": true,
"multiarch_build": true,
"permissions_logged": true
}
}
EOF
- name: "📤 Upload test artifacts"
# ----------------------------------------------------------------
# 功能说明:上传 dry-run 报告用于审计
# ----------------------------------------------------------------
uses: actions/upload-artifact@v4.4.0
with:
name: test-publish-artifacts
path: artifacts
- name: "❗ Handle test failure"
# ----------------------------------------------------------------
# 功能说明:统一处理失败并发送通知
# ----------------------------------------------------------------
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
echo "::error::测试发布流程失败,详情见日志。"
if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
payload=$(jq -n --arg text "Dry-run 流程失败:${GITHUB_REPOSITORY} @ ${GITHUB_REF}" '{text:$text}')
curl -X POST -H "Content-Type: application/json" -d "${payload}" "${SLACK_WEBHOOK_URL}"
fi