Cleanup #803
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: Cleanup | |
| on: | |
| pull_request_target: | |
| types: [closed] | |
| schedule: | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| actions: write | |
| pull-requests: read | |
| jobs: | |
| # --- PR close: worker/container cleanup --- | |
| # Uses pull_request_target so it fires for fork PRs too (needs secrets). | |
| # Safe: only deletes resources keyed by PR number, never executes PR code. | |
| # | |
| # Image tags (pr-* and ci-*) are NOT deleted here. Deleting pr-* tags was | |
| # observed to break the ci-* content-addressed Docker cache, likely because | |
| # they share underlying manifests and deletion triggers GC. Image cleanup | |
| # happens in the daily sweep only for closed/stale PRs. | |
| cleanup-pr: | |
| if: github.event_name == 'pull_request_target' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Install wrangler | |
| run: npm install -g wrangler@latest | |
| - name: Delete GHA caches | |
| timeout-minutes: 1 | |
| continue-on-error: true | |
| run: gh cache delete --all --ref "refs/pull/${{ github.event.pull_request.number }}/merge" || true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Delete test workers and containers | |
| timeout-minutes: 3 | |
| continue-on-error: true | |
| run: | | |
| PR=${{ github.event.pull_request.number }} | |
| CONTAINERS=$(wrangler containers list --json 2>/dev/null || echo "[]") | |
| WORKER="sandbox-e2e-test-worker-pr-${PR}" | |
| IDS=$(echo "$CONTAINERS" | jq -r ".[] | select(.name == \"$WORKER\" or (.name | startswith(\"$WORKER-\"))) | .id" 2>/dev/null) | |
| wrangler delete --name "$WORKER" 2>/dev/null || true | |
| for id in $IDS; do | |
| wrangler containers delete "$id" 2>/dev/null || true | |
| done | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| # --- Daily sweep: backstop for stale resources + image cache eviction --- | |
| cleanup-stale: | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: | | |
| docker-images.txt | |
| .github/load-docker-images.sh | |
| sparse-checkout-cone-mode: false | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Install wrangler | |
| run: npm install -g wrangler@latest | |
| - name: Clean stale PR resources | |
| timeout-minutes: 10 | |
| run: | | |
| # Discover candidate PRs from BOTH containers and images so we | |
| # catch orphaned resources even when one source is already cleaned. | |
| CONTAINERS=$(wrangler containers list --json 2>/dev/null || echo "[]") | |
| CONTAINER_PRS=$(echo "$CONTAINERS" | jq -r '.[].name' 2>/dev/null \ | |
| | grep -oE 'pr-[0-9]+' | sed 's/pr-//' | sort -u || true) | |
| ALL_IMAGES=$(wrangler containers images list --json 2>/dev/null || echo "[]") | |
| IMAGE_PRS=$(echo "$ALL_IMAGES" | jq -r '.[].tags[]?' 2>/dev/null \ | |
| | grep -oE '^pr-[0-9]+$' | sed 's/pr-//' | sort -u || true) | |
| PR_NUMBERS=$(printf '%s\n' $CONTAINER_PRS $IMAGE_PRS | sort -un) | |
| if [ -z "$PR_NUMBERS" ]; then | |
| echo "No PR resources found" | |
| exit 0 | |
| fi | |
| STALE_THRESHOLD=$((7 * 24 * 60 * 60)) | |
| NOW=$(date +%s) | |
| declare -A SHOULD_CLEAN | |
| for PR in $PR_NUMBERS; do | |
| PR_INFO=$(gh pr view "$PR" --json state,updatedAt 2>/dev/null || echo "") | |
| if [ -z "$PR_INFO" ]; then | |
| SHOULD_CLEAN[$PR]=true | |
| continue | |
| fi | |
| STATE=$(echo "$PR_INFO" | jq -r '.state') | |
| if [ "$STATE" = "CLOSED" ] || [ "$STATE" = "MERGED" ]; then | |
| SHOULD_CLEAN[$PR]=true | |
| elif [ "$STATE" = "OPEN" ]; then | |
| UPDATED=$(echo "$PR_INFO" | jq -r '.updatedAt') | |
| UPDATED_EPOCH=$(date -d "$UPDATED" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$UPDATED" +%s 2>/dev/null) | |
| if [ $((NOW - UPDATED_EPOCH)) -gt $STALE_THRESHOLD ]; then | |
| SHOULD_CLEAN[$PR]=true | |
| else | |
| SHOULD_CLEAN[$PR]=false | |
| fi | |
| fi | |
| done | |
| source .github/load-docker-images.sh | |
| for PR in $PR_NUMBERS; do | |
| if [ "${SHOULD_CLEAN[$PR]}" = "true" ]; then | |
| echo "PR #$PR is stale — cleaning up" | |
| WORKER="sandbox-e2e-test-worker-pr-${PR}" | |
| IDS=$(echo "$CONTAINERS" | jq -r ".[] | select(.name == \"$WORKER\" or (.name | startswith(\"$WORKER-\"))) | .id" 2>/dev/null) | |
| wrangler delete --name "$WORKER" 2>/dev/null || true | |
| for id in $IDS; do | |
| wrangler containers delete "$id" 2>/dev/null || true | |
| done | |
| # Delete pr-* images for closed/stale PRs. These tags are no | |
| # longer referenced by any active CI run. Note: if the CF | |
| # registry GC's shared manifests on tag deletion, this could | |
| # invalidate ci-* tags sharing the same digest — the cache | |
| # rebuilds on next use (~6 min one-time cost). | |
| for image in "${DOCKER_IMAGES[@]}"; do | |
| wrangler containers images delete "$image:pr-$PR" 2>/dev/null || true | |
| done | |
| fi | |
| done | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Clean stale GHA caches | |
| timeout-minutes: 2 | |
| run: | | |
| CACHES=$(gh cache list --json ref -q '.[].ref' 2>/dev/null || echo "") | |
| echo "$CACHES" | grep -E 'refs/pull/[0-9]+/merge' | while read -r REF; do | |
| PR=$(echo "$REF" | grep -oE '[0-9]+') | |
| STATE=$(gh pr view "$PR" --json state -q '.state' 2>/dev/null || echo "NOT_FOUND") | |
| if [ "$STATE" = "CLOSED" ] || [ "$STATE" = "MERGED" ] || [ "$STATE" = "NOT_FOUND" ]; then | |
| gh cache delete --all --ref "$REF" || true | |
| fi | |
| done || true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |