This is an automated email from the ASF dual-hosted git repository.

hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new dd7ae9bbc fix(ci): eliminate intermediate Docker tags using 
push-by-digest (#2491)
dd7ae9bbc is described below

commit dd7ae9bbc8b96a80732e6aa43b984f6a6717e65e
Author: Hubert Gruszecki <[email protected]>
AuthorDate: Tue Dec 16 10:26:41 2025 +0100

    fix(ci): eliminate intermediate Docker tags using push-by-digest (#2491)
    
    Push images by digest instead of arch-specific tags, then create
    multi-arch manifests. Removes edge-amd64/edge-arm64 tag pollution.
---
 .github/actions/utils/docker-buildx/action.yml |  55 ++++++++---
 .github/workflows/post-merge.yml               | 122 ++++++++++++++++++++++++-
 .github/workflows/publish.yml                  |  62 +++++++++++--
 3 files changed, 214 insertions(+), 25 deletions(-)

diff --git a/.github/actions/utils/docker-buildx/action.yml 
b/.github/actions/utils/docker-buildx/action.yml
index 0336fdf23..7f06d4826 100644
--- a/.github/actions/utils/docker-buildx/action.yml
+++ b/.github/actions/utils/docker-buildx/action.yml
@@ -47,6 +47,14 @@ inputs:
     required: false
     default: ""
 
+outputs:
+  digest:
+    description: "Image digest (sha256:...)"
+    value: ${{ steps.build.outputs.digest }}
+  image:
+    description: "Registry image name"
+    value: ${{ steps.config.outputs.image }}
+
 runs:
   using: "composite"
   steps:
@@ -158,10 +166,10 @@ runs:
       uses: docker/metadata-action@v5
       with:
         images: ${{ steps.config.outputs.image }}
+        # Tags are only used for local builds (dry-run). Push mode always uses 
digest.
         tags: |
-          type=raw,value=${{ inputs.version }}${{ steps.suffix.outputs.suffix 
}}
-          type=raw,value=latest,enable=${{ steps.suffix.outputs.is_single == 
'false' && steps.config.outputs.should_push == 'true' && inputs.version != 
'test' && !contains(inputs.version, 'edge') && !contains(inputs.version, 'rc') 
&& !contains(inputs.version, 'alpha') && !contains(inputs.version, 'beta') }}
-          type=sha,enable=${{ inputs.version == 'test' }}
+          type=raw,value=${{ inputs.version }}${{ steps.suffix.outputs.suffix 
}},enable=${{ steps.config.outputs.should_push != 'true' }}
+          type=sha,enable=${{ steps.config.outputs.should_push != 'true' && 
inputs.version == 'test' }}
 
     - name: Determine platforms
       id: platforms
@@ -322,20 +330,43 @@ runs:
         echo "context=$context" >> "$GITHUB_OUTPUT"
         echo "📁 Build context: $context"
 
-    - name: Build and push
-      id: build
+    - name: Build and push (by digest)
+      id: build-push
+      if: steps.config.outputs.should_push == 'true'
+      uses: docker/build-push-action@v6
+      with:
+        context: ${{ steps.ctx.outputs.context }}
+        file: ${{ steps.config.outputs.dockerfile }}
+        platforms: ${{ steps.platforms.outputs.platforms }}
+        labels: ${{ steps.meta.outputs.labels }}
+        cache-from: ${{ env.CACHE_FROM }}
+        cache-to: ${{ env.CACHE_TO }}
+        build-args: |
+          ${{ steps.bargs.outputs.all }}
+        outputs: type=image,name=${{ steps.config.outputs.image 
}},push-by-digest=true,name-canonical=true,push=true
+
+    - name: Build only (dry-run)
+      id: build-only
+      if: steps.config.outputs.should_push != 'true'
       uses: docker/build-push-action@v6
       with:
         context: ${{ steps.ctx.outputs.context }}
         file: ${{ steps.config.outputs.dockerfile }}
         platforms: ${{ steps.platforms.outputs.platforms }}
-        push: ${{ steps.config.outputs.should_push }}
         tags: ${{ steps.meta.outputs.tags }}
         labels: ${{ steps.meta.outputs.labels }}
         cache-from: ${{ env.CACHE_FROM }}
         cache-to: ${{ env.CACHE_TO }}
         build-args: |
           ${{ steps.bargs.outputs.all }}
+        push: false
+
+    - name: Consolidate build output
+      id: build
+      shell: bash
+      run: |
+        # Output digest from push step (dry-run builds don't produce useful 
digests)
+        echo "digest=${{ steps.build-push.outputs.digest }}" >> 
"$GITHUB_OUTPUT"
 
     - name: Export image (if not pushing)
       if: steps.config.outputs.should_push == 'false' && inputs.task != 
'publish'
@@ -358,12 +389,14 @@ runs:
           echo "| Dockerfile | \`${{ steps.config.outputs.dockerfile }}\` |"
           echo "| Platforms | \`${{ steps.platforms.outputs.platforms }}\` |"
           echo "| Pushed | ${{ steps.config.outputs.should_push }} |"
-          if [ "${{ steps.config.outputs.should_push }}" = "true" ]; then
+          if [ -n "${{ steps.build.outputs.digest }}" ]; then
             echo "| Digest | \`${{ steps.build.outputs.digest }}\` |"
-            echo ""
-            echo "### Pull"
-            echo '```bash'
-            echo "docker pull ${{ steps.config.outputs.image }}:${{ 
inputs.version }}"
+          fi
+          echo ""
+          if [ "${{ steps.config.outputs.should_push }}" = "true" ]; then
+            echo "### Digest (for manifest creation)"
+            echo '```'
+            echo "${{ steps.config.outputs.image }}@${{ 
steps.build.outputs.digest }}"
             echo '```'
           fi
         } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml
index f1d339e34..d54380f35 100644
--- a/.github/workflows/post-merge.yml
+++ b/.github/workflows/post-merge.yml
@@ -122,6 +122,7 @@ jobs:
           fi
 
       - uses: ./.github/actions/utils/docker-buildx
+        id: docker
         with:
           task: publish
           libc: ${{ steps.libc.outputs.libc }}
@@ -130,6 +131,29 @@ jobs:
           platform: ${{ matrix.platform }}
           dry_run: ${{ github.event.repository.fork }}
 
+      - name: Export digest
+        if: ${{ !github.event.repository.fork }}
+        shell: bash
+        run: |
+          mkdir -p ${{ runner.temp }}/digests
+          digest="${{ steps.docker.outputs.digest }}"
+          if [ -n "$digest" ]; then
+            touch "${{ runner.temp }}/digests/${digest#sha256:}"
+            echo "Exported digest: $digest"
+          else
+            echo "::error::No digest available"
+            exit 1
+          fi
+
+      - name: Upload digest
+        if: ${{ !github.event.repository.fork }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.component }}-${{ matrix.arch }}
+          path: ${{ runner.temp }}/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
   docker-manifests:
     name: Create manifests
     needs: [plan, docker-edge]
@@ -159,6 +183,21 @@ jobs:
           echo "image=$image" >> "$GITHUB_OUTPUT"
           echo "📦 Image: $image"
 
+      - name: Download amd64 digest
+        uses: actions/download-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.component }}-amd64
+          path: ${{ runner.temp }}/digests
+
+      - name: Download arm64 digest
+        uses: actions/download-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.component }}-arm64
+          path: ${{ runner.temp }}/digests
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
       - name: Login to Docker Hub
         uses: docker/login-action@v3
         with:
@@ -166,19 +205,92 @@ jobs:
           password: ${{ env.DOCKERHUB_TOKEN }}
 
       - name: Create and push manifest
