build-cran-binaries/scripts/purge_cdn_zone.sh
pat-s a1c1f5e78f fix(cdn): align repository routing across pull zones (#165)
## Motivation

`cran.rpkgs.com` and `cran.allianceswisspass.devxy.io` serve the same B2 repository through separate Bunny pull zones, but only the first zone was managed and purged after weekly reindexing.
This allowed the Alliance endpoint to retain stale repository metadata and left locked `renv` restores unable to retrieve versions whose binary archive object was absent.

## Changes

- Adopt the Alliance SwissPass pull zone `3265648` into OpenTofu and configure it with the shared B2 origin and middleware script.
- Purge both Bunny pull zones after the weekly rebuild reindex.
- Preserve the requested public hostname in middleware redirects.
- Redirect missing archived binaries to the corresponding CRAN source package, checking whether the version is archived or still current.
- Cover the existing archived-binary passthrough behavior in the edge routing matrix.

## Verification

- `prek run -a`
- `just edge-test`
- `crow lint .crow/`
- `tofu validate`
- `bash -n scripts/purge_cdn_zone.sh`

## Deployment

Run `tofu apply` to adopt pull zone `3265648`, publish the middleware release, and align both pull zones.
After the apply, rerun the Alliance SwissPass CI restore that requested `cli 3.6.5` and `AzureStor 3.7.1`.

Reviewed-on: #165
2026-08-13 14:08:10 +00:00

55 lines
1.9 KiB
Shell
Executable file

#!/usr/bin/env bash
#
# Purge the entire BunnyCDN pull zone.
#
# `purge_cdn_cache.sh` purges the five index files by URL, which is right after
# a normal update: new packages arrive at new URLs, so only the index is stale.
#
# A rebuild is different. It replaces an object *in place*: a package whose
# build failed was published as its CRAN source, and the rebuilt binary takes
# exactly the same URL. The zone caches tarballs for ~370 days
# (`cache_expiration_time` in cdn.tf), so without a purge every client keeps
# receiving the source tarball for up to a year, and nothing about it looks
# wrong from the outside.
#
# Purging per URL would mean one API call per replaced package -- ~13.5k per
# arch against a rate-limited endpoint, where a single missed call leaves a
# silently stale package. One zone purge is a single call regardless of how many
# objects were replaced. The cost is a cold cache for everything else, which is
# why this is not used by the daily update path.
#
# The public hostnames currently use separate pull zones, so callers must pass
# every zone that serves the repository.
#
# Usage:
# purge_cdn_zone.sh <BUNNYNET_API_KEY> <pull_zone_id> [<pull_zone_id>...]
#
set -euo pipefail
if (($# < 2)); then
echo "usage: $0 <api_key> <pull_zone_id> [<pull_zone_id>...]" >&2
exit 2
fi
api_key="$1"
shift
for zone_id in "$@"; do
echo "Purging BunnyCDN pull zone ${zone_id}"
response_file="/tmp/purge_zone_response_${zone_id}.txt"
status=$(
curl -sS -o "${response_file}" -w '%{http_code}' -X POST \
-H "AccessKey: ${api_key}" \
-H "Content-Length: 0" \
"https://api.bunny.net/pullzone/${zone_id}/purgeCache"
)
if [[ "${status}" != "200" && "${status}" != "204" ]]; then
echo "Purge of pull zone ${zone_id} failed with HTTP ${status}:" >&2
cat "${response_file}" >&2
exit 1
fi
echo "Purged pull zone ${zone_id} (HTTP ${status})"
done