1414 release_commit :
1515 description : ' Full source commit for the existing immutable release tag'
1616 required : true
17+ existing_image_digest :
18+ description : ' Continue verification of this existing sha256 index without rebuilding images'
19+ required : false
20+ default : ' '
21+ type : string
1722permissions :
1823 contents : read
1924
@@ -104,6 +109,8 @@ jobs:
104109 id : release_publish
105110 env :
106111 INPUT_TAG : ${{ github.event.inputs.tag }}
112+ INPUT_RELEASE_COMMIT : ${{ inputs.release_commit }}
113+ INPUT_EXISTING_IMAGE_DIGEST : ${{ inputs.existing_image_digest }}
107114 SOURCE_RELEASE_VERSION : ${{ steps.source_release.outputs.version }}
108115 DOCKERHUB_IMAGE : ${{ env.DOCKERHUB_IMAGE }}
109116 GHCR_IMAGE : ${{ env.GHCR_IMAGE }}
@@ -179,6 +186,7 @@ jobs:
179186
180187 - name : Resolve shared release image cache
181188 id : cache
189+ if : ${{ steps.release_publish.outputs.verification_only != 'true' }}
182190 env :
183191 RELEASE_IMAGE_CACHE_ROOT : release-source
184192 RELEASE_SOURCE_COMMIT : ${{ steps.release_source.outputs.commit }}
@@ -188,16 +196,19 @@ jobs:
188196 run : node scripts/ci/resolve-release-image-cache.mjs
189197
190198 - name : Refuse replacement of published release images
199+ if : ${{ steps.release_publish.outputs.verification_only != 'true' }}
191200 env :
192201 RELEASE_TAG : ${{ steps.release_publish.outputs.tag }}
193202 run : sh scripts/ci/guard-unpublished-release-images.sh
194203
195204 - name : Start exact image build timer
196205 id : build_started
206+ if : ${{ steps.release_publish.outputs.verification_only != 'true' }}
197207 run : printf 'epoch_seconds=%s\n' "$(date +%s)" >> "$GITHUB_OUTPUT"
198208
199209 - name : Build and push exact image tags
200210 id : build
211+ if : ${{ steps.release_publish.outputs.verification_only != 'true' }}
201212 # Bound cold multi-architecture extension and application builds.
202213 timeout-minutes : 120
203214 continue-on-error : true
@@ -239,9 +250,11 @@ jobs:
239250 DOCKER_BUILD_OUTCOME : ${{ steps.build.outcome || 'skipped' }}
240251 BUILT_IMAGE_DIGEST : ${{ steps.build.outputs.digest }}
241252 BUILT_IMAGE_METADATA : ${{ steps.build.outputs.metadata }}
253+ RELEASE_IMAGE_VERIFICATION_ONLY : ${{ steps.release_publish.outputs.verification_only }}
254+ EXPECTED_IMAGE_DIGEST : ${{ steps.release_publish.outputs.existing_image_digest }}
242255 RELEASE_COMMIT : ${{ steps.release_source.outputs.commit }}
243- RELEASE_RUN_ID : ${{ github.run_id }}
244- RELEASE_RUN_ATTEMPT : ${{ github.run_attempt }}
256+ RELEASE_RUN_ID : ${{ steps.release_publish.outputs.verification_only != 'true' && github.run_id || '' }}
257+ RELEASE_RUN_ATTEMPT : ${{ steps.release_publish.outputs.verification_only != 'true' && github.run_attempt || '' }}
245258 WORKFLOW_PACKAGE_SOURCE : ${{ steps.workflow.outputs.source }}
246259 WORKFLOW_PACKAGE_REF : ${{ steps.workflow.outputs.ref }}
247260 WORKFLOW_PACKAGE_COMMIT : ${{ steps.workflow.outputs.commit }}
@@ -250,23 +263,27 @@ jobs:
250263 - name : Verify published image trust stores
251264 id : trust_stores
252265 if : ${{ always() && success() && steps.exact.outputs.exact_publish_outcome == 'success' }}
266+ timeout-minutes : 10
253267 env :
254- RELEASE_IMAGE : ${{ env.DOCKERHUB_IMAGE }}:${{ steps.release_publish.outputs.tag }}
268+ RELEASE_IMAGE : ${{ env.DOCKERHUB_IMAGE }}@${{ steps.exact.outputs.image_digest }}
269+ DW_PULL_ATTEMPTS : " 3"
270+ DW_PULL_BACKOFF_SECONDS : " 2"
255271 run : |
256272 set -euo pipefail
257273 # GHSA-6q48-6c7w-9gw2: every published architecture's system, PHP,
258274 # Composer, Node and venv stores, checked offline in the image.
259275 IFS=',' read -ra platforms <<<"$RELEASE_IMAGE_PLATFORMS"
260276 for platform in "${platforms[@]}"; do
261- docker run --rm --pull always --platform "$platform" --network none \
277+ DOCKER_DEFAULT_PLATFORM="$platform" bash scripts/ci/pull-images.sh "$RELEASE_IMAGE"
278+ docker run --rm --pull never --platform "$platform" --network none \
262279 --entrypoint server-verify-trust "$RELEASE_IMAGE"
263280 done
264281
265282 - name : Verify bare image first-run readiness
266283 id : first_run_readiness
267284 if : ${{ always() && success() && steps.exact.outputs.exact_publish_outcome == 'success' }}
268285 env :
269- DW_SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}: ${{ steps.release_publish .outputs.tag }}
286+ DW_SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}@ ${{ steps.exact .outputs.image_digest }}
270287 DW_FIRST_RUN_SCOPE : release-first-run-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
271288 working-directory : release-source
272289 run : scripts/smoke-published-first-run.sh
@@ -275,7 +292,7 @@ jobs:
275292 id : source_free_compose
276293 if : ${{ always() && success() && steps.first_run_readiness.outcome == 'success' }}
277294 env :
278- DW_SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}: ${{ steps.release_publish .outputs.tag }}
295+ DW_SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}@ ${{ steps.exact .outputs.image_digest }}
279296 DW_SERVER_PLATFORM : linux/amd64
280297 DW_PUBLISHED_COMPOSE_PROFILE : local
281298 DW_PUBLISHED_COMPOSE_PROJECT : release-compose-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
@@ -337,7 +354,7 @@ jobs:
337354 if : ${{ always() && success() && steps.exact.outputs.exact_publish_outcome == 'success' }}
338355 env :
339356 RELEASE_TAG : ${{ steps.release_publish.outputs.tag }}
340- SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}: ${{ steps.release_publish .outputs.tag }}
357+ SERVER_IMAGE : ${{ env.DOCKERHUB_IMAGE }}@ ${{ steps.exact .outputs.image_digest }}
341358 WORKFLOW_PACKAGE_SOURCE : ${{ steps.workflow.outputs.source }}
342359 WORKFLOW_PACKAGE_REF : ${{ steps.workflow.outputs.ref }}
343360 WORKFLOW_PACKAGE_COMMIT : ${{ steps.workflow.outputs.commit }}
@@ -357,6 +374,25 @@ jobs:
357374 docker volume rm -f "${RELEASE_PROTOCOL_CATALOG_SCOPE}-database" \
358375 >/dev/null 2>&1 || true
359376
377+ - name : Recheck immutable release identities before promotion
378+ env :
379+ GH_TOKEN : ${{ github.token }}
380+ RELEASE_TAG : ${{ steps.release_publish.outputs.tag }}
381+ RELEASE_COMMIT : ${{ steps.release_source.outputs.commit }}
382+ RELEASE_IMAGE_VERIFICATION_ONLY : " true"
383+ EXPECTED_IMAGE_DIGEST : ${{ steps.exact.outputs.image_digest }}
384+ DOCKER_BUILD_OUTCOME : skipped
385+ DOCKERHUB_IMAGE : ${{ env.DOCKERHUB_IMAGE }}
386+ GHCR_IMAGE : ${{ env.GHCR_IMAGE }}
387+ RELEASE_RUN_ID : ${{ steps.release_publish.outputs.verification_only != 'true' && github.run_id || '' }}
388+ RELEASE_RUN_ATTEMPT : ${{ steps.release_publish.outputs.verification_only != 'true' && github.run_attempt || '' }}
389+ WORKFLOW_PACKAGE_SOURCE : ${{ steps.workflow.outputs.source }}
390+ WORKFLOW_PACKAGE_REF : ${{ steps.workflow.outputs.ref }}
391+ WORKFLOW_PACKAGE_COMMIT : ${{ steps.workflow.outputs.commit }}
392+ run : |
393+ scripts/ci/verify-release-tag-source.sh
394+ scripts/ci/verify-release-exact-images.sh
395+
360396 - name : Resolve rolling image aliases
361397 id : rolling
362398 if : >-
@@ -380,6 +416,7 @@ jobs:
380416 DOCKERHUB_IMAGE : ${{ env.DOCKERHUB_IMAGE }}
381417 GHCR_IMAGE : ${{ env.GHCR_IMAGE }}
382418 ROLLING_SHOULD_PROMOTE : ${{ steps.rolling.outputs.rolling_should_promote }}
419+ VERIFIED_IMAGE_DIGEST : ${{ steps.exact.outputs.image_digest }}
383420 RELEASE_IMAGE_TAG_SOURCE_URL : ${{ github.server_url }}/${{ github.repository }}.git
384421 run : scripts/ci/promote-release-image-rolling-tags.sh
385422
@@ -445,6 +482,7 @@ jobs:
445482 needs : publish
446483 if : >-
447484 always() &&
485+ needs.publish.result == 'success' &&
448486 needs.publish.outputs.exact_publish_outcome == 'success' &&
449487 needs.publish.outputs.published_database_tls_outcome == 'success' &&
450488 github.repository == 'durable-workflow/server' &&
0 commit comments