1515 type : string
1616 required : true
1717 description : ' The images to publish'
18- trigger_internal_ci :
19- description : ' Trigger the internal CI'
20- required : false
21- type : boolean
22- default : false
2318 workflow_dispatch :
24- inputs :
25- trigger_internal_ci :
26- description : ' Trigger the internal CI'
27- required : true
28- type : boolean
29- default : false
3019
3120permissions :
3221 contents : read
@@ -35,20 +24,23 @@ permissions:
3524jobs :
3625 build-and-push-image :
3726 name : Build and Push Docker Image
38- runs-on : ubuntu-22.04
27+ strategy :
28+ fail-fast : false
29+ matrix :
30+ include :
31+ - runs_on : ubuntu-22.04
32+ platform : linux/amd64
33+ - runs_on : ubuntu-22.04-arm
34+ platform : linux/arm64
35+ runs-on : ${{ matrix.runs_on }}
3936
4037 environment : ${{ inputs.environment }}
4138 steps :
4239
4340 - name : Checkout Repo
4441 uses : actions/checkout@v4
4542
46- - name : Set up QEMU
47- if : ${{ github.event_name != 'pull_request' }}
48- uses : docker/setup-qemu-action@v3
49-
5043 - name : Set up Docker Buildx
51- if : ${{ github.event_name != 'pull_request' }}
5244 uses : docker/setup-buildx-action@v3
5345
5446 - name : Log in to Docker Hub
@@ -76,26 +68,192 @@ jobs:
7668 # use the branch + sha if workflow_dispatch
7769 tags : ${{ inputs.tags || format('type=raw,value={0}-{1}', github.ref_name, github.sha) }}
7870
79- - name : Push to Registry(s)
71+ - name : Compute cache scopes
72+ # BuildKit GHA cache scopes to avoid cross-arch/branch clobbering.
73+ # Shared scope depends on TARGET branch (PRs use github.base_ref):
74+ # - default(master) -> shared-default
75+ # - release/* -> shared-release-<release-branch>
76+ # Writes per event:
77+ # - pull_request : read shared+branch, write branch
78+ # - workflow_dispatch : if TARGET is master/release/* write shared+branch; else like PR
79+ # - push (master/release/*): read shared, write shared+branch
80+ id : cache
81+ shell : bash
82+ env :
83+ REPO : ${{ github.repository }}
84+ PLATFORM : ${{ matrix.platform }}
85+ REF : ${{ github.ref_name }}
86+ EVENT : ${{ github.event_name }}
87+ DEFAULT_BRANCH : ${{ github.event.repository.default_branch }}
88+ BASE_REF : ${{ github.base_ref }}
89+ run : |
90+ set -euo pipefail
91+ # Determine TARGET branch for shared cache (master or a specific release/*)
92+ TARGET_REF="${REF}"
93+ if [ "${EVENT}" = "pull_request" ] && [ -n "${BASE_REF}" ]; then
94+ TARGET_REF="${BASE_REF}"
95+ fi
96+ if [ "${TARGET_REF}" = "${DEFAULT_BRANCH}" ]; then
97+ SHARED_SUFFIX="default"
98+ elif [[ "${TARGET_REF}" == release/* ]]; then
99+ SANITIZED_TARGET=$(echo "${TARGET_REF}" | sed 's|/|-|g')
100+ SHARED_SUFFIX="release-${SANITIZED_TARGET}"
101+ else
102+ SHARED_SUFFIX="default"
103+ fi
104+ SHARED="${REPO}-${PLATFORM}-shared-${SHARED_SUFFIX}"
105+ BRANCH="${REPO}-${PLATFORM}-${REF}"
106+ case "${EVENT}" in
107+ pull_request)
108+ FROM=$(printf '%s\n' "type=gha,scope=${SHARED}" "type=gha,scope=${BRANCH}")
109+ TO=$(printf '%s\n' "type=gha,mode=max,scope=${BRANCH}")
110+ ;;
111+ workflow_dispatch)
112+ if [ "${TARGET_REF}" = "${DEFAULT_BRANCH}" ] || [[ "${TARGET_REF}" == release/* ]]; then
113+ FROM=$(printf '%s\n' "type=gha,scope=${SHARED}")
114+ TO=$(printf '%s\n' "type=gha,mode=max,scope=${SHARED}" "type=gha,mode=max,scope=${BRANCH}")
115+ else
116+ FROM=$(printf '%s\n' "type=gha,scope=${SHARED}" "type=gha,scope=${BRANCH}")
117+ TO=$(printf '%s\n' "type=gha,mode=max,scope=${BRANCH}")
118+ fi
119+ ;;
120+ *)
121+ FROM=$(printf '%s\n' "type=gha,scope=${SHARED}")
122+ TO=$(printf '%s\n' "type=gha,mode=max,scope=${SHARED}" "type=gha,mode=max,scope=${BRANCH}")
123+ ;;
124+ esac
125+ echo "from<<EOF" >> "$GITHUB_OUTPUT"; echo "${FROM}" >> "$GITHUB_OUTPUT"; echo "EOF" >> "$GITHUB_OUTPUT"
126+ echo "to<<EOF" >> "$GITHUB_OUTPUT"; echo "${TO}" >> "$GITHUB_OUTPUT"; echo "EOF" >> "$GITHUB_OUTPUT"
127+
128+ - name : Compute arch-suffixed tags
129+ id : tags
130+ shell : bash
131+ env :
132+ META_TAGS : ${{ steps.meta.outputs.tags }}
133+ run : |
134+ set -euo pipefail
135+ case "${{ matrix.platform }}" in
136+ linux/amd64) ARCH="amd64" ;;
137+ linux/arm64) ARCH="arm64" ;;
138+ *) echo "Unsupported platform: ${{ matrix.platform }}" >&2; exit 1 ;;
139+ esac
140+ TMP_FILE="$(mktemp)"
141+ printf '%s\n' "${META_TAGS}" | awk -v a="$ARCH" 'NF{print $0 "-" a}' > "$TMP_FILE"
142+ if [ ! -s "$TMP_FILE" ]; then
143+ echo "No arch-suffixed tags produced" >&2
144+ exit 1
145+ fi
146+ echo "tags<<EOF" >> "$GITHUB_OUTPUT"
147+ cat "$TMP_FILE" >> "$GITHUB_OUTPUT"
148+ echo "EOF" >> "$GITHUB_OUTPUT"
149+ rm -f "$TMP_FILE"
150+
151+ - name : Build and push (${{ matrix.platform }})
152+ id : build
153+ if : ${{ github.event_name != 'pull_request' || matrix.platform == 'linux/amd64' }}
80154 uses : docker/build-push-action@v6
81155 with :
82156 context : .
83- platforms : ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
157+ platforms : ${{ matrix.platform }}
84158 push : ${{ github.event_name != 'pull_request' }}
85159 load : ${{ github.event_name == 'pull_request' }}
86160 provenance : ${{ github.event_name != 'pull_request' }}
87161 sbom : ${{ github.event_name != 'pull_request' }}
88- tags : ${{ steps.meta .outputs.tags }}
162+ tags : ${{ steps.tags .outputs.tags }}
89163 labels : ${{ steps.meta.outputs.labels }}
164+ cache-from : ${{ steps.cache.outputs.from }}
165+ cache-to : ${{ steps.cache.outputs.to }}
166+
167+ - name : Compute safe platform name
168+ id : platform
169+ shell : bash
170+ run : |
171+ SAFE=$(echo "${{ matrix.platform }}" | tr '/' '_')
172+ echo "safe=${SAFE}" >> $GITHUB_OUTPUT
173+
174+ - name : Export digest
175+ if : ${{ github.event_name != 'pull_request' }}
176+ shell : bash
177+ run : |
178+ PLATFORM="${{ matrix.platform }}"
179+ SAFE="${PLATFORM//\//_}"
180+ echo "${{ steps.build.outputs.digest }}" > "digest-${SAFE}.txt"
181+
182+ - name : Upload digest artifact
183+ if : ${{ github.event_name != 'pull_request' }}
184+ uses : actions/upload-artifact@v4
185+ with :
186+ name : digest-${{ steps.platform.outputs.safe }}
187+ path : digest-${{ steps.platform.outputs.safe }}.txt
90188
91189 - name : Scan for vulnerabilities
92190 uses : crazy-max/ghaction-container-scan@v3
93- if : ${{ github.event_name == 'pull_request' || github.ref_name == 'master' }}
191+ if : ${{ ( github.event_name == 'pull_request' && matrix.platform == 'linux/amd64') || ( github.ref_name == 'master' && matrix.platform == 'linux/amd64') }}
94192 with :
95- image : ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
193+ image : ${{ github.event_name == 'pull_request' && steps.tags.outputs.tags || fromJSON(steps.meta.outputs.json).tags[0] }}
96194 annotations : true
97195 severity : LOW
98196 dockerfile : ./Dockerfile
99197 env :
100198 # See https://github.com/aquasecurity/trivy/discussions/7538
101199 TRIVY_DB_REPOSITORY : public.ecr.aws/aquasecurity/trivy-db:2
200+
201+ merge-manifest :
202+ name : Create multi-arch manifest
203+ needs : [build-and-push-image]
204+ if : ${{ github.event_name != 'pull_request' }}
205+ runs-on : ubuntu-22.04
206+ steps :
207+ - name : Checkout Repo
208+ uses : actions/checkout@v4
209+
210+ - name : Log in to the Container registry
211+ uses : docker/login-action@v3
212+ with :
213+ registry : ghcr.io
214+ username : ${{ github.actor }}
215+ password : ${{ secrets.GITHUB_TOKEN }}
216+
217+ - name : Log in to Docker Hub
218+ uses : docker/login-action@v3
219+ if : ${{ inputs.environment == 'docker-publish' }}
220+ with :
221+ username : ${{ secrets.DOCKERHUB_USERNAME }}
222+ password : ${{ secrets.DOCKERHUB_TOKEN }}
223+
224+ - name : Set up Docker Buildx
225+ uses : docker/setup-buildx-action@v3
226+
227+ - name : Extract metadata (tags, labels) for Docker
228+ id : meta
229+ uses : docker/metadata-action@v5
230+ with :
231+ # default to ghcr.io for workflow_dispatch
232+ images : ${{ inputs.images || format('ghcr.io/{0}', github.repository) }}
233+ # use the branch + sha if workflow_dispatch
234+ tags : ${{ inputs.tags || format('type=raw,value={0}-{1}', github.ref_name, github.sha) }}
235+
236+ - name : Download digests
237+ uses : actions/download-artifact@v4
238+ with :
239+ pattern : digest-*
240+ path : ./digests
241+ merge-multiple : true
242+
243+ - name : Create and push manifest list
244+ env :
245+ TAGS : ${{ steps.meta.outputs.tags }}
246+ IMAGE : ${{ inputs.images || format('ghcr.io/{0}', github.repository) }}
247+ shell : bash
248+ run : |
249+ AMD64_DIGEST=$(cat digests/digest-linux_amd64.txt)
250+ ARM64_DIGEST=$(cat digests/digest-linux_arm64.txt)
251+ TAG_ARGS=""
252+ while IFS= read -r tag; do
253+ [ -z "$tag" ] && continue
254+ TAG_ARGS="$TAG_ARGS -t $tag"
255+ done <<< "$TAGS"
256+ docker buildx imagetools create \
257+ $TAG_ARGS \
258+ "$IMAGE@${AMD64_DIGEST}" \
259+ "$IMAGE@${ARM64_DIGEST}"
0 commit comments