@@ -177,17 +177,60 @@ jobs:
177177 file : operator/reporting/cover.out
178178 format : golang
179179
180- # Build multi-platform container image and push to registry
181- build-and-push-operator :
180+ # Compute image tags and version metadata once for reuse
181+ compute-metadata :
182182 runs-on : ubuntu-latest
183- needs : [tests] # Don't run the build and push if tests fail
184- # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
183+ needs : [tests]
184+ outputs :
185+ git-sha : ${{ steps.meta.outputs.git-sha }}
186+ version : ${{ steps.meta.outputs.version }}
187+ tags : ${{ steps.meta.outputs.tags }}
188+ steps :
189+ - uses : actions/checkout@v4
190+ - name : Fetch all tags
191+ run : git fetch --tags --force
192+ - name : Compute metadata
193+ id : meta
194+ run : |
195+ export GIT_SHA=$(git rev-parse --short ${{ github.sha }})
196+ echo "git-sha=${GIT_SHA}" >> $GITHUB_OUTPUT
197+
198+ case ${{ github.ref_type }} in
199+ branch)
200+ export VERSION=$(git tag --list 'operator*' --sort=-v:refname | head -n 1 | cut -d/ -f2)+${GIT_SHA}
201+ TAGS="${GIT_SHA} $(echo "${VERSION}" | tr + -)"
202+ ;;
203+ tag)
204+ export VERSION=$(echo "${{ github.ref_name }}" | cut -f 2 -d /)
205+ TAGS="${GIT_SHA} ${VERSION} latest"
206+ ;;
207+ *)
208+ echo "Unknown ref type: ${{ github.ref_type }}"
209+ exit 1
210+ ;;
211+ esac
212+
213+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
214+ echo "tags=${TAGS}" >> $GITHUB_OUTPUT
215+ echo "📦 Version: ${VERSION}"
216+ echo "🏷️ Tags: ${TAGS}"
217+
218+ # Build container images on native architecture runners (much faster than QEMU)
219+ build-operator :
220+ runs-on : ${{ matrix.runner }}
221+ needs : [compute-metadata]
222+ strategy :
223+ matrix :
224+ include :
225+ - platform : linux/amd64
226+ runner : ubuntu-latest
227+ - platform : linux/arm64
228+ runner : ubuntu-24.04-arm
185229 permissions :
186230 contents : read
187231 packages : write
188232 attestations : write
189233 id-token : write
190- #
191234 steps :
192235 - name : Checkout repository
193236 uses : actions/checkout@v4
@@ -199,60 +242,95 @@ jobs:
199242 registry : ${{ env.REGISTRY }}
200243 username : ${{ github.actor }}
201244 password : ${{ secrets.GITHUB_TOKEN }}
202-
203- # Setup for multi-platform builds (linux/amd64, linux/arm64)
204- - name : Set up QEMU
205- uses : docker/setup-qemu-action@v3
206245
207246 - name : Set up Docker Buildx
208247 uses : docker/setup-buildx-action@v3
209248
210- # Build and tag container image based on git ref type
211- - name : Build the operator container image
249+ # Build and tag container image for single platform on native hardware
250+ - name : Build the operator container image (${{ matrix.platform }})
212251 id : build
213252 env :
214- platforms : ${{ env.PLATFORMS }}
253+ GIT_SHA : ${{ needs.compute-metadata.outputs.git-sha }}
254+ VERSION : ${{ needs.compute-metadata.outputs.version }}
215255 run : |
216- apt-get update && apt-get install -y make git jq
256+ sudo apt-get update && sudo apt-get install -y jq
217257 cd operator
218- # if this is a tag build, use the tag as the version, otherwise use the sha
219- git fetch --all
220- export GIT_SHA=$(git rev-parse --short ${{ github.sha }})
221- TAGS="-t ${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${GIT_SHA}"
222- case ${{ github.ref_type }} in
223- branch)
224- # The last tag + current git sha
225- export OPERATOR_VERSION=$(git tag --list 'operator*' --sort=-v:refname | head -n 1 | cut -d/ -f2)+${GIT_SHA}
226- TAGS="$TAGS -t ${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:$(echo "${OPERATOR_VERSION}" | tr + -)"
227- ;;
228- tag)
229- # The version part of the tag
230- export OPERATOR_VERSION=$(echo "${{ github.ref_name }}" | cut -f 2 -d /)
231- TAGS="$TAGS -t ${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${OPERATOR_VERSION} -t ${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:latest"
232- ;;
233- *)
234- echo "Unkown type ${{ github.ref_type }}"
235- exit 1
236- ;;
237- esac
258+ PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')
259+
260+ # Build platform-specific tags for all target tags
261+ TAGS=""
262+ for TAG in ${{ needs.compute-metadata.outputs.tags }}; do
263+ TAGS="$TAGS -t ${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${TAG}-${PLATFORM_TAG}"
264+ done
265+
238266 set -x
239267 docker buildx build \
240268 --build-arg GIT_SHA=${GIT_SHA} \
241- --build-arg VERSION=${OPERATOR_VERSION } \
242- --build-arg GO_VERSION=${GO_VERSION} \
269+ --build-arg VERSION=${VERSION } \
270+ --build-arg GO_VERSION=${{ env. GO_VERSION } } \
243271 --push \
244- --platform ${{ env.PLATFORMS }} \
272+ --platform ${{ matrix.platform }} \
273+ --provenance=false \
245274 ${TAGS@L} \
246275 --metadata-file=metadata.json \
247276 -f ../containers/operator.Dockerfile .
248- cat metadata.json
277+
249278 echo "digest=$(cat metadata.json | jq -r .\"containerimage.digest\")" >> $GITHUB_OUTPUT
250- cat $GITHUB_OUTPUT
279+
280+ # Create multi-platform manifest from individual architecture builds
281+ create-manifest :
282+ runs-on : ubuntu-latest
283+ needs : [compute-metadata, build-operator]
284+ permissions :
285+ contents : read
286+ packages : write
287+ attestations : write
288+ id-token : write
289+ steps :
290+ - name : Log in to the Container registry
291+ uses : docker/login-action@v3
292+ with :
293+ registry : ${{ env.REGISTRY }}
294+ username : ${{ github.actor }}
295+ password : ${{ secrets.GITHUB_TOKEN }}
296+
297+ # Create and push multi-platform manifests, then delete platform-specific tags
298+ - name : Create manifests and cleanup
299+ id : manifest
300+ run : |
301+ # Create manifest for each tag combining amd64 and arm64 images
302+ for TAG in ${{ needs.compute-metadata.outputs.tags }}; do
303+ FULL_TAG="${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${TAG}"
304+ echo "📦 Creating manifest for $FULL_TAG"
305+ docker manifest create $FULL_TAG \
306+ ${FULL_TAG}-linux-amd64 \
307+ ${FULL_TAG}-linux-arm64
308+ docker manifest push $FULL_TAG
309+ echo "✅ Pushed $FULL_TAG"
310+ done
311+
312+ # Get digest of the main tag (git sha) for attestation
313+ MAIN_TAG="${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${{ needs.compute-metadata.outputs.git-sha }}"
314+ DIGEST=$(docker manifest inspect $MAIN_TAG | jq -r .manifests[0].digest)
315+ echo "digest=$DIGEST" >> $GITHUB_OUTPUT
316+
317+ # Clean up platform-specific tags to avoid confusion
318+ echo "🗑️ Cleaning up platform-specific tags..."
319+ for TAG in ${{ needs.compute-metadata.outputs.tags }}; do
320+ for PLATFORM in linux-amd64 linux-arm64; do
321+ PLATFORM_TAG="${REGISTRY@L}/${{env.IMAGE_NAME}}/operator:${TAG}-${PLATFORM}"
322+ echo "Deleting ${PLATFORM_TAG}"
323+ docker run --rm quay.io/skopeo/stable:latest delete \
324+ --creds ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} \
325+ docker://${PLATFORM_TAG} 2>&1 || echo " (tag may not exist)"
326+ done
327+ done
328+ echo "✅ Cleanup complete"
251329
252- # Generate supply chain security attestation
330+ # Generate supply chain security attestation for the multi-platform manifest
253331 - name : Generate artifact attestation
254332 uses : actions/attest-build-provenance@v2
255333 with :
256334 subject-name : ${{ env.REGISTRY }}/${{env.IMAGE_NAME}}/operator
257- subject-digest : ${{ steps.build .outputs.digest }}
335+ subject-digest : ${{ steps.manifest .outputs.digest }}
258336 push-to-registry : true
0 commit comments