91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
252 lines
9.3 KiB
YAML
252 lines
9.3 KiB
YAML
name: Reusable Docker Publish Workflow
|
|
|
|
on:
|
|
workflow_dispatch: # Prevents phantom runs on push, allows manual testing
|
|
workflow_call:
|
|
inputs:
|
|
image_name:
|
|
description: "Name of the Docker image (e.g. cua-ubuntu, cua-xfce)"
|
|
required: true
|
|
type: string
|
|
context_dir:
|
|
description: "Directory containing the Dockerfile relative to workspace root (e.g. libs/kasm, libs/xfce)"
|
|
required: true
|
|
type: string
|
|
dockerfile_path:
|
|
description: "Path to Dockerfile relative to context_dir (e.g. Dockerfile)"
|
|
required: false
|
|
type: string
|
|
default: "Dockerfile"
|
|
tag_prefix:
|
|
description: "Prefix for semantic version tags (e.g. docker-kasm-v, docker-xfce-v)"
|
|
required: true
|
|
type: string
|
|
docker_hub_org:
|
|
description: "Docker Hub organization name"
|
|
required: false
|
|
type: string
|
|
default: "trycua"
|
|
skip_arm64:
|
|
description: "Skip arm64 build (for images that don't support arm64)"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
secrets:
|
|
DOCKER_HUB_TOKEN:
|
|
required: true
|
|
|
|
jobs:
|
|
build-and-push:
|
|
runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
platform:
|
|
- linux/amd64
|
|
- linux/arm64
|
|
exclude:
|
|
- platform: ${{ inputs.skip_arm64 && 'linux/arm64' || 'none' }}
|
|
steps:
|
|
- name: Free disk space
|
|
run: |
|
|
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
|
|
sudo docker system prune -af
|
|
df -h /
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Prepare platform tag
|
|
id: platform
|
|
run: |
|
|
TAG=$(echo "${{ matrix.platform }}" | sed 's/\//-/g')
|
|
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: ${{ inputs.docker_hub_org }}
|
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
|
|
- name: Build only (PR) - validate Dockerfile without pushing
|
|
if: github.event_name == 'pull_request'
|
|
id: build-pr
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: ./${{ inputs.context_dir }}
|
|
file: ./${{ inputs.context_dir }}/${{ inputs.dockerfile_path }}
|
|
push: false
|
|
platforms: ${{ matrix.platform }}
|
|
cache-from: |
|
|
type=registry,ref=${{ inputs.docker_hub_org }}/${{ inputs.image_name }}:buildcache-${{ steps.platform.outputs.tag }}
|
|
|
|
- name: Extract metadata (main)
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
id: meta-main
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ inputs.docker_hub_org }}/${{ inputs.image_name }}
|
|
tags: |
|
|
type=raw,value=latest
|
|
|
|
- name: Build & push digest (main)
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
id: build-main
|
|
continue-on-error: true
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: ./${{ inputs.context_dir }}
|
|
file: ./${{ inputs.context_dir }}/${{ inputs.dockerfile_path }}
|
|
push: true
|
|
platforms: ${{ matrix.platform }}
|
|
outputs: type=registry,name=${{ inputs.docker_hub_org }}/${{ inputs.image_name }},push-by-digest=true
|
|
labels: ${{ steps.meta-main.outputs.labels }}
|
|
cache-from: |
|
|
type=registry,ref=${{ inputs.docker_hub_org }}/${{ inputs.image_name }}:buildcache-${{ steps.platform.outputs.tag }}
|
|
cache-to: type=registry,ref=${{ inputs.docker_hub_org }}/${{ inputs.image_name }}:buildcache-${{ steps.platform.outputs.tag }},mode=max
|
|
|
|
- name: Extract metadata (semver)
|
|
if: startsWith(github.ref, format('refs/tags/{0}', inputs.tag_prefix))
|
|
id: meta-semver
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ inputs.docker_hub_org }}/${{ inputs.image_name }}
|
|
tags: |
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+\.\d+\.\d+),group=1
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+\.\d+),group=1
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+),group=1
|
|
type=raw,value=latest
|
|
|
|
- name: Build & push digest (semver)
|
|
if: startsWith(github.ref, format('refs/tags/{0}', inputs.tag_prefix))
|
|
id: build-semver
|
|
continue-on-error: true
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: ./${{ inputs.context_dir }}
|
|
file: ./${{ inputs.context_dir }}/${{ inputs.dockerfile_path }}
|
|
push: true
|
|
platforms: ${{ matrix.platform }}
|
|
outputs: type=registry,name=${{ inputs.docker_hub_org }}/${{ inputs.image_name }},push-by-digest=true
|
|
labels: ${{ steps.meta-semver.outputs.labels }}
|
|
cache-from: |
|
|
type=registry,ref=${{ inputs.docker_hub_org }}/${{ inputs.image_name }}:buildcache-${{ steps.platform.outputs.tag }}
|
|
cache-to: type=registry,ref=${{ inputs.docker_hub_org }}/${{ inputs.image_name }}:buildcache-${{ steps.platform.outputs.tag }},mode=max
|
|
|
|
- name: Export digest
|
|
id: export-digest
|
|
if: |
|
|
github.event_name != 'pull_request' &&
|
|
((steps.build-main.outcome == 'success') ||
|
|
(steps.build-semver.outcome == 'success'))
|
|
run: |
|
|
mkdir -p /tmp/digests
|
|
digest="${{ steps.build-main.outputs.digest || steps.build-semver.outputs.digest }}"
|
|
if [ -n "$digest" ]; then
|
|
echo "$digest" > "/tmp/digests/${{ steps.platform.outputs.tag }}.txt"
|
|
echo "Digest exported for platform ${{ matrix.platform }}: $digest"
|
|
else
|
|
echo "No digest to export for platform ${{ matrix.platform }}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload digest artifact (unique per platform)
|
|
if: steps.export-digest.outcome == 'success'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: digests-${{ steps.platform.outputs.tag }}
|
|
path: /tmp/digests/*.txt
|
|
retention-days: 1
|
|
|
|
publish-manifest-list:
|
|
runs-on: ubuntu-latest
|
|
needs:
|
|
- build-and-push
|
|
if: github.event_name != 'pull_request' && (success() || failure()) # Skip on PRs, run on push/tag even if some builds failed
|
|
|
|
steps:
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: ${{ inputs.docker_hub_org }}
|
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
|
|
- name: Extract final metadata (main)
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ inputs.docker_hub_org }}/${{ inputs.image_name }}
|
|
tags: |
|
|
type=raw,value=latest
|
|
|
|
- name: Extract final metadata (semver)
|
|
if: startsWith(github.ref, format('refs/tags/{0}', inputs.tag_prefix))
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ inputs.docker_hub_org }}/${{ inputs.image_name }}
|
|
tags: |
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+\.\d+\.\d+),group=1
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+\.\d+),group=1
|
|
type=match,pattern=${{ inputs.tag_prefix }}(\d+),group=1
|
|
type=raw,value=latest
|
|
|
|
- name: Download all digest artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: digests-*
|
|
path: /tmp/digests
|
|
merge-multiple: true
|
|
|
|
- name: Check available digests
|
|
id: check-digests
|
|
run: |
|
|
DIGEST_COUNT=$(find /tmp/digests -type f -name "*.txt" | wc -l)
|
|
echo "digest_count=$DIGEST_COUNT" >> $GITHUB_OUTPUT
|
|
|
|
if [ "$DIGEST_COUNT" -eq 0 ]; then
|
|
echo "Error: No platform builds succeeded!"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found $DIGEST_COUNT platform digest(s)"
|
|
for f in $(find /tmp/digests -type f -name "*.txt"); do
|
|
platform=$(basename "$f" .txt)
|
|
echo " - $platform"
|
|
done
|
|
|
|
- name: Create & push multi-arch manifest
|
|
run: |
|
|
IMAGE="${{ inputs.docker_hub_org }}/${{ inputs.image_name }}"
|
|
|
|
DIGEST_ARGS=""
|
|
for f in $(find /tmp/digests -type f -name "*.txt"); do
|
|
d=$(cat "$f")
|
|
DIGEST_ARGS="$DIGEST_ARGS ${IMAGE}@${d}"
|
|
done
|
|
|
|
echo "Using digests:"
|
|
echo "$DIGEST_ARGS"
|
|
|
|
# Create manifest for each tag produced by metadata-action
|
|
echo "${DOCKER_METADATA_OUTPUT_JSON}" | jq -r '.tags[]' | while read FULL_TAG; do
|
|
echo "Creating manifest: $FULL_TAG"
|
|
docker buildx imagetools create --tag "$FULL_TAG" $DIGEST_ARGS
|
|
done
|
|
|
|
- name: Inspect pushed manifests
|
|
run: |
|
|
IMAGE="${{ inputs.docker_hub_org }}/${{ inputs.image_name }}"
|
|
echo "Inspecting manifests:"
|
|
|
|
echo "${DOCKER_METADATA_OUTPUT_JSON}" | jq -r '.tags[]' | while read FULL_TAG; do
|
|
echo ""
|
|
echo "Inspecting: $FULL_TAG"
|
|
docker buildx imagetools inspect "$FULL_TAG"
|
|
done
|