3636 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
3737 run : echo "${GITHUB_TOKEN}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin
3838
39+ - name : Set up QEMU
40+ uses : docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130
41+ with :
42+ platforms : arm64
43+
44+ - name : Set up Docker Buildx
45+ uses : docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
46+
3947 - name : Resolve published package source
4048 id : package
4149 env :
5664
5765 version="$(jq -r '.version' <<<"${metadata}")"
5866 git_head="$(jq -r '.gitHead' <<<"${metadata}")"
67+ short_sha="${git_head:0:7}"
5968 tarball="$(jq -r '.dist.tarball' <<<"${metadata}")"
6069 latest_version="$(npm view @atomicmemory/core@latest version)"
6170
@@ -69,32 +78,64 @@ jobs:
6978 exit 1
7079 fi
7180
72- manifest_digest() {
73- docker manifest inspect "$1" --verbose 2>/dev/null | jq -r 'if type == "array" then .[0].Descriptor.digest else .Descriptor.digest // empty end'
81+ tag_exists() {
82+ docker manifest inspect "$1" >/dev/null 2>&1
83+ }
84+
85+ tag_has_platform() {
86+ local image_ref="$1"
87+ local os="$2"
88+ local arch="$3"
89+ docker manifest inspect "${image_ref}" --verbose 2>/dev/null | jq -e --arg os "${os}" --arg arch "${arch}" '
90+ if type == "array" then
91+ any(.[]; .Descriptor.platform.os == $os and .Descriptor.platform.architecture == $arch)
92+ else
93+ .Descriptor.platform.os == $os and .Descriptor.platform.architecture == $arch
94+ end
95+ ' >/dev/null
96+ }
97+
98+ tag_has_required_platforms() {
99+ tag_has_platform "$1" linux amd64 && tag_has_platform "$1" linux arm64
74100 }
75101
76- version_digest="$(manifest_digest "${IMAGE_NAME}:${version}" || true)"
77- latest_digest="$(manifest_digest "${IMAGE_NAME}:latest" || true)"
78102 is_latest="$([[ "${version}" == "${latest_version}" ]] && echo true || echo false)"
103+ release_refs=(
104+ "${IMAGE_NAME}:${version}"
105+ "${IMAGE_NAME}:${short_sha}"
106+ "${IMAGE_NAME}:sha-${short_sha}"
107+ )
108+ missing_platform_refs=()
109+
110+ for image_ref in "${release_refs[@]}"; do
111+ if ! tag_exists "${image_ref}" || ! tag_has_required_platforms "${image_ref}"; then
112+ missing_platform_refs+=("${image_ref}")
113+ fi
114+ done
115+
116+ release_tags_have_required_platforms=false
117+ if [[ "${#missing_platform_refs[@]}" == "0" ]]; then
118+ release_tags_have_required_platforms=true
119+ fi
79120
80- if [[ -n "${version_digest }" && "${is_latest}" == "true" && "${version_digest }" != "${latest_digest} " ]]; then
121+ if [[ "${release_tags_have_required_platforms }" == "true" && "${is_latest }" == "true " ]]; then
81122 {
82123 echo "should_publish=true"
83124 echo "retag_latest_only=true"
84125 echo "version=${version}"
85126 echo "git_head=${git_head}"
86- echo "short_sha=${git_head:0:7 }"
127+ echo "short_sha=${short_sha }"
87128 echo "tarball=${tarball}"
88129 echo "is_latest=true"
89130 } >>"${GITHUB_OUTPUT}"
90- echo "${IMAGE_NAME}:${version} already exists; moving latest to the same digest ."
131+ echo "Release tags already include linux/amd64 and linux/arm64; ensuring latest points to ${IMAGE_NAME}:${version} ."
91132 exit 0
92133 fi
93134
94- if [[ -n "${version_digest} " ]]; then
135+ if [[ "${release_tags_have_required_platforms}" == "true " ]]; then
95136 echo "should_publish=false" >>"${GITHUB_OUTPUT}"
96- echo "skip_reason=${IMAGE_NAME}:${version} already exists and latest is current ." >>"${GITHUB_OUTPUT}"
97- echo "${IMAGE_NAME}:${version} already exists and latest is current ; no Docker publish required."
137+ echo "skip_reason=Release tags already include linux/amd64 and linux/arm64 ." >>"${GITHUB_OUTPUT}"
138+ echo "Release tags already include linux/amd64 and linux/arm64 ; no Docker publish required."
98139 exit 0
99140 fi
100141
@@ -103,14 +144,16 @@ jobs:
103144 echo "retag_latest_only=false"
104145 echo "version=${version}"
105146 echo "git_head=${git_head}"
106- echo "short_sha=${git_head:0:7 }"
147+ echo "short_sha=${short_sha }"
107148 echo "tarball=${tarball}"
108149 echo "is_latest=${is_latest}"
109150 } >>"${GITHUB_OUTPUT}"
110151
111152 echo "Resolved @atomicmemory/core@${version}"
112153 echo "gitHead=${git_head}"
113154 echo "tarball=${tarball}"
155+ printf 'Rebuilding release tags without complete linux/amd64 and linux/arm64 coverage:\n'
156+ printf ' - %s\n' "${missing_platform_refs[@]}"
114157
115158 - name : Report skipped publish
116159 if : steps.package.outputs.should_publish != 'true'
@@ -140,6 +183,7 @@ jobs:
140183 set -euo pipefail
141184
142185 docker build \
186+ --platform linux/amd64 \
143187 --file release-source/packages/core/Dockerfile \
144188 --label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
145189 --label "org.opencontainers.image.revision=${{ steps.package.outputs.git_head }}" \
@@ -295,25 +339,78 @@ jobs:
295339 -d '{"user_id":"x"}')"
296340 test "${bad_status}" = "400"
297341
298- - name : Push release tags
342+ - name : Build and push multi-platform release tags
299343 if : steps.package.outputs.should_publish == 'true' && steps.package.outputs.retag_latest_only != 'true'
300344 run : |
301345 set -euo pipefail
302346
303- docker push "${IMAGE_NAME}:${{ steps.package.outputs.version }}"
304- docker push "${IMAGE_NAME}:${{ steps.package.outputs.short_sha }}"
305- docker push "${IMAGE_NAME}:sha-${{ steps.package.outputs.short_sha }}"
347+ tag_args=(
348+ --tag "${IMAGE_NAME}:${{ steps.package.outputs.version }}"
349+ --tag "${IMAGE_NAME}:${{ steps.package.outputs.short_sha }}"
350+ --tag "${IMAGE_NAME}:sha-${{ steps.package.outputs.short_sha }}"
351+ )
306352
307353 if [[ "${{ steps.package.outputs.is_latest }}" == "true" ]]; then
308- docker push "${IMAGE_NAME}:latest"
354+ tag_args+=(--tag "${IMAGE_NAME}:latest")
309355 else
310356 echo "Not moving latest: ${{ steps.package.outputs.version }} is not npm latest."
311357 fi
312358
359+ docker buildx build \
360+ --platform linux/amd64,linux/arm64 \
361+ --file release-source/packages/core/Dockerfile \
362+ --label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
363+ --label "org.opencontainers.image.revision=${{ steps.package.outputs.git_head }}" \
364+ --label "org.opencontainers.image.version=${{ steps.package.outputs.version }}" \
365+ --label "org.opencontainers.image.title=@atomicmemory/core" \
366+ "${tag_args[@]}" \
367+ --push \
368+ release-source
369+
313370 - name : Move latest to existing version image
314371 if : steps.package.outputs.should_publish == 'true' && steps.package.outputs.retag_latest_only == 'true'
315372 run : |
316373 set -euo pipefail
317374 docker buildx imagetools create \
318375 --tag "${IMAGE_NAME}:latest" \
319376 "${IMAGE_NAME}:${{ steps.package.outputs.version }}"
377+
378+ - name : Verify release platforms
379+ if : steps.package.outputs.should_publish == 'true'
380+ run : |
381+ set -euo pipefail
382+
383+ assert_platform() {
384+ local image_ref="$1"
385+ local os="$2"
386+ local arch="$3"
387+ docker manifest inspect "${image_ref}" --verbose | jq -e --arg os "${os}" --arg arch "${arch}" '
388+ if type == "array" then
389+ any(.[]; .Descriptor.platform.os == $os and .Descriptor.platform.architecture == $arch)
390+ else
391+ .Descriptor.platform.os == $os and .Descriptor.platform.architecture == $arch
392+ end
393+ ' >/dev/null
394+ }
395+
396+ verify_tag() {
397+ local image_ref="$1"
398+ for attempt in {1..12}; do
399+ if assert_platform "${image_ref}" linux amd64 && assert_platform "${image_ref}" linux arm64; then
400+ echo "${image_ref} includes linux/amd64 and linux/arm64."
401+ return 0
402+ fi
403+ sleep 5
404+ done
405+
406+ echo "::error::${image_ref} does not include both linux/amd64 and linux/arm64."
407+ exit 1
408+ }
409+
410+ verify_tag "${IMAGE_NAME}:${{ steps.package.outputs.version }}"
411+ verify_tag "${IMAGE_NAME}:${{ steps.package.outputs.short_sha }}"
412+ verify_tag "${IMAGE_NAME}:sha-${{ steps.package.outputs.short_sha }}"
413+
414+ if [[ "${{ steps.package.outputs.is_latest }}" == "true" ]]; then
415+ verify_tag "${IMAGE_NAME}:latest"
416+ fi
0 commit comments