Repository navigation
chore: add Kokoro release config and script for OSS Exit Gate #1102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Format: //devtools/kokoro/config/proto/build.proto | ||
|
|
||
| build_file: "github/google-ads-python/.kokoro/release.sh" | ||
|
|
||
| # Default to DRY_RUN=true for safety; can be overridden via API or UI | ||
| env_vars: { | ||
| key: "DRY_RUN" | ||
| value: "true" | ||
| } | ||
|
|
||
| container_properties { | ||
| docker_image: "us-central1-docker.pkg.dev/kokoro-container-bakery/kokoro/ubuntu/ubuntu2204/full:current" | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,108 @@ | ||||||||||||||||
| #!/bin/bash | ||||||||||||||||
| set -euo pipefail | ||||||||||||||||
|
|
||||||||||||||||
| # Default to dry-run for safety unless DRY_RUN=false is explicitly passed. | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth being explicit in the header that Also consider normalizing case so a
Suggested change
|
||||||||||||||||
| DRY_RUN="${DRY_RUN:-true}" | ||||||||||||||||
| if [[ "${DRY_RUN}" != "true" && "${DRY_RUN}" != "false" ]]; then | ||||||||||||||||
| echo "ERROR: DRY_RUN must be 'true' or 'false' (got '${DRY_RUN}')." >&2 | ||||||||||||||||
| exit 1 | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| # 1. Workspace & Directory Resolution | ||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||||||||||||||||
| cd "${REPO_DIR}" | ||||||||||||||||
|
|
||||||||||||||||
| echo "=== Building and Releasing from: ${REPO_DIR} (DRY_RUN=${DRY_RUN}) ===" | ||||||||||||||||
|
|
||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| # 2. Environment & Tooling Setup | ||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| PYENV_VER="3.12" | ||||||||||||||||
|
|
||||||||||||||||
| echo "=== Installing pyenv Python version (if needed): ${PYENV_VER} ===" | ||||||||||||||||
| pyenv install -s "${PYENV_VER}" | ||||||||||||||||
|
|
||||||||||||||||
| echo "=== Activating pyenv Python version: ${PYENV_VER} ===" | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Also note |
||||||||||||||||
| pyenv global "${PYENV_VER}" | ||||||||||||||||
|
|
||||||||||||||||
| python3 -m venv .venv | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: creating the venv inside the repo root means it's sitting next to
Suggested change
|
||||||||||||||||
| source ./.venv/bin/activate | ||||||||||||||||
|
|
||||||||||||||||
| python3 -m pip install --require-hashes -r .kokoro/requirements.txt | ||||||||||||||||
|
|
||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| # 3. Clean and Build Distribution Packages | ||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| echo "=== Cleaning previous build artifacts ===" | ||||||||||||||||
| rm -rf dist/ build/ *.egg-info | ||||||||||||||||
|
|
||||||||||||||||
| echo "=== Building sdist and wheel ===" | ||||||||||||||||
| python3 -m build | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This undercuts Suggest building in the already-verified venv instead, and adding
Suggested change
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 VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
echo "=== Building google-ads ${VERSION} ===" |
||||||||||||||||
|
|
||||||||||||||||
| echo "=== Validating built packages with twine ===" | ||||||||||||||||
| twine --version | ||||||||||||||||
| twine check dist/* | ||||||||||||||||
|
|
||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| # 4. Upload to Internal Exit Gate Artifact Registry | ||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| EXIT_GATE_PROJECT="oss-exit-gate-prod" | ||||||||||||||||
| EXIT_GATE_LOCATION="us" | ||||||||||||||||
| EXIT_GATE_REPOSITORY="google-ads-python--pypi" | ||||||||||||||||
| PACKAGE_NAME="google-ads" | ||||||||||||||||
|
|
||||||||||||||||
| if gcloud artifacts packages describe "${PACKAGE_NAME}" \ | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
|
||||||||||||||||
| --project="${EXIT_GATE_PROJECT}" \ | ||||||||||||||||
| --location="${EXIT_GATE_LOCATION}" \ | ||||||||||||||||
| --repository="${EXIT_GATE_REPOSITORY}" &>/dev/null; then | ||||||||||||||||
| echo "=== Deleting existing package '${PACKAGE_NAME}' from Exit Gate staging repository ===" | ||||||||||||||||
| gcloud artifacts packages delete "${PACKAGE_NAME}" \ | ||||||||||||||||
| --project="${EXIT_GATE_PROJECT}" \ | ||||||||||||||||
| --location="${EXIT_GATE_LOCATION}" \ | ||||||||||||||||
| --repository="${EXIT_GATE_REPOSITORY}" \ | ||||||||||||||||
| --quiet | ||||||||||||||||
| else | ||||||||||||||||
| echo "=== Skipping deletion. Package '${PACKAGE_NAME}' not found in staging repository. This is normal. ===" | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| EXIT_GATE_REPO="https://us-python.pkg.dev/oss-exit-gate-prod/google-ads-python--pypi" | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: derive this from the variables you already defined so project/location/repo can't drift from the
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| echo "=== Uploading packages to Exit Gate staging repository: ${EXIT_GATE_REPO} ===" | ||||||||||||||||
| twine upload --repository-url "${EXIT_GATE_REPO}" dist/* | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| # 5. Trigger Exit Gate Release via GCS Manifest | ||||||||||||||||
| # ----------------------------------------------------------------------------- | ||||||||||||||||
| if [[ "${DRY_RUN}" == "true" ]]; then | ||||||||||||||||
| echo "=== DRY_RUN is enabled. Skipping manifest upload to Exit Gate. ===" | ||||||||||||||||
| echo "Artifacts are staged in Artifact Registry." | ||||||||||||||||
| exit 0 | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| echo "=== Creating targeted release manifest for google-ads ===" | ||||||||||||||||
| cat <<EOF > manifest.json | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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}" |
||||||||||||||||
| { | ||||||||||||||||
| "publish_all": false, | ||||||||||||||||
| "publishing_groups": [ | ||||||||||||||||
| { | ||||||||||||||||
| "packages": [ | ||||||||||||||||
| { | ||||||||||||||||
| "name": "google-ads" | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please pin the version here. Without 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):
Suggested change
|
||||||||||||||||
| } | ||||||||||||||||
| ] | ||||||||||||||||
| } | ||||||||||||||||
| ] | ||||||||||||||||
| } | ||||||||||||||||
| 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" | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| echo "=== Uploading manifest to ${EXIT_GATE_BUCKET}/${MANIFEST_NAME} ===" | ||||||||||||||||
| gcloud storage cp manifest.json "${EXIT_GATE_BUCKET}/${MANIFEST_NAME}" | ||||||||||||||||
|
|
||||||||||||||||
| echo "=========================================================================" | ||||||||||||||||
| echo "Release successfully triggered! Exit Gate will now verify BCID and publish to PyPI." | ||||||||||||||||
| echo "=========================================================================" | ||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,4 @@ | ||||||||||||||||
| build | ||||||||||||||||
| twine | ||||||||||||||||
| keyrings.google-artifactregistry-auth | ||||||||||||||||
| requests >= 2.33.0 | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: a one-line comment on why
Suggested change
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: