99 - main
1010 paths :
1111 - ' calm-hub/**'
12+ - ' calm-hub-ui/**'
1213 - ' .github/workflows/docker-publish-calm-hub-native.yml'
1314 # Manual trigger from GitHub UI (allows custom tag)
1415 workflow_dispatch :
@@ -23,9 +24,24 @@ concurrency:
2324 cancel-in-progress : false
2425
2526jobs :
26- build-and-push :
27- name : Build and Push Native Docker Image
28- runs-on : ubuntu-latest
27+ # ── Per-architecture build, smoke test, and digest push ──────────────────
28+ # Each runner compiles the native binary for its own architecture (GraalVM
29+ # native binaries are arch-specific) via Quarkus container-build. After a
30+ # Docker smoke test the image is pushed to the registry by digest only (no
31+ # tag yet). Digests are uploaded as artifacts for the merge job.
32+ build :
33+ name : Build ${{ matrix.arch }}
34+ runs-on : ${{ matrix.runner }}
35+ strategy :
36+ fail-fast : false
37+ matrix :
38+ include :
39+ - arch : amd64
40+ runner : ubuntu-latest
41+ platform : linux/amd64
42+ - arch : arm64
43+ runner : ubuntu-24.04-arm
44+ platform : linux/arm64
2945
3046 steps :
3147 # Step 1: Checkout the repository
@@ -44,29 +60,131 @@ jobs:
4460 uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
4561 with :
4662 path : ~/.m2
47- key : ${{ runner.os }}-m2-${{ hashFiles('calm-hub/pom.xml') }}
63+ key : ${{ runner.os }}-${{ matrix.arch }}- m2-${{ hashFiles('calm-hub/pom.xml') }}
4864 restore-keys : |
49- ${{ runner.os }}-m2-
65+ ${{ runner.os }}-${{ matrix.arch }}- m2-
5066
51- # Step 4: Compile the native binary via the shared build script.
52- # --no-docker: skip the local docker build; this workflow uses buildx below
53- # for the push. Quarkus pulls and runs the Mandrel builder container
54- # (quarkus.native.container-build=true) to produce a linux/amd64 binary.
67+ # Step 4: Compile the native binary for this runner's architecture.
68+ # --no-docker: skip the local docker build; buildx handles the image below.
69+ # Quarkus pulls and runs the Mandrel builder container
70+ # (quarkus.native.container-build=true) to produce a native binary
71+ # targeting the runner's architecture.
5572 - name : Build native binary
5673 run : bash calm-hub/build-native-image.sh --no-docker
5774
5875 # Step 5: Set up Docker Buildx
5976 - name : Set up Docker Buildx
6077 uses : docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
6178
62- # Step 6: Login to Docker Hub
79+ # Step 6: Login to Docker Hub (required for the push-by-digest step)
80+ - name : Login to Docker Hub
81+ uses : docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
82+ with :
83+ username : ${{ secrets.DOCKER_USERNAME }}
84+ password : ${{ secrets.DOCKER_PASSWORD }}
85+
86+ # Step 7: Build image locally for smoke testing.
87+ # load: true loads the image into the local Docker daemon (single-arch only).
88+ # cache-to populates the GHA layer cache scoped per-arch so the push step
89+ # below is a near-instant cache hit.
90+ - name : Build image for smoke test
91+ uses : docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
92+ with :
93+ context : calm-hub
94+ file : calm-hub/src/main/docker/Dockerfile.native
95+ platforms : ${{ matrix.platform }}
96+ load : true
97+ tags : calm-hub:smoke
98+ cache-from : type=gha,scope=native-${{ matrix.arch }}
99+ cache-to : type=gha,mode=max,scope=native-${{ matrix.arch }}
100+
101+ # Step 8: Smoke test — start a transient MongoDB and the calm-hub container
102+ # on a private Docker network, run the smoke-test.sh assertions, then stop.
103+ # The test validates: GET /api/calm/namespaces -> 200 and POST -> 201 (writes
104+ # enabled; this is the writable MongoDB-backed image).
105+ # Auth is disabled for the smoke test (the prod image defaults to auth-on).
106+ - name : Smoke test
107+ run : |
108+ docker network create smoke-net
109+ docker run -d --name mongo --network smoke-net mongo:8
110+ docker run -d --name calmhub --network smoke-net \
111+ -p 8080:8080 \
112+ -e QUARKUS_MONGODB_CONNECTION_STRING=mongodb://mongo:27017 \
113+ -e CALM_AUTH_ENABLED=false \
114+ -e QUARKUS_OIDC_TENANT_ENABLED=false \
115+ calm-hub:smoke
116+ bash calm-hub/smoke-test.sh http://localhost:8080 readwrite
117+
118+ - name : Dump container logs on failure
119+ if : failure()
120+ run : docker logs calmhub 2>/dev/null || true
121+
122+ - name : Cleanup smoke test containers
123+ if : always()
124+ run : |
125+ docker rm -f calmhub mongo 2>/dev/null || true
126+ docker network rm smoke-net 2>/dev/null || true
127+
128+ # Produce labels without tags (only the merge job applies tags)
129+ - name : Extract Docker labels
130+ id : meta-labels
131+ uses : docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
132+ with :
133+ images : ${{ secrets.DOCKER_USERNAME }}/calm-hub
134+
135+ # Step 9: Push this architecture's image by digest (no tag).
136+ # Tags are applied in the merge job once both arch digests are available.
137+ # The GHA layer cache from step 7 (load) makes this build a cache hit.
138+ - name : Build and push by digest
139+ id : push
140+ uses : docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
141+ with :
142+ context : calm-hub
143+ file : calm-hub/src/main/docker/Dockerfile.native
144+ platforms : ${{ matrix.platform }}
145+ outputs : type=image,name=${{ secrets.DOCKER_USERNAME }}/calm-hub,push-by-digest=true,name-canonical=true,push=true
146+ labels : ${{ steps.meta-labels.outputs.labels }}
147+ cache-from : type=gha,scope=native-${{ matrix.arch }}
148+ cache-to : type=gha,mode=max,scope=native-${{ matrix.arch }}
149+
150+ # Step 10: Export the pushed digest to a file for the merge job.
151+ - name : Export digest
152+ run : |
153+ mkdir -p /tmp/digests
154+ digest="${{ steps.push.outputs.digest }}"
155+ touch "/tmp/digests/${digest#sha256:}"
156+
157+ - name : Upload digest
158+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
159+ with :
160+ name : digests-native-${{ matrix.arch }}
161+ path : /tmp/digests/*
162+ if-no-files-found : error
163+ retention-days : 1
164+
165+ # ── Merge per-arch digests into a single multi-arch manifest ─────────────
166+ merge :
167+ name : Merge multi-arch manifest
168+ runs-on : ubuntu-latest
169+ needs : build
170+
171+ steps :
172+ - name : Download digests
173+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
174+ with :
175+ path : /tmp/digests
176+ pattern : digests-native-*
177+ merge-multiple : true
178+
179+ - name : Set up Docker Buildx
180+ uses : docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
181+
63182 - name : Login to Docker Hub
64183 uses : docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
65184 with :
66185 username : ${{ secrets.DOCKER_USERNAME }}
67186 password : ${{ secrets.DOCKER_PASSWORD }}
68187
69- # Step 7: Extract metadata for Docker
70188 - name : Extract Docker metadata
71189 id : meta
72190 uses : docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
@@ -77,17 +195,13 @@ jobs:
77195 type=sha,format=short,suffix=-native
78196 type=ref,event=tag,suffix=-native
79197
80- # Step 8: Build and push the native image.
81- # Native binaries are architecture-specific; the binary compiled above is
82- # linux/amd64, so the image is published for linux/amd64 only.
83- - name : Build and push
84- uses : docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
85- with :
86- context : calm-hub
87- file : calm-hub/src/main/docker/Dockerfile.native
88- platforms : linux/amd64
89- push : true
90- tags : ${{ steps.meta.outputs.tags }}
91- labels : ${{ steps.meta.outputs.labels }}
92- cache-from : type=gha
93- cache-to : type=gha,mode=max
198+ # Assemble a multi-arch manifest from the per-arch digests and tag it.
199+ - name : Create multi-arch manifest
200+ working-directory : /tmp/digests
201+ run : |
202+ docker buildx imagetools create \
203+ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
204+ $(printf '${{ secrets.DOCKER_USERNAME }}/calm-hub@sha256:%s ' *)
205+
206+ - name : Inspect manifest
207+ run : docker buildx imagetools inspect ${{ secrets.DOCKER_USERNAME }}/calm-hub:${{ steps.meta.outputs.version }}
0 commit comments