ci(cpp): job 超时 15→30 分钟——vcpkg 重装+新增测试逼近原预算 #2048
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| permissions: | |
| contents: read | |
| packages: write | |
| jobs: | |
| build-server: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - timeout-minutes: 3 | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: false | |
| - name: Set up Docker Buildx | |
| timeout-minutes: 5 | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to GHCR | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) for Docker | |
| timeout-minutes: 3 | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/croupier-server | |
| - name: Build and push Docker image | |
| timeout-minutes: 15 | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.server | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| # IP 属地数据源(可选):在仓库 secrets 配置 IP2LOCATION_BIN_URL | |
| # (从 https://lite.ip2location.com 账号下载页复制的完整链接), | |
| # 镜像内置 BIN 后审计日志即可展示 IP 属地;未配置则跳过下载。 | |
| build-args: | | |
| IP2LOCATION_BIN_URL=${{ secrets.IP2LOCATION_BIN_URL }} | |
| - name: Make GHCR package public (server) | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| env: | |
| GH_OWNER: ${{ github.repository_owner }} | |
| GH_OWNER_SCOPE: ${{ github.event.repository.owner.type == 'Organization' && 'orgs' || 'users' }} | |
| # Prefer a PAT if provided; fall back to GITHUB_TOKEN (may lack perms to change visibility) | |
| TOKEN: ${{ secrets.GHCR_TOKEN != '' && secrets.GHCR_TOKEN || github.token }} | |
| PKG: croupier-server | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| echo "Ensuring GHCR package visibility: ghcr.io/${GH_OWNER}/${PKG} -> public" | |
| scope="${GH_OWNER_SCOPE}/${GH_OWNER}" | |
| # Wait for package to exist (up to ~60s) | |
| for i in $(seq 1 12); do | |
| code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer ${TOKEN}" -H "Accept: application/vnd.github+json" "https://api.github.com/${scope}/packages/container/${PKG}" || true) | |
| echo "Probe ${scope}/packages/container/${PKG} -> $code" | |
| [ "$code" = "200" ] && break | |
| sleep 5 | |
| done | |
| # Attempt to set visibility | |
| code=$(curl -s -o /dev/null -w "%{http_code}" -L -X PUT \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/${scope}/packages/container/${PKG}/visibility" \ | |
| -d '{"visibility":"public"}' || true) | |
| if [ "$code" = "204" ] || [ "$code" = "200" ]; then | |
| echo "Package ${PKG} set to public via ${scope}" | |
| else | |
| echo "Skipping GHCR visibility update for ${PKG} (HTTP $code)" | |
| fi | |
| build-agent: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - timeout-minutes: 3 | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: false | |
| - name: Set up Docker Buildx | |
| timeout-minutes: 5 | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to GHCR | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) for Docker | |
| timeout-minutes: 3 | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/croupier-agent | |
| - name: Build and push Docker image | |
| timeout-minutes: 15 | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.agent | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| - name: Make GHCR package public (agent) | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| env: | |
| GH_OWNER: ${{ github.repository_owner }} | |
| GH_OWNER_SCOPE: ${{ github.event.repository.owner.type == 'Organization' && 'orgs' || 'users' }} | |
| TOKEN: ${{ secrets.GHCR_TOKEN != '' && secrets.GHCR_TOKEN || github.token }} | |
| PKG: croupier-agent | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| echo "Ensuring GHCR package visibility: ghcr.io/${GH_OWNER}/${PKG} -> public" | |
| scope="${GH_OWNER_SCOPE}/${GH_OWNER}" | |
| for i in $(seq 1 12); do | |
| code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer ${TOKEN}" -H "Accept: application/vnd.github+json" "https://api.github.com/${scope}/packages/container/${PKG}" || true) | |
| echo "Probe ${scope}/packages/container/${PKG} -> $code" | |
| [ "$code" = "200" ] && break | |
| sleep 5 | |
| done | |
| code=$(curl -s -o /dev/null -w "%{http_code}" -L -X PUT \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/${scope}/packages/container/${PKG}/visibility" \ | |
| -d '{"visibility":"public"}' || true) | |
| if [ "$code" = "204" ] || [ "$code" = "200" ]; then | |
| echo "Package ${PKG} set to public via ${scope}" | |
| else | |
| echo "Skipping GHCR visibility update for ${PKG} (HTTP $code)" | |
| fi | |
| build-ingest: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - timeout-minutes: 3 | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: false | |
| - name: Set up Docker Buildx | |
| timeout-minutes: 5 | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to GHCR | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) for Docker | |
| timeout-minutes: 3 | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/croupier-ingest | |
| - name: Build and push Docker image | |
| timeout-minutes: 15 | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.ingest | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=ingest | |
| cache-to: type=gha,scope=ingest,mode=max | |
| build-analytics-worker: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - timeout-minutes: 3 | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: false | |
| - name: Set up Docker Buildx | |
| timeout-minutes: 5 | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to GHCR | |
| if: github.event_name != 'pull_request' | |
| timeout-minutes: 3 | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) for Docker | |
| timeout-minutes: 3 | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/croupier-analytics-worker | |
| - name: Build and push Docker image | |
| timeout-minutes: 15 | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.analytics-worker | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=analytics-worker | |
| cache-to: type=gha,scope=analytics-worker,mode=max | |
| # 供应链安全:镜像漏洞扫描(trivy)+ SBOM 归档 + cosign keyless 签名。 | |
| # 仅在镜像实际推送后执行(PR 构建不 push)。 | |
| supply-chain: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [build-server, build-agent, build-ingest, build-analytics-worker] | |
| if: github.event_name != 'pull_request' | |
| strategy: | |
| matrix: | |
| image: | |
| [ | |
| croupier-server, | |
| croupier-agent, | |
| croupier-ingest, | |
| croupier-analytics-worker, | |
| ] | |
| permissions: | |
| contents: read | |
| # cosign 签名是向包仓库写签名层( attest/blobs upload),需要 write。 | |
| packages: write | |
| # cosign keyless 签名需要 id-token。 | |
| id-token: write | |
| # 扫描结果上传 SARIF。 | |
| security-events: write | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} | |
| steps: | |
| - name: Login to GHCR | |
| timeout-minutes: 3 | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Resolve pushed tag | |
| id: tag | |
| timeout-minutes: 2 | |
| run: | | |
| set -euo pipefail | |
| # 与 metadata-action 的 tag 策略对齐:tag 触发 → 版本 tag; | |
| # 分支推送(main)→ 分支名 tag(metadata-action 默认仅生成分支 tag)。 | |
| if [[ "$GITHUB_REF" == refs/tags/* ]]; then | |
| echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| elif [[ "$GITHUB_REF" == refs/heads/* ]]; then | |
| echo "tag=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=main" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Trivy vulnerability scan | |
| timeout-minutes: 10 | |
| uses: aquasecurity/trivy-action@0.35.0 | |
| with: | |
| image-ref: ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }} | |
| format: sarif | |
| output: trivy-${{ matrix.image }}.sarif | |
| severity: HIGH,CRITICAL | |
| exit-code: "0" # 发现不阻断(先观察基线);收紧为 1 可做门禁 | |
| ignore-unfixed: true | |
| - name: Upload Trivy results to GitHub Security | |
| timeout-minutes: 3 | |
| uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: trivy-${{ matrix.image }}.sarif | |
| category: trivy-${{ matrix.image }} | |
| - name: Generate SBOM (SPDX) | |
| timeout-minutes: 10 | |
| uses: aquasecurity/trivy-action@0.35.0 | |
| with: | |
| image-ref: ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }} | |
| format: spdx-json | |
| output: sbom-${{ matrix.image }}.spdx.json | |
| - name: Upload SBOM artifact | |
| timeout-minutes: 3 | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sbom-${{ matrix.image }}-${{ steps.tag.outputs.tag }} | |
| path: sbom-${{ matrix.image }}.spdx.json | |
| retention-days: 90 | |
| - name: Sign image with cosign (keyless OIDC) | |
| timeout-minutes: 5 | |
| uses: sigstore/cosign-installer@v4.1.2 | |
| - name: cosign sign | |
| timeout-minutes: 5 | |
| run: | | |
| set -euo pipefail | |
| # 凭据沿用本 job 第一步的 docker login(GITHUB_TOKEN + job 声明的 | |
| # packages:write)。禁止再用 PAT 二次 login 覆盖——历史 GHCR_TOKEN | |
| # secret 无效时会把好凭据换坏(403)。keyless 身份来自 id-token。 | |
| DIGEST="$(docker buildx imagetools inspect "${IMAGE}:${{ steps.tag.outputs.tag }}" --format '{{.Manifest.Digest}}')" | |
| if [ -z "${DIGEST}" ]; then | |
| echo "::error::empty digest for ${IMAGE}:${{ steps.tag.outputs.tag }}" | |
| exit 1 | |
| fi | |
| echo "Signing ${IMAGE}@${DIGEST}" | |
| cosign sign --yes "${IMAGE}@${DIGEST}" |