Files
openclaw/.github/workflows/vercel-container-registry-publish.yml
Patrick Erichsen 105038e658 ci: mirror Docker release images to Vercel registry (#120058)
* ci: publish release images to Vercel registry

* ci: publish beta images to Vercel registry

* fix(ci): allow beta Vercel dispatch

* fix(ci): publish VCR-compatible image indexes

* fix(ci): allow VCR readiness propagation

* fix(ci): use Sandbox as VCR readiness proof

* fix(ci): promote clean VCR channel indexes

* fix(release): harden VCR publication

* fix(release): bind VCR publishing to verified inputs

* fix(test): follow script declaration migration

* fix(release): isolate VCR mirroring

* test(release): align VCR secret ownership
2026-08-10 14:45:05 -07:00

227 lines
8.4 KiB
YAML

name: Vercel Container Registry Publish
on:
workflow_call:
inputs:
version:
description: Docker release version without the v prefix
required: true
type: string
source_refs:
description: Newline-delimited alias=immutable-ref entries verified by the caller
required: true
type: string
include_browser:
description: Whether the tagged Docker release includes browser images
required: true
type: boolean
secrets:
VERCEL_TOKEN:
required: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
concurrency:
# Alias promotion is read-then-write. Serialize VCR independently so a slow
# mirror never holds the Docker publication lock.
group: vcr-release-publish
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-24.04
timeout-minutes: 30
# VCR is a best-effort mirror. Failed steps remain visible, but the primary
# Docker publication and GitHub release finalization must continue.
continue-on-error: true
permissions:
contents: read
steps:
- name: Checkout trusted registry tooling
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Set up Docker Builder
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Setup trusted Node runtime
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
# Materialize the complete CLI dependency closure before the production
# token enters any step environment. The committed lock and integrity
# checks keep registry and Sandbox operations on reviewed executable code.
- name: Materialize locked Vercel CLI
id: vercel_cli
run: |
set -euo pipefail
bash scripts/materialize-vercel-cli.sh \
.github/release/vercel-cli \
"${RUNNER_TEMP}/vercel-cli" \
"${GITHUB_OUTPUT}"
- name: Validate Vercel Container Registry configuration
env:
VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }}
VERCEL_SCOPE: ${{ vars.VERCEL_SCOPE }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_VCR_IMAGE: ${{ vars.VERCEL_VCR_IMAGE }}
run: |
set -euo pipefail
for name in VERCEL_PROJECT_ID VERCEL_SCOPE VERCEL_TOKEN VERCEL_VCR_IMAGE; do
if [[ -z "${!name}" ]]; then
echo "::error::${name} is required for Vercel Container Registry publishing."
exit 1
fi
done
if [[ "${VERCEL_VCR_IMAGE}" != vcr.vercel.com/*/*/* ]]; then
echo "::error::VERCEL_VCR_IMAGE must be a full vcr.vercel.com/team/project/repository image name."
exit 1
fi
- name: Authenticate Docker to Vercel Container Registry
env:
VERCEL_CLI: ${{ steps.vercel_cli.outputs.cli }}
VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }}
VERCEL_SCOPE: ${{ vars.VERCEL_SCOPE }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
set -euo pipefail
"${VERCEL_CLI}" vcr login docker \
--project "${VERCEL_PROJECT_ID}" \
--scope "${VERCEL_SCOPE}" \
--format json
- name: Copy and verify immutable release images
id: copy_images
env:
INCLUDE_BROWSER: ${{ inputs.include_browser }}
SOURCE_REFS: ${{ inputs.source_refs }}
TARGET_IMAGE: ${{ vars.VERCEL_VCR_IMAGE }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
browser_args=()
if [[ "${INCLUDE_BROWSER}" == "true" ]]; then
browser_args+=(--include-browser)
fi
source_ref_args=()
while IFS= read -r source_ref; do
[[ -z "${source_ref}" ]] && continue
source_ref_args+=(--source-ref "${source_ref}")
done <<< "${SOURCE_REFS}"
node scripts/vercel-container-registry-publish.mjs \
--version "${VERSION}" \
"${source_ref_args[@]}" \
--target-image "${TARGET_IMAGE}" \
"${browser_args[@]}"
# VCR tag status can remain null after an image is usable, so Sandbox
# creation is the authoritative readiness check for this publication.
- name: Run custom-image Sandbox smoke
id: sandbox_smoke
env:
VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_CLI: ${{ steps.vercel_cli.outputs.cli }}
VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }}
VERCEL_SCOPE: ${{ vars.VERCEL_SCOPE }}
VERCEL_VCR_IMAGE: ${{ vars.VERCEL_VCR_IMAGE }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
repository="${VERCEL_VCR_IMAGE##*/}"
retry_deadline=$((SECONDS + 600))
attempt=1
while true; do
set +e
output="$(
"${VERCEL_CLI}" sandbox run \
--silent \
--rm \
--non-persistent \
--network-policy deny-all \
--timeout 3m \
--project "${VERCEL_PROJECT_ID}" \
--scope "${VERCEL_SCOPE}" \
--image "${repository}:${VERSION}" \
--workdir /app \
--env "EXPECTED_VERSION=${VERSION}" \
-- /bin/sh -lc 'test "$(node -p "require(\"./package.json\").version")" = "${EXPECTED_VERSION}" && node openclaw.mjs --version' \
2>&1
)"
status=$?
set -e
printf '%s\n' "${output}"
if [[ "${status}" -eq 0 ]]; then
break
fi
if [[ "${output}" != *"image_not_ready"* ]]; then
exit "${status}"
fi
if (( SECONDS >= retry_deadline )); then
echo "::error::Vercel image preparation remained image_not_ready for 10 minutes."
exit "${status}"
fi
delay=10
remaining=$((retry_deadline - SECONDS))
if (( delay > remaining )); then
delay="${remaining}"
fi
echo "::notice::Vercel image is not ready; retrying Sandbox smoke in ${delay}s (attempt ${attempt})."
sleep "${delay}"
attempt=$((attempt + 1))
done
- name: Promote and verify channel aliases
id: promote_aliases
env:
INCLUDE_BROWSER: ${{ inputs.include_browser }}
TARGET_IMAGE: ${{ vars.VERCEL_VCR_IMAGE }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
browser_args=()
if [[ "${INCLUDE_BROWSER}" == "true" ]]; then
browser_args+=(--include-browser)
fi
node scripts/vercel-container-registry-publish.mjs \
--version "${VERSION}" \
--target-image "${TARGET_IMAGE}" \
--promote-aliases \
"${browser_args[@]}"
- name: Summarize Vercel publication
if: ${{ always() }}
env:
COPY_OUTCOME: ${{ steps.copy_images.outcome }}
INCLUDE_BROWSER: ${{ inputs.include_browser }}
PROMOTE_OUTCOME: ${{ steps.promote_aliases.outcome }}
SMOKE_OUTCOME: ${{ steps.sandbox_smoke.outcome }}
TARGET_IMAGE: ${{ vars.VERCEL_VCR_IMAGE }}
VERSION: ${{ inputs.version }}
run: |
variants="default, slim"
if [[ "${INCLUDE_BROWSER}" == "true" ]]; then
variants="${variants}, browser"
fi
status="passed"
if [[ "${COPY_OUTCOME}" != "success" || "${SMOKE_OUTCOME}" != "success" || "${PROMOTE_OUTCOME}" != "success" ]]; then
status="failed (non-blocking)"
echo "::warning::VCR mirror failed; Docker publication and GitHub release finalization are unaffected."
fi
{
echo "## Vercel Container Registry"
echo "- Image: ${TARGET_IMAGE}:${VERSION}"
echo "- Variants: ${variants}"
echo "- Platforms: linux/amd64, linux/arm64"
echo "- Status: ${status}"
echo "- Copy: ${COPY_OUTCOME}"
echo "- Sandbox smoke: ${SMOKE_OUTCOME}"
echo "- Alias promotion: ${PROMOTE_OUTCOME}"
} >> "${GITHUB_STEP_SUMMARY}"