+        working-directory: ${{ runner.temp }}/digests
         run: |
           IMAGE="${{ steps.config.outputs.image }}"
           VERSION="edge"
 
-          echo "Creating manifest for $IMAGE:$VERSION"
+          echo "Creating manifest for $IMAGE:$VERSION from digests:"
+          ls -la
 
-          docker manifest create "${IMAGE}:${VERSION}" \
-            "${IMAGE}:${VERSION}-amd64" \
-            "${IMAGE}:${VERSION}-arm64"
+          docker buildx imagetools create \
+            -t "${IMAGE}:${VERSION}" \
+            $(printf "${IMAGE}@sha256:%s " *)
 
-          docker manifest push "${IMAGE}:${VERSION}"
           echo "✅ Pushed manifest: ${IMAGE}:${VERSION}"
 
+      - name: Inspect manifest
+        run: |
+          docker buildx imagetools inspect "${{ steps.config.outputs.image 
}}:edge"
+
+  # TEMPORARY: Clean up legacy architecture-specific tags from DockerHub
+  # This job can be removed after the first successful run cleans up the old 
tags
+  cleanup-legacy-tags:
+    name: Cleanup legacy tags
+    needs: [plan, docker-manifests]
+    if: ${{ !github.event.repository.fork && 
fromJson(needs.plan.outputs.components).include[0].component != 'noop' }}
+    runs-on: ubuntu-latest
+    env:
+      DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
+      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
+    steps:
+      - name: Delete legacy edge-amd64 and edge-arm64 tags
+        run: |
+          set -euo pipefail
+
+          # Get Docker Hub JWT token
+          echo "🔐 Authenticating with Docker Hub..."
+          TOKEN=$(curl -s -X POST \
+            -H "Content-Type: application/json" \
+            -d "{\"username\": \"${DOCKERHUB_USER}\", \"password\": 
\"${DOCKERHUB_TOKEN}\"}" \
+            https://hub.docker.com/v2/users/login/ | jq -r .token)
+
+          if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
+            echo "::error::Failed to authenticate with Docker Hub"
+            exit 1
+          fi
+
+          # Images to clean up (from .github/config/publish.yml)
+          IMAGES=(
+            "apache/iggy"
+            "apache/iggy-connect"
+            "apache/iggy-mcp"
+            "apache/iggy-web-ui"
+            "apache/iggy-bench-dashboard"
+          )
+
+          # Tags to delete
+          TAGS=("edge-amd64" "edge-arm64")
+
+          for IMAGE in "${IMAGES[@]}"; do
+            # Extract namespace and repo from image name
+            NAMESPACE="${IMAGE%%/*}"
+            REPO="${IMAGE#*/}"
+
+            for TAG in "${TAGS[@]}"; do
+              echo "🗑️ Deleting ${IMAGE}:${TAG}..."
+
+              HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
+                -H "Authorization: JWT ${TOKEN}" \
+                
"https://hub.docker.com/v2/repositories/${NAMESPACE}/${REPO}/tags/${TAG}/";)
+
+              case $HTTP_CODE in
+                200|204)
+                  echo "   ✅ Deleted ${IMAGE}:${TAG}"
+                  ;;
+                404)
+                  echo "   ⏭️ Tag ${IMAGE}:${TAG} not found (already deleted 
or never existed)"
+                  ;;
+                *)
+                  echo "   ⚠️ Failed to delete ${IMAGE}:${TAG} (HTTP 
${HTTP_CODE})"
+                  ;;
+              esac
+            done
+          done
+
+          echo ""
+          echo "🎉 Legacy tag cleanup complete!"
+
   build-artifacts:
     name: Build artifacts
     uses: ./.github/workflows/_build_rust_artifacts.yml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 4aaa794ef..e5c02f49b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -702,6 +702,7 @@ jobs:
           fi
 
       - name: Publish Docker image
