Repository navigation
Conversation
| #!/bin/bash | ||
| set -euo pipefail | ||
|
|
||
| # Default to dry-run for safety unless DRY_RUN=false is explicitly passed. |
There was a problem hiding this comment.
Worth being explicit in the header that DRY_RUN=true is not side-effect free: it still deletes any existing google-ads package in the Exit Gate staging repo and re-uploads. Someone reading "dry run" will assume read-only.
Also consider normalizing case so a True from the Kokoro UI doesn't hard-fail:
| # Default to dry-run for safety unless DRY_RUN=false is explicitly passed. | |
| # Default to dry-run for safety unless DRY_RUN=false is explicitly passed. | |
| # NOTE: DRY_RUN=true still builds, wipes the staging package in Artifact | |
| # Registry, and re-uploads; it only skips the manifest upload that triggers | |
| # the external publish. | |
| DRY_RUN="${DRY_RUN:-true}" | |
| DRY_RUN="${DRY_RUN,,}" |
| echo "=== Installing pyenv Python version (if needed): ${PYENV_VER} ===" | ||
| pyenv install -s "${PYENV_VER}" | ||
|
|
||
| echo "=== Activating pyenv Python version: ${PYENV_VER} ===" |
There was a problem hiding this comment.
nit: pyenv global mutates container-wide state. export PYENV_VERSION="${PYENV_VER}" (or pyenv shell) scopes it to this process and is one less thing to reason about if the image is ever reused.
Also note pyenv install -s 3.12 only resolves the bare 3.12 prefix on pyenv ≥ 2.3.x; fine for ubuntu2204/full:current today, but if this ever fails with "definition not found" that's why.
| echo "=== Activating pyenv Python version: ${PYENV_VER} ===" | ||
| pyenv global "${PYENV_VER}" | ||
|
|
||
| python3 -m venv .venv |
There was a problem hiding this comment.
nit: creating the venv inside the repo root means it's sitting next to pyproject.toml when build runs. setuptools won't pick it up for the wheel, but keeping it out of the tree avoids any surprises with sdist/egg-info globbing:
| python3 -m venv .venv | |
| VENV_DIR="${KOKORO_ARTIFACTS_DIR:-/tmp}/release-venv" | |
| python3 -m venv "${VENV_DIR}" | |
| source "${VENV_DIR}/bin/activate" |
| rm -rf dist/ build/ *.egg-info | ||
|
|
||
| echo "=== Building sdist and wheel ===" | ||
| python3 -m build |
There was a problem hiding this comment.
This undercuts --require-hashes above. python -m build defaults to an isolated build env and installs whatever pyproject.toml lists in [build-system].requires — which is an unpinned setuptools — fresh from PyPI with no hash check. So the one dependency that actually produces the wheel is the one that isn't pinned.
Suggest building in the already-verified venv instead, and adding setuptools (+ wheel) to requirements.in so they're hash-locked too:
| python3 -m build | |
| # --no-isolation: build with the hash-verified setuptools from .kokoro/requirements.txt | |
| # rather than letting `build` fetch an unpinned copy from PyPI. | |
| python3 -m build --no-isolation |
While here, it'd be useful to capture the version once so the later steps can use it (see manifest comment below). Python 3.12 has tomllib in the stdlib:
VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
echo "=== Building google-ads ${VERSION} ==="| EXIT_GATE_REPOSITORY="google-ads-python--pypi" | ||
| PACKAGE_NAME="google-ads" | ||
|
|
||
| if gcloud artifacts packages describe "${PACKAGE_NAME}" \ |
There was a problem hiding this comment.
I follow why this is here (AR rejects re-uploading an existing version, and Exit Gate normally deletes the package after a successful publish, so anything present is a leftover from a failed/unpublished run). But a short comment saying exactly that would save the next person from thinking it's gratuitously destructive.
Two smaller points:
- If
twine uploadfails after this delete, the staging repo is now empty and a previously-staged-but-unpublished version is gone. Acceptable given it's staging, but worth noting. - Once the manifest pins
version(below), you could narrow this togcloud artifacts versions delete "${VERSION}" --package=...and leave other versions alone. Not required — whole-package delete is simpler and matches the Java script — just flagging the option.
| { | ||
| "packages": [ | ||
| { | ||
| "name": "google-ads" |
There was a problem hiding this comment.
Please pin the version here. Without "version", Exit Gate publishes the newest version of google-ads in the staging repo. Today that's correct only because step 4 wiped the package first — the publish is implicitly relying on a delete succeeding, and if someone later removes/relaxes that step (or two jobs overlap) you can push the wrong artifact to PyPI, which can't be un-published.
Pinning makes the manifest self-describing and the delete step a cleanliness measure rather than a correctness one (this is also what the gVisor Exit Gate release does):
| "name": "google-ads" | |
| "name": "google-ads", | |
| "version": "${VERSION}" |
| fi | ||
|
|
||
| echo "=== Creating targeted release manifest for google-ads ===" | ||
| cat <<EOF > manifest.json |
There was a problem hiding this comment.
nit: write this somewhere other than the repo root so it can't be left behind in the checkout (and so it's retained as a Kokoro artifact for audit):
MANIFEST_PATH="${KOKORO_ARTIFACTS_DIR:-/tmp}/manifest.json"
cat <<EOF > "${MANIFEST_PATH}"| EOF | ||
|
|
||
| EXIT_GATE_BUCKET="gs://oss-exit-gate-prod-projects-bucket/google-ads-python/pypi/manifests" | ||
| MANIFEST_NAME="manifest-$(date +%Y%m%d%H%M%S).json" |
There was a problem hiding this comment.
nit: include the version (and the Kokoro build ID when present) in the object name so manifests in the bucket are self-identifying and trivially correlated back to a job. Each upload must still be a unique name since every object triggers a new publish:
| MANIFEST_NAME="manifest-$(date +%Y%m%d%H%M%S).json" | |
| MANIFEST_NAME="manifest-${VERSION}-${KOKORO_BUILD_ID:-$(date --utc +%Y%m%d%H%M%S)}.json" |
|
|
||
| container_properties { | ||
| docker_image: "us-central1-docker.pkg.dev/kokoro-container-bakery/kokoro/ubuntu/ubuntu2204/full:current" | ||
| } |
There was a problem hiding this comment.
nit: consider retaining the built distributions (and the manifest, if moved to KOKORO_ARTIFACTS_DIR) as Kokoro artifacts so there's an auditable copy of exactly what was staged, plus an explicit timeout so a hung auth prompt can't run the default:
| } | |
| } | |
| timeout_mins: 30 | |
| action { | |
| define_artifacts { | |
| regex: "github/google-ads-python/dist/*" | |
| } | |
| } |
| build | ||
| twine | ||
| keyrings.google-artifactregistry-auth | ||
| requests >= 2.33.0 |
There was a problem hiding this comment.
nit: a one-line comment on why requests has a floor here (CVE? twine compat?) would help whoever next runs pip-compile. And per the --no-isolation suggestion in release.sh, add the build backend so it's hash-locked too:
| requests >= 2.33.0 | |
| # Floor for <reason>; everything else is unpinned and resolved by pip-compile. | |
| requests >= 2.33.0 | |
| # Build backend from pyproject.toml, pinned here so `build --no-isolation` | |
| # uses a hash-verified copy instead of fetching the latest from PyPI. | |
| setuptools | |
| wheel |
Summary
Adds
.kokoro/release.cfgand.kokoro/release.shto migrategoogle-ads-pythonreleases (PyPI (google-ads)) to OSS Exit Gate (google-ads-python/pypi) via the BAM DevRel Kokoro RBE release pool:ubuntu2204/full:current).oss-exit-gate-prod).DRY_RUN=true(default) to stage and validate artifacts without publishing, andDRY_RUN=falseto uploadmanifest.jsontogs://oss-exit-gate-prod-projects-bucket/google-ads-python/pypi/manifeststo trigger automated BCID verification and production release.Related Internal CLs
google-ads-python/pypi):http://cl/1009120863(submitted) &http://cl/1009709848ads-api-devrel/bam-devrel/aim/google-ads-python-release.cfg):http://cl/1009766585