Skip to content

Commit b40a19f

Browse files
committed
feat(ci): split docker platforms speed up docker build time
1 parent 495f6ac commit b40a19f

File tree

1 file changed

+118
-40
lines changed

1 file changed

+118
-40
lines changed

.github/workflows/operator-ci.yaml

Lines changed: 118 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)