Skip to content

Commit 52d2b92

Browse files
authored
Add a job summary listing promoted files to the release workflows (#8479)
The three release workflows -- `release-download-pytorch-org.yml`, `release-stage-pypi.yml` and `release-pypi.yml` -- print their S3 transfers into a step log and nothing else. Finding out what a promotion actually moved means expanding the step and scrolling past `--debug` output and xtrace noise, and after a partial failure it means diffing the bucket by hand. This adds a job summary to each of them, listing every file the run transferred, grouped into collapsed sections by destination directory. Each promotion step now tees its transfer output to a log under `${RUNNER_TEMP}`, and a new `release/summarize_promoted_files.sh` parses the `copy:` / `upload:` / `download:` lines out of it and writes markdown to `${GITHUB_STEP_SUMMARY}`. Parsing the transfer log rather than listing the destination afterwards means the summary reports what this run moved, not whatever already happened to be there. Details worth noting: - The summary steps are `if: always()`, because a promotion that dies partway through is exactly when you want to know which files already made it. - Dry runs are labelled with a banner and still list the files, since `--dryrun` emits `(dryrun) copy:` lines carrying the real destinations. - Zero transferred files gets an explicit callout instead of an empty list -- that state means the source pattern matched nothing, which currently passes silently. - `set -ex` became `set -exo pipefail` in the teed steps so a failing `aws` command is not masked by `tee` succeeding. - Progress output is carriage-return delimited, so the script converts CRs to newlines rather than stripping them; otherwise `Completed 3 file(s)...\rcopy: ...` would hide the transfer line. Sample output: > ## torchvision 0.27.1 - promoted to download.pytorch.org > > **Dry run** -- these transfers were simulated, nothing was published. > > **6 file(s) transferred.** > > <details><summary><code>s3://pytorch/whl/cu128</code> -- 2 file(s)</summary> > > - torchvision-0.27.1%2Bcu128-cp311-cp311-linux_x86_64.whl > - torchvision-0.27.1%2Bcu128-cp312-cp312-linux_x86_64.whl > > </details> ### Test plan Ran the script against a synthetic log covering CR-interleaved progress lines, `(dryrun)` prefixes, a duplicate line from a retried transfer, and two sibling destination directories sharing a prefix (`whl/cpu` and `whl/cpu2`) -- output groups correctly, dedupes the retry, and keeps the siblings apart. Also verified the empty-log and no-transfers paths. `actionlint` (with `shellcheck` enabled) passes on all three workflows and `shellcheck` passes on the new script. Still needs a real `workflow_dispatch` dry run of each workflow to confirm the AWS CLI output in these containers matches what the parser expects.
1 parent 1251e80 commit 52d2b92

4 files changed

Lines changed: 149 additions & 6 deletions

File tree

.github/workflows/release-download-pytorch-org.yml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
5959
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
6060
run: |
61-
set -ex
61+
set -exo pipefail
6262
cd release
6363
# Install requirements
6464
pip install awscli==1.32.18
@@ -97,8 +97,26 @@ jobs:
9797
export PACKAGE_INCLUDE_SUFFIX="-*"
9898
fi
9999
100+
# Tee the transfer output so the next step can list what moved.
100101
# shellcheck disable=SC2086
101-
promote_s3 ${PACKAGE} whl "${!version}"
102+
promote_s3 ${PACKAGE} whl "${!version}" 2>&1 | tee -a "${RUNNER_TEMP}/promote.log"
103+
104+
# always(): a promotion that dies partway through is exactly when you want to
105+
# know which files already made it to prod.
106+
- name: Summarize promoted files
107+
if: always()
108+
shell: bash
109+
env:
110+
PACKAGE: ${{ inputs.package || 'torchvision' }}
111+
DRY_RUN: ${{ inputs.dryrun || 'enabled' }}
112+
run: |
113+
set -euo pipefail
114+
source ./release/release_versions.sh
115+
version="${PACKAGE^^}_VERSION"
116+
./release/summarize_promoted_files.sh \
117+
"${RUNNER_TEMP}/promote.log" \
118+
"${PACKAGE} ${!version} - promoted to download.pytorch.org" \
119+
"${DRY_RUN}"
102120
103121
- name: Recompute SHA256 on whl/ (prod) for promoted package
104122
if: ${{ inputs.dryrun == 'disabled' }}

.github/workflows/release-pypi.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,37 @@ jobs:
5353
PACKAGE: ${{ inputs.package || 'torchvision' }}
5454
DRY_RUN: ${{ inputs.dryrun || 'enabled' }}
5555
run: |
56-
set -ex
56+
set -exo pipefail
5757
5858
# Init release versions variables
5959
source ./release/release_versions.sh
6060
# shellcheck disable=SC2086
6161
version="${PACKAGE^^}_VERSION"
6262
mkdir dist/
63+
# Only stdout is teed; --debug goes to stderr and stays in the job log.
6364
# shellcheck disable=SC2086
64-
aws s3 sync "s3://pytorch-backup/${PACKAGE}-${!version}-pypi-staging/" dist/ --debug
65+
aws s3 sync "s3://pytorch-backup/${PACKAGE}-${!version}-pypi-staging/" dist/ --debug \
66+
| tee -a "${RUNNER_TEMP}/download.log"
6567
- name: Display structure of downloaded files
6668
run: ls -R dist/
69+
70+
# Runs before the publish step, so the title says what will be published
71+
# rather than asserting that it was.
72+
- name: Summarize files to publish
73+
if: always()
74+
shell: bash
75+
env:
76+
PACKAGE: ${{ inputs.package || 'torchvision' }}
77+
DRY_RUN: ${{ inputs.dryrun || 'enabled' }}
78+
run: |
79+
set -euo pipefail
80+
source ./release/release_versions.sh
81+
version="${PACKAGE^^}_VERSION"
82+
./release/summarize_promoted_files.sh \
83+
"${RUNNER_TEMP}/download.log" \
84+
"${PACKAGE} ${!version} - files to publish to PyPI" \
85+
"${DRY_RUN}"
86+
6787
- name: Publish package to PyPI
6888
if: ${{ inputs.dryrun == 'disabled' }}
6989
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/release-stage-pypi.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
PACKAGE: ${{ inputs.package || 'torchvision' }}
4747
DRY_RUN: ${{ inputs.dryrun || 'enabled' }}
4848
run: |
49-
set -ex
49+
set -exo pipefail
5050
cd release/pypi
5151
5252
# Default Version to promote
@@ -66,10 +66,12 @@ jobs:
6666
local promote_version
6767
promote_version=$2
6868
echo "=-=-=-= Promoting ${package_name}'s v${promote_version} to pypi staging' =-=-=-="
69+
# Tee every platform's transfers into one log so the summary step can
70+
# list them all together.
6971
(
7072
set -x
7173
PACKAGE_VERSION="${promote_version}" PACKAGE_NAME="${package_name}" DRY_RUN="${DRY_RUN}" bash ./upload_pypi_to_staging.sh
72-
)
74+
) 2>&1 | tee -a "${RUNNER_TEMP}/stage.log"
7375
echo
7476
}
7577
@@ -118,3 +120,20 @@ jobs:
118120
# shellcheck disable=SC2086
119121
PLATFORM="win_amd64" VERSION_SUFFIX="${CPU_VERSION_SUFFIX}" upload_pypi_to_staging ${PACKAGE} "${!version}"
120122
fi
123+
124+
# always(): staging fans out over several platforms, so a mid-run failure
125+
# still leaves a partial set in the staging bucket worth listing.
126+
- name: Summarize staged files
127+
if: always()
128+
shell: bash
129+
env:
130+
PACKAGE: ${{ inputs.package || 'torchvision' }}
131+
DRY_RUN: ${{ inputs.dryrun || 'enabled' }}
132+
run: |
133+
set -euo pipefail
134+
source ./release/release_versions.sh
135+
version="${PACKAGE^^}_VERSION"
136+
./release/summarize_promoted_files.sh \
137+
"${RUNNER_TEMP}/stage.log" \
138+
"${PACKAGE} ${!version} - staged for PyPI" \
139+
"${DRY_RUN}"
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env bash
2+
# Write a GitHub Actions job summary listing the files a release step transferred.
3+
#
4+
# The release workflows all drive `aws s3 cp` / `aws s3 sync`, which print one line
5+
# per object:
6+
#
7+
# copy: s3://pytorch/whl/test/cu128/foo.whl to s3://pytorch/whl/cu128/foo.whl
8+
# (dryrun) copy: s3://... to s3://...
9+
# upload: ./foo.whl to s3://pytorch-backup/foo-1.0-pypi-staging/foo.whl
10+
# download: s3://pytorch-backup/... to dist/foo.whl
11+
#
12+
# Capture that output to a log and pass it here. Reading the transfer log rather
13+
# than listing the destination afterwards means the summary reports what this run
14+
# actually moved, not whatever already happened to be there.
15+
#
16+
# Usage: summarize_promoted_files.sh <logfile> <title> [dry_run]
17+
# dry_run: "enabled" (default) or "disabled" -- only controls the banner.
18+
19+
set -euo pipefail
20+
21+
LOG_FILE=${1:?usage: summarize_promoted_files.sh <logfile> <title> [dry_run]}
22+
TITLE=${2:?usage: summarize_promoted_files.sh <logfile> <title> [dry_run]}
23+
DRY_RUN=${3:-enabled}
24+
25+
# Fall back to stdout when run outside Actions, so the script stays testable.
26+
SUMMARY_FILE=${GITHUB_STEP_SUMMARY:-/dev/stdout}
27+
28+
if [[ ! -s "${LOG_FILE}" ]]; then
29+
{
30+
echo "## ${TITLE}"
31+
echo
32+
echo "No transfer log was produced, so there is nothing to report."
33+
} >>"${SUMMARY_FILE}"
34+
exit 0
35+
fi
36+
37+
# The destination of a transfer is whatever follows the last " to ". Progress
38+
# output is carriage-return delimited, so turn CRs into newlines (rather than
39+
# deleting them) to keep the "copy:" prefix at the start of its own line.
40+
# sort -u because a retried transfer logs the same object twice.
41+
DESTS=$(tr '\r' '\n' <"${LOG_FILE}" \
42+
| grep -E '^(\(dryrun\) )?(copy|upload|download|move): ' \
43+
| sed -E -e 's/.* to (.*)$/\1/' -e 's/[[:space:]]*$//' \
44+
| grep -v '^$' \
45+
| sort -u || true)
46+
47+
COUNT=$(printf '%s' "${DESTS}" | grep -c '^' || true)
48+
49+
{
50+
echo "## ${TITLE}"
51+
echo
52+
if [[ "${DRY_RUN}" != "disabled" ]]; then
53+
echo "> **Dry run** -- these transfers were simulated, nothing was published."
54+
echo
55+
fi
56+
57+
if [[ "${COUNT}" -eq 0 ]]; then
58+
echo "**No files were transferred.**"
59+
echo
60+
echo "The commands ran but moved nothing, which usually means the source"
61+
echo "pattern matched no objects. Check the package version and channel"
62+
echo "before treating this run as a success."
63+
else
64+
echo "**${COUNT} file(s) transferred.**"
65+
echo
66+
67+
# Group by destination directory: a multi-accelerator promotion then reads
68+
# as a handful of collapsed buckets rather than one flat list of ~200 wheels.
69+
printf '%s\n' "${DESTS}" | sed -E 's:/[^/]+$::' | sort -u | while read -r dir; do
70+
if [[ -z "${dir}" ]]; then
71+
continue
72+
fi
73+
files=$(printf '%s\n' "${DESTS}" | awk -v d="${dir}/" '
74+
index($0, d) == 1 {
75+
rest = substr($0, length(d) + 1)
76+
if (index(rest, "/") == 0) { print rest }
77+
}')
78+
n=$(printf '%s' "${files}" | grep -c '^' || true)
79+
echo "<details><summary><code>${dir}</code> -- ${n} file(s)</summary>"
80+
echo
81+
printf '%s\n' "${files}" | sed 's/^/- /'
82+
echo
83+
echo "</details>"
84+
done
85+
fi
86+
} >>"${SUMMARY_FILE}"

0 commit comments

Comments
 (0)