mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
Squash main through e03d5c23f3.
Preserve scoped funds/concurrency preflight, original Run workspace authority,
manual recovery, registration invite, and complete feature-test packaging binding.
Combine unified ProjectView, telemetry, previews, media search, BYOK and TS7.
Synchronize prototype profile version and release test fixtures.
234 lines
9.5 KiB
YAML
234 lines
9.5 KiB
YAML
name: Docker image
|
|
|
|
# Standalone Docker packaging line (not part of core merge validation in ci.yml).
|
|
#
|
|
# - pull_request / standalone workflow_dispatch: smoke build only (push: false, amd64)
|
|
# - workflow_call from release-stable-docker: idempotent version-tag reconcile
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: "Git ref to build when invoked as a reusable workflow."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
release_version:
|
|
description: "Stable Docker tag to publish when invoked from release automation."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
expected_revision:
|
|
description: "Authoritative stable source commit from versioned release metadata."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
outputs:
|
|
reconciliation:
|
|
description: "Version-tag reconciliation action: noop or published."
|
|
value: ${{ jobs.build-and-push.outputs.reconciliation }}
|
|
version_digest:
|
|
description: "Verified multi-arch digest for the immutable version tag."
|
|
value: ${{ jobs.build-and-push.outputs.version_digest }}
|
|
pull_request:
|
|
paths:
|
|
- "deploy/**"
|
|
- ".dockerignore"
|
|
- "package.json"
|
|
- "pnpm-lock.yaml"
|
|
- "pnpm-workspace.yaml"
|
|
- ".github/scripts/release/stable-docker.py"
|
|
- ".github/workflows/docker-image.yml"
|
|
- ".github/workflows/release-stable-docker.yml"
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-and-push:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
reconciliation: ${{ steps.verify.outputs.reconciliation }}
|
|
version_digest: ${{ steps.verify.outputs.digest }}
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- name: Check out trusted Docker control plane
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.repository.default_branch || 'main' }}
|
|
path: .docker-control
|
|
|
|
- name: Check out image source
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.ref || github.sha }}
|
|
path: source
|
|
|
|
- name: Resolve publish mode
|
|
id: mode
|
|
shell: bash
|
|
env:
|
|
EXPECTED_REVISION: ${{ inputs.expected_revision }}
|
|
RELEASE_VERSION: ${{ inputs.release_version }}
|
|
run: |
|
|
set -euo pipefail
|
|
if { [ -n "${RELEASE_VERSION:-}" ] && [ -z "${EXPECTED_REVISION:-}" ]; } || \
|
|
{ [ -z "${RELEASE_VERSION:-}" ] && [ -n "${EXPECTED_REVISION:-}" ]; }; then
|
|
echo "::error::release_version and expected_revision must be supplied together"
|
|
exit 1
|
|
fi
|
|
if [ -n "${RELEASE_VERSION:-}" ]; then
|
|
echo "publish=true" >> "$GITHUB_OUTPUT"
|
|
echo "platforms=linux/amd64,linux/arm64" >> "$GITHUB_OUTPUT"
|
|
else
|
|
# pull_request and standalone workflow_dispatch: smoke only
|
|
echo "publish=false" >> "$GITHUB_OUTPUT"
|
|
echo "platforms=linux/amd64" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Set up QEMU
|
|
if: ${{ steps.mode.outputs.publish == 'true' }}
|
|
uses: docker/setup-qemu-action@v3
|
|
with:
|
|
platforms: linux/amd64,linux/arm64
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
with:
|
|
# Prefer Google's public Docker Hub cache, with upstream fallback.
|
|
buildkitd-config-inline: |
|
|
[registry."docker.io"]
|
|
mirrors = ["mirror.gcr.io"]
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
if: ${{ steps.mode.outputs.publish == 'true' }}
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Inspect immutable version tag
|
|
id: existing
|
|
if: ${{ steps.mode.outputs.publish == 'true' }}
|
|
shell: bash
|
|
env:
|
|
EXPECTED_REVISION: ${{ inputs.expected_revision }}
|
|
IMAGE: ghcr.io/${{ github.repository_owner }}/od:${{ inputs.release_version }}
|
|
RELEASE_VERSION: ${{ inputs.release_version }}
|
|
run: |
|
|
set -euo pipefail
|
|
inspection="$RUNNER_TEMP/stable-docker-existing.json"
|
|
error="$RUNNER_TEMP/stable-docker-existing.err"
|
|
if docker buildx imagetools inspect "$IMAGE" --format '{{json .}}' > "$inspection" 2> "$error"; then
|
|
python3 .docker-control/.github/scripts/release/stable-docker.py image-state \
|
|
--inspection "$inspection" \
|
|
--expected-revision "$EXPECTED_REVISION" \
|
|
--expected-version "$RELEASE_VERSION" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
elif grep -Eqi 'manifest unknown|not found|does not exist' "$error"; then
|
|
{
|
|
echo "state=missing"
|
|
echo "digest="
|
|
echo "missing_platforms=linux/amd64,linux/arm64"
|
|
} >> "$GITHUB_OUTPUT"
|
|
else
|
|
cat "$error" >&2
|
|
echo "::error::could not determine whether $IMAGE already exists"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository_owner }}/od
|
|
# spec §15.1 tag scheme:
|
|
# - workflow_call (stable) → immutable :<release_version> only
|
|
# - PR / manual validate → local-only sha tags (only when not publishing)
|
|
tags: |
|
|
type=raw,value=${{ inputs.release_version }},enable=${{ inputs.release_version != '' }}
|
|
type=sha,prefix=pr-${{ github.event.pull_request.number }}-sha-,format=short,enable=${{ steps.mode.outputs.publish != 'true' && github.event_name == 'pull_request' }}
|
|
type=sha,prefix=manual-sha-,format=short,enable=${{ steps.mode.outputs.publish != 'true' && github.event_name == 'workflow_dispatch' }}
|
|
labels: |
|
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
org.opencontainers.image.revision=${{ inputs.expected_revision || github.sha }}
|
|
org.opencontainers.image.version=${{ inputs.release_version || github.sha }}
|
|
|
|
- name: Build and push
|
|
if: ${{ steps.mode.outputs.publish != 'true' || steps.existing.outputs.state != 'complete' }}
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: source
|
|
file: source/deploy/Dockerfile
|
|
platforms: ${{ steps.mode.outputs.platforms }}
|
|
push: ${{ steps.mode.outputs.publish == 'true' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
annotations: |
|
|
index:org.opencontainers.image.revision=${{ inputs.expected_revision || github.sha }}
|
|
index:org.opencontainers.image.version=${{ inputs.release_version || github.sha }}
|
|
# The in-tree Dockerfile uses node:24-alpine + apk for build
|
|
# tooling; we keep that default so the workflow doesn't drift
|
|
# from local builds. Spec §15.1 nominates bookworm-slim as
|
|
# the canonical base; switching is a follow-up that needs
|
|
# the Dockerfile's apk lines re-cast for apt.
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
- name: Verify public GHCR pull access
|
|
id: verify
|
|
if: ${{ steps.mode.outputs.publish == 'true' }}
|
|
shell: bash
|
|
env:
|
|
EXPECTED_REVISION: ${{ inputs.expected_revision }}
|
|
EXISTING_STATE: ${{ steps.existing.outputs.state }}
|
|
IMAGE: ghcr.io/${{ github.repository_owner }}/od:${{ inputs.release_version }}
|
|
RELEASE_VERSION: ${{ inputs.release_version }}
|
|
run: |
|
|
set -euo pipefail
|
|
docker logout ghcr.io >/dev/null 2>&1 || true
|
|
inspection="$RUNNER_TEMP/stable-docker-verified.json"
|
|
verified=false
|
|
for attempt in 1 2 3 4 5; do
|
|
if docker buildx imagetools inspect "$IMAGE" --format '{{json .}}' > "$inspection"; then
|
|
verified=true
|
|
break
|
|
fi
|
|
echo "public GHCR verification attempt $attempt failed; retrying"
|
|
sleep 15
|
|
done
|
|
if [ "$verified" != "true" ]; then
|
|
printf '%s\n' \
|
|
"Published image is not anonymously pullable: $IMAGE" \
|
|
"" \
|
|
"Make the GHCR package public before cutting a public Docker release:" \
|
|
"GitHub organization -> Packages -> od -> Package settings -> Change visibility -> Public." >&2
|
|
exit 1
|
|
fi
|
|
python3 .docker-control/.github/scripts/release/stable-docker.py image-state \
|
|
--inspection "$inspection" \
|
|
--expected-revision "$EXPECTED_REVISION" \
|
|
--expected-version "$RELEASE_VERSION" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
if [ "${EXISTING_STATE:-}" = "complete" ]; then
|
|
echo "reconciliation=noop" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "reconciliation=published" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Publish reconciliation summary
|
|
if: ${{ steps.mode.outputs.publish == 'true' }}
|
|
env:
|
|
DIGEST: ${{ steps.verify.outputs.digest }}
|
|
IMAGE: ghcr.io/${{ github.repository_owner }}/od:${{ inputs.release_version }}
|
|
RECONCILIATION: ${{ steps.verify.outputs.reconciliation }}
|
|
run: |
|
|
{
|
|
echo "## Stable Docker version tag"
|
|
echo
|
|
echo "- Image: \`$IMAGE\`"
|
|
echo "- Action: \`$RECONCILIATION\`"
|
|
echo "- Digest: \`$DIGEST\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|