Skip to content

Commit fe2dbb0

Browse files
committed
feat(ci): support configurable and RC Docker images in E2E and promotion workflows
Signed-off-by: EmmaLien <emmalien@google.com>
1 parent a1e68a2 commit fe2dbb0

3 files changed

Lines changed: 86 additions & 137 deletions

File tree

‎.github/workflows/promote_docker_image.yml‎

Lines changed: 68 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,43 @@
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

2425
name: Promote MaxText Docker Image
2526
run-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
3031
on:
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

4941
permissions:
5042
contents: read
51-
actions: read
5243

5344
jobs:
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-n2-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

‎.github/workflows/rc_release.yml‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,13 @@ jobs:
139139
device: ${{ matrix.device }}
140140
workflow: ${{ matrix.workflow }}
141141

142-
# TODO(b/573710066): Pass custom Docker image names (tpu_pre_training and
143-
# tpu_post_training) once run_e2e_tests.yml, promote_docker_image.yml, and
144-
# the maxtext_e2e_tests Airflow DAG support configurable Docker images.
145142
run_e2e_tests:
146143
name: Run E2E tests
147144
needs: [get_maxtext_sha, build_release_candidate_images]
148145
if: github.event_name == 'schedule' || inputs.repository_name == 'maxtext-images'
149146
uses: ./.github/workflows/run_e2e_tests.yml
150147
with:
151-
mode: stable
148+
mode: release_candidate
152149
run_id: ${{ github.run_id }}
153150
maxtext_sha: ${{ needs.get_maxtext_sha.outputs.short_sha }}
154151
secrets:

‎.github/workflows/run_e2e_tests.yml‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ on:
2020
workflow_call:
2121
inputs:
2222
mode:
23-
description: 'Build mode: stable or nightly'
23+
description: 'Build mode: stable, nightly, or release_candidate'
2424
required: true
2525
type: string
2626
run_id:
@@ -67,25 +67,39 @@ jobs:
6767
GITHUB_REPO: ${{ github.repository }}
6868
GITHUB_TOKEN: ${{ secrets.AIRFLOW_CALLBACK_TOKEN }}
6969
TEST_SCOPE: ${{ inputs.test_scope }}
70+
PROJECT_NAME: ${{ vars.PROJECT_NAME }}
7071
run: |
7172
IAP_TOKEN=$(gcloud auth print-access-token)
7273
PAYLOAD=$(python3 -c 'import json, os
7374
from datetime import datetime, timezone
75+
mode = os.environ["BUILD_MODE"]
7476
run_id = os.environ["RUN_ID"]
77+
sha = os.environ["COMMIT_SHA"]
7578
repo = os.environ["GITHUB_REPO"]
7679
test_scope = os.environ["TEST_SCOPE"]
80+
project = os.environ.get("PROJECT_NAME") or "tpu-prod-env-multipod"
81+
if mode == "release_candidate":
82+
pre_img = f"us-docker.pkg.dev/{project}/maxtext-images/tpu_pre_training:{sha}"
83+
post_img = f"us-docker.pkg.dev/{project}/maxtext-images/tpu_post_training:{sha}"
84+
else:
85+
pre_img = f"us-docker.pkg.dev/{project}/maxtext-images/maxtext_jax_{mode}:{run_id}"
86+
post_img = f"us-docker.pkg.dev/{project}/maxtext-images/maxtext_post_training_{mode}:{run_id}"
7787
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
7888
print(json.dumps({
7989
"dag_run_id": f"auto_gha__{run_id}__{now}",
8090
"note": f"Automated E2E TPU test suite ({test_scope}) triggered by GitHub Actions.\n\nWorkflow Run: https://github.com/{repo}/actions/runs/{run_id}",
8191
"conf": {
82-
"build_mode": os.environ["BUILD_MODE"],
83-
"commit_sha": os.environ["COMMIT_SHA"],
92+
"build_mode": mode,
93+
"commit_sha": sha,
8494
"github_run_id": run_id,
8595
"github_repo": repo,
8696
"github_token": os.environ["GITHUB_TOKEN"],
8797
"is_automated": True,
8898
"test_scope": test_scope,
99+
"docker_images": {
100+
"tpu_pre_training": pre_img,
101+
"tpu_post_training": post_img,
102+
},
89103
}
90104
}))')
91105
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \

0 commit comments

Comments
 (0)