1515# This workflow acts as the final gate in the CI/CD pipeline for MaxText Docker images.
1616# Triggered by:
1717# 1. Airflow DAG completion (repository_dispatch):
18- # - Validates the result of the Airflow DAG (E2E tests).
19- # - Validates the result of the nightly build pipeline that produced the Docker images.
20- # - If both succeed, it promotes the Docker images by tagging the artifact with the latest tag.
18+ # - Tags the tested Docker image with the result of the Airflow DAG (E2E tests):
19+ # verified-e2e-<tag>, failed-e2e-<tag>, or unknown-e2e-<tag>.
20+ # - If E2E passed on an image from the daily build that also passed CI (verified-ci-<tag>),
21+ # it promotes the image (and its nightly counterpart) by tagging it with the latest tag.
2122# 2. Manual workflow dispatch (workflow_dispatch):
22- # - Forcefully promotes the Docker images when Airflow E2E tests could not run (e.g. due to capacity).
23+ # - Forcefully promotes a Docker image when Airflow E2E tests could not run (e.g. due to capacity).
2324
2425name : Promote MaxText Docker Image
2526run-name : >-
2627 ${{ github.event_name == 'workflow_dispatch' &&
27- format('Forceful promote: {0} ({1}) ', inputs.caller_run_id, inputs.test_type ) ||
28+ format('Forceful promote: {0}', inputs.docker_image ) ||
2829 format('Triggered by Airflow {0} Tests DAG', github.event.client_payload.test_type) }}
2930
3031on :
3132 repository_dispatch :
3233 types : [airflow-dag-complete]
3334 workflow_dispatch :
3435 inputs :
35- caller_run_id :
36- description : ' GitHub workflow run ID of the build pipeline that produced the Docker images'
36+ docker_image :
37+ description : ' Full Docker image URI to promote, e.g. us-docker.pkg.dev/<project>/maxtext-images/tpu_pre_training:<tag> (7-char SHA for RC images, or GitHub run ID for daily images) '
3738 required : true
3839 type : string
39- test_type :
40- description : ' Workflow to promote (pre_training, post_training, or all)'
41- required : false
42- default : ' all'
43- type : choice
44- options :
45- - all
46- - pre_training
47- - post_training
4840
4941permissions :
5042 contents : read
51- actions : read
5243
5344jobs :
5445 handle_result :
5546 name : Handle Airflow DAG Result
5647 runs-on : ubuntu-latest
48+ outputs :
49+ e2e_state : ${{ steps.report.outputs.e2e_state }}
5750 steps :
5851 - name : Report DAG result
52+ id : report
5953 if : github.event_name == 'repository_dispatch'
6054 env :
61- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
6255 STATE : ${{ github.event.client_payload.state }}
6356 DAG_ID : ${{ github.event.client_payload.dag_id }}
6457 DAG_RUN_ID : ${{ github.event.client_payload.dag_run_id }}
@@ -76,148 +69,93 @@ jobs:
7669 echo "================================"
7770
7871 case "$STATE" in
79- success) echo "E2E tests PASSED." ;;
80- failed|upstream_failed) echo "E2E tests FAILED."; exit 1 ;;
81- *) echo "DAG ended with unexpected state: ${STATE}"; exit 1 ;;
72+ success)
73+ echo "E2E tests PASSED."
74+ echo "e2e_state=success" >> "$GITHUB_OUTPUT"
75+ ;;
76+ failed|upstream_failed)
77+ echo "E2E tests FAILED."
78+ echo "e2e_state=failed" >> "$GITHUB_OUTPUT"
79+ ;;
80+ *)
81+ echo "DAG ended with unexpected state: ${STATE}"
82+ echo "e2e_state=unknown" >> "$GITHUB_OUTPUT"
83+ ;;
8284 esac
8385
84- if [ -n "$CALLER_RUN_ID" ]; then
85- echo "Checking nightly build status for run: $CALLER_RUN_ID"
86- gh run watch "$CALLER_RUN_ID" --exit-status --interval 60 --repo "$GITHUB_REPOSITORY"
87- else
88- echo "Error: No Github Run ID provided. Cannot verify nightly build status."
89- exit 1
90- fi
91-
9286 - name : Report manual trigger
9387 if : github.event_name == 'workflow_dispatch'
9488 env :
95- GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
96- CALLER_RUN_ID : ${{ inputs.caller_run_id }}
97- TEST_TYPE : ${{ inputs.test_type }}
89+ DOCKER_IMAGE : ${{ inputs.docker_image }}
9890 run : |
9991 echo "================================"
10092 echo "Manual Promotion"
101- echo "Github Run ID: ${CALLER_RUN_ID}"
102- echo "Test Type: ${TEST_TYPE}"
93+ echo "Docker Image: ${DOCKER_IMAGE}"
10394 echo "================================"
10495
105- if [ -z "$CALLER_RUN_ID" ]; then
106- echo "Error: No Github Run ID provided. Cannot promote Docker images without run ID."
107- exit 1
108- fi
109-
11096 tag_docker_image :
111- name : Promote ${{ matrix.test_type }} Docker Image
97+ name : Promote Docker Image
11298 needs : handle_result
11399 if : needs.handle_result.result == 'success'
114100 runs-on : linux-x86-n4-16-buildkit
115101 container : google/cloud-sdk:524.0.0
116- strategy :
117- fail-fast : false
118- matrix :
119- include :
120- - test_type : pre_training
121- stable_image_name : maxtext_jax_stable
122- nightly_image_name : maxtext_jax_nightly
123- - test_type : post_training
124- stable_image_name : maxtext_post_training_stable
125- nightly_image_name : maxtext_post_training_nightly
126102 steps :
127- - name : Checkout MaxText
128- if : >-
129- ${{
130- (github.event_name == 'workflow_dispatch' &&
131- (inputs.test_type == 'all' || inputs.test_type == '' || inputs.test_type == matrix.test_type)) ||
132- (github.event_name == 'repository_dispatch' &&
133- (github.event.client_payload.test_type == '' || github.event.client_payload.test_type == matrix.test_type))
134- }}
135- uses : actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
136- with :
137- persist-credentials : false
138103 - name : Configure Docker
139- if : >-
140- ${{
141- (github.event_name == 'workflow_dispatch' &&
142- (inputs.test_type == 'all' || inputs.test_type == '' || inputs.test_type == matrix.test_type)) ||
143- (github.event_name == 'repository_dispatch' &&
144- (github.event.client_payload.test_type == '' || github.event.client_payload.test_type == matrix.test_type))
145- }}
146104 run : gcloud auth configure-docker us-docker.pkg.dev -q
147105 - name : Add tags to Docker image
148- if : >-
149- ${{
150- (github.event_name == 'workflow_dispatch' &&
151- (inputs.test_type == 'all' || inputs.test_type == '' || inputs.test_type == matrix.test_type)) ||
152- (github.event_name == 'repository_dispatch' &&
153- (github.event.client_payload.test_type == '' || github.event.client_payload.test_type == matrix.test_type))
154- }}
155106 shell : bash
156107 env :
157- CALLER_RUN_ID : ${{ github.event.client_payload.github_run_id || inputs.caller_run_id }}
158- PROJECT_NAME : ${{ vars.PROJECT_NAME }}
159- SBOM_BUCKET : ${{ vars.SBOM_BUCKET }}
160- STABLE_IMAGE_NAME : ${{ matrix.stable_image_name }}
161- NIGHTLY_IMAGE_NAME : ${{ matrix.nightly_image_name }}
108+ E2E_STATE : ${{ needs.handle_result.outputs.e2e_state || 'success' }}
109+ PAYLOAD_IMAGE : ${{ github.event.client_payload.docker_image || inputs.docker_image }}
162110 run : |
163111 tag_image() {
164112 local img="$1"
165113 local tag="$2"
166- echo "Tagging ${img}:${CALLER_RUN_ID } as ${tag}..."
167- gcloud container images add-tag "${img}:${CALLER_RUN_ID }" "${img}:${tag}" --quiet
114+ echo "Tagging ${img}:${IMAGE_TAG } as ${tag}..."
115+ gcloud container images add-tag "${img}:${IMAGE_TAG }" "${img}:${tag}" --quiet
168116 }
169117
170- promote_from_staging() {
171- local img_name="$1"
172- local staging_img="us-docker.pkg.dev/${PROJECT_NAME}/maxtext-images-staging/${img_name}"
173- local prod_img="us-docker.pkg.dev/${PROJECT_NAME}/maxtext-images/${img_name}"
174-
175- local digest
176- digest=$(gcloud container images describe "${staging_img}:${CALLER_RUN_ID}" \
177- --format="value(image_summary.digest)" 2>/dev/null || true)
178-
179- if [ -n "$digest" ]; then
180- bash .github/scripts/promote_artifacts.sh \
181- "${PROJECT_NAME}" "maxtext-images-staging" "maxtext-images" "${img_name}" "${digest}" "${SBOM_BUCKET}"
182- gcloud container images add-tag "${prod_img}@${digest}" "${prod_img}:${CALLER_RUN_ID}" --quiet
118+ # Promotes a daily stable image to latest once it has also passed CI. Nightly images are
119+ # built alongside stable ones but are not E2E tested, so they are promoted together with
120+ # their stable counterpart.
121+ promote_to_latest() {
122+ if gcloud container images describe "${IMAGE}:verified-ci-${IMAGE_TAG}" >/dev/null 2>&1; then
123+ tag_image "${IMAGE}" "latest"
124+ local nightly_image="${IMAGE%_stable}_nightly"
125+ if gcloud container images describe "${nightly_image}:${IMAGE_TAG}" >/dev/null 2>&1; then
126+ tag_image "${nightly_image}" "latest"
127+ else
128+ echo "Nightly image ${nightly_image}:${IMAGE_TAG} does not exist. Skipping nightly image tagging."
129+ fi
130+ else
131+ echo "${IMAGE}:verified-ci-${IMAGE_TAG} does not exist. Skipping latest tag."
183132 fi
184133 }
185134
186- if [ -z "$CALLER_RUN_ID" ]; then
187- echo "Error: CALLER_RUN_ID is empty. Cannot promote Docker image without image tag."
188- exit 1
189- fi
190-
191- tagged_any=false
192-
193- # 1. Check if stable image exists (or can be promoted from staging) and tag it
194- STABLE_IMAGE="us-docker.pkg.dev/${PROJECT_NAME}/maxtext-images/${STABLE_IMAGE_NAME}"
195- if ! gcloud container images describe "${STABLE_IMAGE}:${CALLER_RUN_ID}" >/dev/null 2>&1; then
196- promote_from_staging "${STABLE_IMAGE_NAME}"
197- fi
198- if gcloud container images describe "${STABLE_IMAGE}:${CALLER_RUN_ID}" >/dev/null 2>&1; then
199- echo "Stable image ${STABLE_IMAGE}:${CALLER_RUN_ID} exists. Tagging stable image..."
200- tag_image "${STABLE_IMAGE}" "verified-e2e-${CALLER_RUN_ID}"
201- tag_image "${STABLE_IMAGE}" "latest"
202- tagged_any=true
203- else
204- echo "Stable image ${STABLE_IMAGE}:${CALLER_RUN_ID} does not exist. Skipping stable image tagging."
205- fi
135+ # Both Airflow and manual runs pass the full image URI (<image>:<tag>).
136+ IMAGE="${PAYLOAD_IMAGE%:*}"
137+ IMAGE_TAG="${PAYLOAD_IMAGE##*:}"
206138
207- # 2. Check if nightly image exists (or can be promoted from staging) and tag it
208- NIGHTLY_IMAGE="us-docker.pkg.dev/${PROJECT_NAME}/maxtext-images/${NIGHTLY_IMAGE_NAME}"
209- if ! gcloud container images describe "${NIGHTLY_IMAGE}:${CALLER_RUN_ID}" >/dev/null 2>&1; then
210- promote_from_staging "${NIGHTLY_IMAGE_NAME}"
211- fi
212- if gcloud container images describe "${NIGHTLY_IMAGE}:${CALLER_RUN_ID}" >/dev/null 2>&1; then
213- echo "Nightly image ${NIGHTLY_IMAGE}:${CALLER_RUN_ID} exists. Tagging nightly image..."
214- tag_image "${NIGHTLY_IMAGE}" "latest"
215- tagged_any=true
216- else
217- echo "Nightly image ${NIGHTLY_IMAGE}:${CALLER_RUN_ID} does not exist. Skipping nightly image tagging."
218- fi
219-
220- if [ "$tagged_any" = false ]; then
221- echo "Error: Neither stable image (${STABLE_IMAGE}:${CALLER_RUN_ID}) nor nightly image (${NIGHTLY_IMAGE}:${CALLER_RUN_ID}) was found."
139+ if ! gcloud container images describe "${IMAGE}:${IMAGE_TAG}" >/dev/null 2>&1; then
140+ echo "Error: Image ${IMAGE}:${IMAGE_TAG} was not found."
222141 exit 1
223142 fi
143+
144+ echo "Image ${IMAGE}:${IMAGE_TAG} exists. E2E state: ${E2E_STATE}."
145+ case "$E2E_STATE" in
146+ success)
147+ tag_image "${IMAGE}" "verified-e2e-${IMAGE_TAG}"
148+ gcloud container images untag "${IMAGE}:failed-e2e-${IMAGE_TAG}" --quiet 2>/dev/null || true
149+ # Only daily-build images are promoted to latest. RC images already get latest-rc
150+ # when they are built.
151+ if [[ "$IMAGE" == *_stable ]]; then
152+ promote_to_latest
153+ fi
154+ ;;
155+ failed)
156+ tag_image "${IMAGE}" "failed-e2e-${IMAGE_TAG}"
157+ ;;
158+ *)
159+ tag_image "${IMAGE}" "unknown-e2e-${IMAGE_TAG}"
160+ ;;
161+ esac
0 commit comments