+        id: docker
         uses: ./.github/actions/utils/docker-buildx
         with:
           task: publish
@@ -711,6 +712,29 @@ jobs:
           platform: ${{ matrix.platform }}
           dry_run: ${{ inputs.dry_run }}
 
+      - name: Export digest
+        if: ${{ !inputs.dry_run }}
+        shell: bash
+        run: |
+          mkdir -p ${{ runner.temp }}/digests
+          digest="${{ steps.docker.outputs.digest }}"
+          if [ -n "$digest" ]; then
+            touch "${{ runner.temp }}/digests/${digest#sha256:}"
+            echo "Exported digest: $digest"
+          else
+            echo "::error::No digest available"
+            exit 1
+          fi
+
+      - name: Upload digest
+        if: ${{ !inputs.dry_run }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.key }}-${{ matrix.arch }}
+          path: ${{ runner.temp }}/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
   # Create multi-arch Docker manifests after platform-specific images are 
pushed
   docker-manifests:
     name: Docker manifests
@@ -746,6 +770,7 @@ jobs:
 
       - name: Resolve image from config
         id: config
+        shell: bash
         run: |
           if ! command -v yq >/dev/null 2>&1; then
             YQ_VERSION="v4.47.1"
@@ -758,6 +783,21 @@ jobs:
           echo "image=$image" >> "$GITHUB_OUTPUT"
           echo "📦 Image: $image"
 
+      - name: Download amd64 digest
+        uses: actions/download-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.key }}-amd64
+          path: ${{ runner.temp }}/digests
+
+      - name: Download arm64 digest
+        uses: actions/download-artifact@v4
+        with:
+          name: docker-digest-${{ matrix.key }}-arm64
+          path: ${{ runner.temp }}/digests
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
       - name: Login to Docker Hub
         uses: docker/login-action@v3
         with:
@@ -765,29 +805,33 @@ jobs:
           password: ${{ env.DOCKERHUB_TOKEN }}
 
       - name: Create and push manifest
+        working-directory: ${{ runner.temp }}/digests
         run: |
           IMAGE="${{ steps.config.outputs.image }}"
           VERSION="${{ steps.ver.outputs.version }}"
 
-          echo "Creating manifest for $IMAGE:$VERSION"
+          echo "Creating manifest for $IMAGE:$VERSION from digests:"
+          ls -la
 
-          docker manifest create "${IMAGE}:${VERSION}" \
-            "${IMAGE}:${VERSION}-amd64" \
-            "${IMAGE}:${VERSION}-arm64"
+          docker buildx imagetools create \
+            -t "${IMAGE}:${VERSION}" \
+            $(printf "${IMAGE}@sha256:%s " *)
 
-          docker manifest push "${IMAGE}:${VERSION}"
           echo "✅ Pushed manifest: ${IMAGE}:${VERSION}"
 
           # Also create 'latest' tag for stable releases (not 
edge/rc/alpha/beta)
           if [[ ! "$VERSION" =~ (edge|rc|alpha|beta) ]]; then
             echo "Creating 'latest' manifest"
-            docker manifest create "${IMAGE}:latest" \
-              "${IMAGE}:${VERSION}-amd64" \
-              "${IMAGE}:${VERSION}-arm64"
-            docker manifest push "${IMAGE}:latest"
+            docker buildx imagetools create \
+              -t "${IMAGE}:latest" \
+              $(printf "${IMAGE}@sha256:%s " *)
             echo "✅ Pushed manifest: ${IMAGE}:latest"
           fi
 
+      - name: Inspect manifest
+        run: |
+          docker buildx imagetools inspect "${{ steps.config.outputs.image 
}}:${{ steps.ver.outputs.version }}"
+
   # Non-Docker, non-Rust publishing (Python, Node, Java, C#, Go SDKs)
   # Note: This job runs in parallel with Docker publishing - no dependency 
between them
   publish:

Reply via email to