Release process v2: full-snapshot releases, staging freeze, PAT-free tag+dispatch chain #19
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |