Publish release metadata to R2 channels (#34508)

## What changed

- Publish the verified `release.json` metadata to `codex/channels/latest` for
  stable releases and `codex/channels/prerelease` for prereleases.
- Pass the release classification through the reusable R2 workflow so GitHub
  Releases and R2 channels use the same stable-versus-prerelease decision.
- Allow channel objects to be replaced while keeping versioned release objects
  immutable, and verify each channel upload before completing the publication.

GitOrigin-RevId: bd5c728c5565befe42b041ee1a032b111daca1c2
This commit is contained in:
zsol-openai
2026-07-21 09:49:58 +00:00
committed by copyberry
parent 94bb6a09a6
commit a148e0b50a
3 changed files with 71 additions and 8 deletions

View File

@@ -6,7 +6,9 @@ standard AWS credentials and the R2 endpoint from ``AWS_ENDPOINT_URL``.
Objects are created under ``codex/releases/<version>/`` with a validated upload
checksum and checked using object metadata before the run succeeds. The
versioned prefix includes every release asset plus installer-facing
``release.json`` metadata derived from the verified downloads.
``release.json`` metadata derived from the verified downloads. Once those
objects are verified, the same metadata advances ``codex/channels/latest`` when
the release is marked latest and ``codex/channels/prerelease`` for prereleases.
"""
import argparse
@@ -148,7 +150,14 @@ def raise_s3(
) from error
def put_immutable(endpoint: str, key: str, path: Path, sha256: str) -> None:
def put_object(
endpoint: str,
key: str,
path: Path,
sha256: str,
*,
extra_args: list[str],
) -> None:
try:
run_command(
[
@@ -157,7 +166,7 @@ def put_immutable(endpoint: str, key: str, path: Path, sha256: str) -> None:
"cp",
str(path),
f"s3://{BUCKET}/{key}",
"--no-overwrite",
*extra_args,
"--checksum-algorithm",
"CRC64NVME",
"--metadata",
@@ -223,6 +232,8 @@ def verify_remote(
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--tag", required=True)
parser.add_argument("--make-latest", choices=("true", "false"), required=True)
parser.add_argument("--prerelease", choices=("true", "false"), required=True)
return parser.parse_args()
@@ -258,7 +269,7 @@ def main() -> int:
size = asset.size
sha256 = asset.sha256
key = f"{PREFIX}/releases/{version}/{path.name}"
put_immutable(endpoint, key, path, sha256)
put_object(endpoint, key, path, sha256, extra_args=["--no-overwrite"])
verify_remote(endpoint, key, size, sha256)
print(
f"published and verified s3://{BUCKET}/{key} "
@@ -298,7 +309,13 @@ def main() -> int:
with metadata_path.open("rb") as source:
metadata_size, metadata_sha256 = stream_digest(source)
metadata_key = f"{PREFIX}/releases/{version}/{RELEASE_METADATA_NAME}"
put_immutable(endpoint, metadata_key, metadata_path, metadata_sha256)
put_object(
endpoint,
metadata_key,
metadata_path,
metadata_sha256,
extra_args=["--no-overwrite"],
)
verify_remote(
endpoint,
metadata_key,
@@ -310,6 +327,31 @@ def main() -> int:
f"size={metadata_size} sha256={metadata_sha256}",
file=sys.stderr,
)
channels = []
if args.make_latest == "true":
channels.append("latest")
if args.prerelease == "true":
channels.append("prerelease")
for channel in channels:
channel_key = f"{PREFIX}/channels/{channel}"
put_object(
endpoint,
channel_key,
metadata_path,
metadata_sha256,
extra_args=["--content-type", "application/json"],
)
verify_remote(
endpoint,
channel_key,
metadata_size,
metadata_sha256,
)
print(
f"published and verified s3://{BUCKET}/{channel_key} "
f"size={metadata_size} sha256={metadata_sha256}",
file=sys.stderr,
)
print(
json.dumps(

View File

@@ -6,6 +6,12 @@ on:
tag:
required: true
type: string
make_latest:
required: true
type: boolean
prerelease:
required: true
type: boolean
permissions: {}
@@ -32,7 +38,11 @@ jobs:
AWS_REGION: ${{ vars.CODEX_R2_REGION }}
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAKE_LATEST: ${{ inputs.make_latest }}
RELEASE_PRERELEASE: ${{ inputs.prerelease }}
run: |
set -euo pipefail
python3 .github/scripts/publish_r2_release.py \
--tag "${RELEASE_TAG}"
--tag "${RELEASE_TAG}" \
--make-latest "${RELEASE_MAKE_LATEST}" \
--prerelease "${RELEASE_PRERELEASE}"

View File

@@ -1130,6 +1130,8 @@ jobs:
outputs:
version: ${{ steps.release_name.outputs.name }}
tag: ${{ github.ref_name }}
make_latest: ${{ steps.release_name.outputs.make_latest }}
prerelease: ${{ steps.release_name.outputs.prerelease }}
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
@@ -1208,6 +1210,13 @@ jobs:
# "rust-v0.1.0".
version="${GITHUB_REF_NAME#rust-v}"
echo "name=${version}" >> $GITHUB_OUTPUT
if [[ "${version}" == *-* ]]; then
echo "make_latest=false" >> $GITHUB_OUTPUT
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "make_latest=true" >> $GITHUB_OUTPUT
echo "prerelease=false" >> $GITHUB_OUTPUT
fi
- name: Determine npm publish settings
id: npm_publish_settings
@@ -1268,10 +1277,10 @@ jobs:
body_path: ${{ steps.release_notes.outputs.path }}
files: dist/**
overwrite_files: true
make_latest: ${{ !contains(steps.release_name.outputs.name, '-') }}
make_latest: ${{ steps.release_name.outputs.make_latest }}
# Mark as prerelease only when the version has a suffix after x.y.z
# (e.g. -alpha, -beta). Otherwise publish a normal release.
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
prerelease: ${{ steps.release_name.outputs.prerelease }}
publish-r2:
name: publish-r2
@@ -1282,6 +1291,8 @@ jobs:
secrets: inherit
with:
tag: ${{ needs.release.outputs.tag }}
make_latest: ${{ fromJSON(needs.release.outputs.make_latest) }}
prerelease: ${{ fromJSON(needs.release.outputs.prerelease) }}
publish-dotslash:
name: publish-dotslash