--- name: 'merge LocalAI image manifest list (reusable)' # Reusable workflow that joins per-arch digest artifacts (uploaded by # image_build.yml when called with platform-tag) into a single tagged # multi-arch manifest list. on: workflow_call: inputs: tag-latest: description: 'Whether the manifest list should also be tagged latest (auto/false/true)' required: false type: string default: '' tag-suffix: description: 'Image tag suffix (empty for core image). Used in artifact pattern with a -core placeholder for empty.' required: true type: string secrets: dockerUsername: required: false dockerPassword: required: false quayUsername: required: true quayPassword: required: true jobs: merge: runs-on: ubuntu-latest env: quay_username: ${{ secrets.quayUsername }} steps: # Sparse checkout: needed for .github/scripts/ (the keepalive cleanup # script). Skips the rest of the source tree. - name: Checkout (.github/scripts only) uses: actions/checkout@v6 with: sparse-checkout: | .github/scripts sparse-checkout-cone-mode: false - name: Download digests uses: actions/download-artifact@v8 with: # `--` separator anchors the glob so we don't over-match sibling # tag-suffixes (e.g. -nvidia-l4t-arm64 vs -nvidia-l4t-arm64-cuda-13). # Must stay in sync with image_build.yml's upload-artifact name. pattern: digests-localai${{ inputs.tag-suffix == '' && '-core' || inputs.tag-suffix }}--* merge-multiple: true path: /tmp/digests - name: Set up Docker Buildx uses: docker/setup-buildx-action@master - name: Login to DockerHub if: github.event_name != 'pull_request' uses: docker/login-action@v4 with: username: ${{ secrets.dockerUsername }} password: ${{ secrets.dockerPassword }} - name: Login to Quay.io uses: docker/login-action@v4 with: registry: quay.io username: ${{ secrets.quayUsername }} password: ${{ secrets.quayPassword }} - name: Docker meta id: meta uses: docker/metadata-action@v6 with: images: | quay.io/go-skynet/local-ai localai/localai tags: | type=ref,event=branch type=semver,pattern={{raw}} type=sha type=raw,value={{branch}}-{{date 'X'}}-{{sha}},enable={{is_default_branch}} flavor: | latest=${{ inputs.tag-latest }} suffix=${{ inputs.tag-suffix }},onlatest=true # Source from ci-cache, not local-ai. See backend_merge.yml for the # detailed rationale — quay's manifest GC is per-repository, so the # untagged digest in local-ai gets reaped while the same content lives # tagged under ci-cache (anchored by image_build.yml). buildx imagetools # create copies the manifest into local-ai (blobs already cross-mounted) # and publishes the manifest list with user-facing tags. End state in # local-ai is self-contained; no embedded reference to ci-cache. - name: Create manifest list and push (quay) working-directory: /tmp/digests run: | set -euo pipefail tags=$(jq -cr '.tags | map(select(startswith("quay.io/"))) | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") if [ -z "$tags" ]; then echo "No quay.io tags from docker/metadata-action; skipping quay merge" else # shellcheck disable=SC2086 docker buildx imagetools create $tags \ $(printf 'quay.io/go-skynet/ci-cache@sha256:%s ' *) fi - name: Create manifest list and push (dockerhub) if: github.event_name != 'pull_request' working-directory: /tmp/digests run: | set -euo pipefail tags=$(jq -cr '.tags | map(select(startswith("localai/"))) | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") if [ -z "$tags" ]; then echo "No dockerhub tags from docker/metadata-action; skipping dockerhub merge" else # shellcheck disable=SC2086 docker buildx imagetools create $tags \ $(printf 'localai/localai@sha256:%s ' *) fi - name: Inspect manifest run: | set -euo pipefail first_tag=$(jq -cr '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON") if [ -n "$first_tag" ] && [ "$first_tag" != "null" ]; then docker buildx imagetools inspect "$first_tag" fi # See .github/scripts/cleanup-keepalive-tags.sh for the best-effort # semantics — fails soft when the registry credential isn't OAuth-scoped. - name: Cleanup keepalive tags in ci-cache if: github.event_name != 'pull_request' && success() env: TAG_SUFFIX: ${{ inputs.tag-suffix == '' && '-core' || inputs.tag-suffix }} QUAY_TOKEN: ${{ secrets.quayPassword }} run: .github/scripts/cleanup-keepalive-tags.sh - name: Job summary run: | set -euo pipefail echo "Merged manifest tags:" >> "$GITHUB_STEP_SUMMARY" jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON" | sed 's/^/- /' >> "$GITHUB_STEP_SUMMARY" echo >> "$GITHUB_STEP_SUMMARY" echo "Per-arch digests:" >> "$GITHUB_STEP_SUMMARY" ls -1 /tmp/digests | sed 's/^/- sha256:/' >> "$GITHUB_STEP_SUMMARY"