From 42a8db3573d9c1090707828b2f1157315194fa1a Mon Sep 17 00:00:00 2001 From: "LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 14 May 2026 00:28:48 +0200 Subject: [PATCH] ci(image): publish missing :latest-* and :v-* singleton image tags (#9812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(image): wire singleton merges + `--` artifact separator Closes the same singletons gap on the LocalAI server image workflow that PR #9781 closed for backends. The user observed it as missing :latest-gpu-nvidia-cuda-12 etc. on quay.io/go-skynet/local-ai — the build matrix has six single-arch entries with no corresponding merge step, so their per-arch digests push (push-by-digest=true) and never get tagged: - -gpu-hipblas (hipblas-jobs) - -gpu-nvidia-cuda-12 (core-image-build) - -gpu-nvidia-cuda-13 (core-image-build) - -gpu-intel (core-image-build) - -nvidia-l4t-arm64 (gh-runner) - -nvidia-l4t-arm64-cuda-13 (gh-runner) Only :latest, :v, :latest-gpu-vulkan and :v-gpu-vulkan were actually being published before this commit (the two multiarch suffixes that had merge jobs). Changes: 1. image.yml: add six new merge jobs, one per single-arch entry. Each `needs:` only its parent build job (matching the existing pattern for core-image-merge / gpu-vulkan-image-merge). 2. image_build.yml: switch artifact name to `digests-localai--`. The `--` separator anchors the merge-side glob so a singleton tag-suffix doesn't over-match a longer suffix that shares its prefix (-nvidia-l4t-arm64 vs -nvidia-l4t-arm64-cuda-13). Same convention as backend_build.yml's fix. 3. image_merge.yml: update the download pattern to match. Next master push or tag release should produce :latest-gpu-hipblas, :latest-gpu-nvidia-cuda-12, :latest-gpu-nvidia-cuda-13, :latest-gpu-intel, :latest-nvidia-l4t-arm64, :latest-nvidia-l4t-arm64-cuda-13 (and their :v-* equivalents) for the first time on the post-#9781 workflow. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto * ci(image): add !cancelled() guard to all 8 image merge jobs Parity pass with backend.yml's merge jobs (8521af14). Without !cancelled(), GHA's default `needs:` cascade skips the merge when ANY matrix cell of the parent build job fails or is cancelled — so a single flaky leg would suppress publication of every other tag-suffix's manifest list. Same fix the backend got after v4.2.1 showed 2 failed singlearch builds cascade-skip 199 singlearch merge entries. Applied to all 8 image merges: - core-image-merge - gpu-vulkan-image-merge - gpu-nvidia-cuda-12-image-merge (added in e5300f1a) - gpu-nvidia-cuda-13-image-merge (added in e5300f1a) - gpu-intel-image-merge (added in e5300f1a) - gpu-hipblas-image-merge (added in e5300f1a) - nvidia-l4t-arm64-image-merge (added in e5300f1a) - nvidia-l4t-arm64-cuda-13-image-merge (added in e5300f1a) Build jobs (hipblas-jobs, core-image-build, gh-runner) are intentionally NOT changed — they have no upstream `needs:` to cascade- skip from. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto --------- Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- .github/workflows/image.yml | 94 ++++++++++++++++++++++++++++++- .github/workflows/image_build.yml | 6 +- .github/workflows/image_merge.yml | 5 +- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 91e5c97f7..716faaaee 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -151,7 +151,11 @@ ubuntu-codename: 'noble' core-image-merge: - if: github.repository == 'mudler/LocalAI' + # !cancelled(): without it, GHA's default `needs:` cascade skips the + # merge whenever any matrix cell of the parent build fails or is + # cancelled. Same fix as backend.yml's merge jobs — we still want to + # publish the manifest list for tag-suffixes whose legs all succeeded. + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} needs: core-image-build uses: ./.github/workflows/image_merge.yml with: @@ -164,7 +168,7 @@ quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} gpu-vulkan-image-merge: - if: github.repository == 'mudler/LocalAI' + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} needs: core-image-build uses: ./.github/workflows/image_merge.yml with: @@ -175,7 +179,91 @@ dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} - + + # Single-arch server-image merges. Same conceptual fix as the backend + # singletons in PR #9781: image_build.yml pushes by canonical digest + # only, so without a downstream merge step there's no tag for consumers + # (no :latest-gpu-nvidia-cuda-12, no :v-gpu-nvidia-cuda-12, etc.). + # Each merge job needs only its parent build matrix and is filtered by + # tag-suffix in image_merge.yml's artifact-download pattern. + gpu-nvidia-cuda-12-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: core-image-build + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-gpu-nvidia-cuda-12' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + + gpu-nvidia-cuda-13-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: core-image-build + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-gpu-nvidia-cuda-13' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + + gpu-intel-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: core-image-build + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-gpu-intel' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + + gpu-hipblas-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: hipblas-jobs + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-gpu-hipblas' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + + nvidia-l4t-arm64-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: gh-runner + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-nvidia-l4t-arm64' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + + nvidia-l4t-arm64-cuda-13-image-merge: + if: ${{ !cancelled() && github.repository == 'mudler/LocalAI' }} + needs: gh-runner + uses: ./.github/workflows/image_merge.yml + with: + tag-latest: 'auto' + tag-suffix: '-nvidia-l4t-arm64-cuda-13' + secrets: + dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} + dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + gh-runner: if: github.repository == 'mudler/LocalAI' uses: ./.github/workflows/image_build.yml diff --git a/.github/workflows/image_build.yml b/.github/workflows/image_build.yml index 9b9683b32..96cfebdd8 100644 --- a/.github/workflows/image_build.yml +++ b/.github/workflows/image_build.yml @@ -202,7 +202,11 @@ jobs: if: github.event_name != 'pull_request' uses: actions/upload-artifact@v7 with: - name: digests-localai${{ inputs.tag-suffix == '' && '-core' || inputs.tag-suffix }}-${{ inputs.platform-tag }} + # `--` separator + 'single' placeholder for empty platform-tag — + # same pattern as backend_build.yml. Prevents prefix collisions + # in the merge-side glob (e.g. -nvidia-l4t-arm64 is a prefix of + # -nvidia-l4t-arm64-cuda-13). + name: digests-localai${{ inputs.tag-suffix == '' && '-core' || inputs.tag-suffix }}--${{ inputs.platform-tag || 'single' }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 diff --git a/.github/workflows/image_merge.yml b/.github/workflows/image_merge.yml index 85315fe58..f667c7d4c 100644 --- a/.github/workflows/image_merge.yml +++ b/.github/workflows/image_merge.yml @@ -45,7 +45,10 @@ jobs: - name: Download digests uses: actions/download-artifact@v8 with: - pattern: digests-localai${{ inputs.tag-suffix == '' && '-core' || inputs.tag-suffix }}-* + # `--` 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