From 85295a949536c863ddaf0107b15dacf1401dcc07 Mon Sep 17 00:00:00 2001 From: pat-s Date: Mon, 31 Aug 2026 09:55:37 +0000 Subject: [PATCH] fix(cdn): resolve a pull zone when the API answers with a bare array (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Motivation Every reindex reports `failure` at the purge step: ``` Purging BunnyCDN pull zone 3857050 Purged pull zone 3857050 (HTTP 204) jq: error (at /tmp/tmp.eFPFmO:0): Cannot index array with string "Items" Could not find BunnyCDN pull zone for hostname cran.allianceswisspass.devxy.io ``` `cran.rpkgs.com` purges fine. The Alliance zone never has, so it is still serving objects that rebuilds replaced, behind a ~370-day `cache_expiration_time`. ## The defect ```sh jq -r '(.Items // .)[] | ...' ``` This was meant to accept both response shapes. It accepts neither: indexing an array with a string is an **error** in jq, not a null, so `//` never gets the chance to substitute and the whole expression aborts. The listing endpoint answers with a bare array for this account, so the lookup has always failed. ## Change - Select the array explicitly by type instead of relying on `//` to absorb an error. - Check the HTTP status of the listing call. It was previously used unconditionally, so an auth or rate-limit failure surfaced as "could not find hostname" — pointing at the wrong thing entirely. - Fail when a hostname matches multiple zones rather than silently purging whichever jq emitted first. - Request `perPage=1000`, so a paginated response cannot silently truncate the zone list. ## Verification Ran the current `main` script and the fixed one against a stubbed `curl` returning an array-shaped listing: ``` === BEFORE (main) === Purged pull zone 3857050 (HTTP 204) jq: error (at ...): Cannot index array with string ("Items") Could not find BunnyCDN pull zone for hostname cran.allianceswisspass.devxy.io === AFTER === Purged pull zone 3857050 (HTTP 204) Purging BunnyCDN pull zone 222 Purged pull zone 222 (HTTP 204) ``` The jq expression was also checked against both an array-shaped and an object-shaped (`.Items`) response; the old one fails the array case, the new one handles both. `shellcheck` clean. Reviewed-on: https://git.devxy.io/devxy/build-cran-binaries/pulls/178 --- scripts/purge_cdn_zone.sh | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/scripts/purge_cdn_zone.sh b/scripts/purge_cdn_zone.sh index 648dfc3..6c07eef 100755 --- a/scripts/purge_cdn_zone.sh +++ b/scripts/purge_cdn_zone.sh @@ -47,12 +47,31 @@ resolve_zone_id() { fi response_file=$(mktemp) - curl -sS -o "${response_file}" \ - -H "AccessKey: ${api_key}" \ - "https://api.bunny.net/pullzone" + local status + status=$( + curl -sS -o "${response_file}" -w '%{http_code}' \ + -H "AccessKey: ${api_key}" \ + "https://api.bunny.net/pullzone?perPage=1000" + ) + + if [[ "${status}" != "200" ]]; then + echo "Listing BunnyCDN pull zones failed with HTTP ${status}:" >&2 + head -c 500 "${response_file}" >&2 + echo >&2 + rm -f "${response_file}" + exit 1 + fi + + # The endpoint answers with a bare array on some accounts and a paginated + # object on others. `.Items // .` looks like it covers both but does not: + # indexing an array with a string is an *error*, and `//` only substitutes + # for null, so the array case aborted with + # "Cannot index array with string" and the zone was never purged. zone_id=$( jq -r --arg hostname "${zone}" \ - '(.Items // .)[] | select(any(.Hostnames[]?; .Value == $hostname)) | .Id' \ + '(if type == "object" then (.Items // []) else . end)[] + | select(any(.Hostnames[]?; .Value == $hostname)) + | .Id' \ "${response_file}" ) rm -f "${response_file}" @@ -62,6 +81,12 @@ resolve_zone_id() { exit 1 fi + # Two zones sharing a hostname would purge only whichever jq emitted first. + if [[ $(wc -l <<<"${zone_id}") -gt 1 ]]; then + echo "Hostname ${zone} matched multiple pull zones: ${zone_id//$'\n'/ }" >&2 + exit 1 + fi + echo "${zone_id}" }