Skip to content

Release process v2: full-snapshot releases, staging freeze, PAT-free tag+dispatch chain #19

Release process v2: full-snapshot releases, staging freeze, PAT-free tag+dispatch chain

Release process v2: full-snapshot releases, staging freeze, PAT-free tag+dispatch chain #19

Workflow file for this run

name: OBS PR Cleanup
# When a PR is closed:
# - merged release PR (all changed root/** files under root/*/releases/):
# create the release tag on the merge commit and dispatch obs-release.yml
# with it (workflow_dispatch works with GITHUB_TOKEN; a tag push would
# not trigger anything — GitHub suppresses workflow-to-workflow triggers
# for the default token, and this repo has no PAT).
# - every PR: delete the PR-specific OBS project created by
# obs-pr-check.yml (no-op if it never existed) and the PR's Actions caches.
on:
pull_request:
branches: [main]
paths: ['root/**']
types: [closed]
jobs:
cleanup:
name: percona-obs sync delete pr-${{ github.event.number }}
runs-on: ubuntu-latest
container: ghcr.io/${{ github.repository_owner }}/obs-tools:latest
permissions:
# Required to create release tags on the merge commit.
contents: write
# Required to dispatch obs-release.yml and delete the PR's Actions caches.
actions: write
packages: read
env:
PR_NUMBER: ${{ github.event.number }}
OBS_APIURL: ${{ vars.OBS_APIURL }}
OBS_PR_ROOTPRJ: ${{ vars.OBS_PR_ROOTPRJ }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/obs-setup
with:
obs-apiurl: ${{ vars.OBS_APIURL }}
obs-user: ${{ vars.OBS_USER }}
obs-password: ${{ secrets.OBS_PASSWORD }}
# Create the pr-N profile so subsequent steps can use -P "pr-N" instead
# of repeating -A and -R.
- name: Create percona-obs profiles
run: |
PR_ROOTPRJ="${OBS_PR_ROOTPRJ}:pr-${PR_NUMBER}"
venv/bin/python -m percona_obs \
-A "$OBS_APIURL" \
-R "$PR_ROOTPRJ" \
-e "REMOTE_OBS_ORG_INTERCONNECT:" \
-e "PERCONA_OBS_PACKAGING_BRANCH:refs/pull/${PR_NUMBER}/head" \
-e "PERCONA_OBS_PACKAGING_REPO:${{ github.event.pull_request.head.repo.clone_url }}" \
profile create "pr-${PR_NUMBER}"
# Detect whether this is a release-only PR (all changed root/** files are
# under root/*/releases/). This output gates tag creation below; it also
# still tells us that a release-only PR never created a PR OBS project,
# so the delete step below runs as a no-op for it.
- name: Detect release-only PR
id: detect
env:
GH_TOKEN: ${{ github.token }}
run: |
ALL_ROOT=$(gh pr view "$PR_NUMBER" \
--json files \
--jq '[.files[] | select(.path | startswith("root/")) | .path] | length')
REL_ROOT=$(gh pr view "$PR_NUMBER" \
--json files \
--jq '[.files[] | select(.path | test("^root/[^/]+/releases/")) | .path] | length')
if [ "$ALL_ROOT" -gt 0 ] && [ "$ALL_ROOT" -eq "$REL_ROOT" ]; then
echo "is_release_pr=true" >> "$GITHUB_OUTPUT"
else
echo "is_release_pr=false" >> "$GITHUB_OUTPUT"
fi
# Merged release PR: tag the merge commit (the exact reviewed snapshot)
# and dispatch the release workflow with it. Tag-creation errors other
# than "already exists" fail the job loudly — a silently missing tag is
# how releases used to vanish.
- name: Tag release and dispatch obs-release
if: github.event.pull_request.merged == true && steps.detect.outputs.is_release_pr == 'true'
env:
GH_TOKEN: ${{ github.token }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
set -euo pipefail
FILES=$(gh pr view "$PR_NUMBER" --json files \
--jq '.files[].path | select(test("^root/[^/]+/releases/[^/]+/release\\.yaml$"))' | sort -u)
[ -z "$FILES" ] && { echo "No release.yaml changed; nothing to tag."; exit 0; }
while IFS= read -r release_file; do
TAG=$(git show "${MERGE_SHA}:${release_file}" | venv/bin/python3 -c \
"import sys,yaml; d=yaml.safe_load(sys.stdin); r=d.get('releases') or []; print(r[-1] if r else d.get('revision') or '')")
[ -z "$TAG" ] && continue
echo "Creating tag ${TAG} at ${MERGE_SHA}"
if ! OUT=$(gh api "repos/${GITHUB_REPOSITORY}/git/refs" -X POST \
-f "ref=refs/tags/${TAG}" -f "sha=${MERGE_SHA}" 2>&1); then
if echo "$OUT" | grep -q "Reference already exists"; then
echo "Tag ${TAG} already exists — dispatching anyway"
else
echo "::error::Failed to create tag ${TAG}: ${OUT}"
exit 1
fi
fi
echo "Dispatching obs-release.yml with tag=${TAG}"
gh workflow run obs-release.yml --ref main -f "tag=${TAG}"
done <<< "$FILES"
# Delete the PR's root project and all its sub-projects.
# --yes skips the confirmation prompt.
# --recursive ensures OBS deletes projects that still contain packages
# (e.g. if a build was in progress when the PR was closed).
# 404 responses are handled gracefully by percona-obs (no error).
- name: Delete PR OBS project
run: |
venv/bin/python -m percona_obs --verbose \
-P "pr-${PR_NUMBER}" \
sync delete \
--yes \
--recursive \
--from-obs
# Delete this PR's Actions caches (created by obs-pr-check.yml on the
# refs/pull/N/merge ref). Caches on PR merge refs can never be
# restored by main-branch runs, so leaving them around only churns the
# repo's 10 GB cache quota and evicts the main branch's caches.
- name: Delete PR Actions caches
if: always()
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api --paginate \
"repos/${GITHUB_REPOSITORY}/actions/caches?ref=refs/pull/${PR_NUMBER}/merge" \
--jq '.actions_caches[].id' |
while read -r cache_id; do
echo "Deleting Actions cache id ${cache_id}"
gh api -X DELETE \
"repos/${GITHUB_REPOSITORY}/actions/caches/${cache_id}" || true
done