mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
105038e658
* 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
227 lines
8.4 KiB
YAML
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}"
|