build-cran-binaries/cdn.tf
pat-s 771d3a7768
feat(edge): send unsupported R minors to CRAN instead of serving them
Falling back to the flat index for an excluded minor is the silent case:
risky packages there are built under another minor and fail at load time,
far from the cause. Send those clients to CRAN for sources instead, which
is what the router already does for an unidentifiable distro.

The whole interaction has to move, not just the index. R resolves tarball
URLs against the repo it was configured with, so serving the index from
CRAN and tarballs from here would hand R a binary where it expects a
source tarball.

A client reporting no R minor at all is not excluded: mirror scripts and
image builds keep getting the flat slot.

- Declare the supported window once in cdn.tf as local.rpkgs_supported_minors
  and set KNOWN_MINORS from it on both zones, so the router cannot drift
  from build-env-images' LATEST/PREV1/PREV2 unnoticed.
- Split the verification script into supported and excluded minors: the
  former must have published indexes, the latter must have none and must
  redirect to CRAN under --live.
2026-08-30 15:44:14 +00:00

328 lines
9.9 KiB
HCL

# https://registry.terraform.io/providers/BunnyWay/bunnynet/latest/docs/resources/pullzone
# terraform import bunnynet_pullzone.devxy-r-binaries cran
# resource "bunnynet_pullzone" "devxy-r-binaries" {
# name = "cran"
# origin {
# type = "OriginUrl"
# url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
# }
# routing {
# tier = "Standard"
# }
# s3_auth_enabled = true
# s3_auth_key = var.B2_S3_ACCESS_KEY
# s3_auth_secret = var.B2_S3_SECRET_KEY
# s3_auth_region = "eu-central-003"
# cache_enabled = true
# cache_errors = true
# request_coalescing_enabled = true
# block_post_requests = true
# limit_requests = 500
# limit_connections = 50
# safehop_enabled = true
# add_canonical_header = true
# cache_stale = ["offline", "updating"]
# use_background_update = true
# block_ips = var.cdn_block_ips
# # 50 TB
# limit_bandwidth = 50000000000000
# permacache_storagezone = bunnynet_storage_zone.devxy-r-binaries.id
# # rec from docs: https://www.backblaze.com/docs/cloud-storage-integrate-bunnynet-with-backblaze-b2
# block_root_path = true
# }
# resource "bunnynet_pullzone_hostname" "devxy-r-binaries" {
# pullzone = bunnynet_pullzone.devxy-r-binaries.id
# name = "cran.devxy.io"
# force_ssl = true
# tls_enabled = true
# }
### cran.rpkgs.com
locals {
rpkgs_slots = [
for pair in setproduct(
["amd64", "arm64"],
["resolute", "noble", "jammy", "rhel8", "rhel9", "rhel10", "alpine323", "alpine324"]
) : "${pair[0]}/${pair[1]}"
]
# The supported R minors: the current one plus the two previous, which is
# exactly what build-env-images installs as R_VERSION_LATEST / PREV1 / PREV2.
# These must stay in step. A minor listed here without a published index
# sends those clients to a 404; a published minor missing from this list
# sends them to CRAN for sources instead of serving the binaries we built.
rpkgs_supported_minors = ["4.4", "4.5", "4.6"]
# bunny.net serves every pull zone on <name>.b-cdn.net, so staging needs no
# DNS record and is never advertised.
rpkgs_test_hostname = "cran-rpkgs-test.b-cdn.net"
}
# The edge middleware that resolves the bare cran.rpkgs.com form to an
# <arch>/<os> slot and routes PACKAGES* to the per-R-minor slot. The source of
# truth is edge/rpkgs-router.ts; `tofu apply` publishes a new release.
#
# The script pre-dates this configuration, so it is adopted rather than created:
# tofu import bunnynet_compute_script.rpkgs_router 29277
resource "bunnynet_compute_script" "rpkgs_router" {
type = "middleware"
name = "rpkgs-router"
content = file("${path.module}/edge/rpkgs-router.ts")
}
# Slots ("<arch>/<os>", comma separated) whose per-minor index bincraft has
# already republished as a union of the per-minor and flat slots. Routing to a
# slot that is not listed here would hide every package the per-minor index does
# not carry, so this stays empty until a slot has been backfilled.
resource "bunnynet_compute_script_variable" "rpkgs_router_union_slots" {
script = bunnynet_compute_script.rpkgs_router.id
name = "UNION_SLOTS"
default_value = ""
required = false
}
resource "bunnynet_compute_script_variable" "rpkgs_router_known_minors" {
script = bunnynet_compute_script.rpkgs_router.id
name = "KNOWN_MINORS"
default_value = join(",", local.rpkgs_supported_minors)
required = false
}
resource "bunnynet_pullzone" "cran_rpkgs_com" {
name = "cran-rpkgs"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
# Set on the zone since before this configuration existed; declared here so
# `tofu apply` stops silently removing it.
#
# The router makes it redundant on paper: the only UA-dependent responses it
# produces are redirects, and those carry `Cache-Control: no-store`, while
# their targets are concrete per-slot, per-minor URLs whose content depends
# only on the path. Dropping it would also be a real win, because otherwise
# every distinct R version string keys its own copy of every tarball.
#
# It stays for now anyway: it is the second line of defence against the one
# failure that would be quiet and confusing (an R 4.6 client served the 4.5
# index), and removing it is worth doing on its own once per-minor routing is
# confirmed live, not as a side effect of enabling that routing.
cache_vary_headers = ["User-Agent"]
limit_requests = 5000
limit_connections = 1000
safehop_enabled = true
add_canonical_header = true
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 50 TB
limit_bandwidth = 50000000000000
# permacache_storagezone = bunnynet_storage_zone.devxy-r-binaries.id
# rec from docs: https://www.backblaze.com/docs/cloud-storage-integrate-bunnynet-with-backblaze-b2
block_root_path = true
}
resource "bunnynet_pullzone_hostname" "cran_rpkgs_com" {
pullzone = bunnynet_pullzone.cran_rpkgs_com.id
name = "cran.rpkgs.com"
force_ssl = true
tls_enabled = true
}
### Staging zone for edge-router changes
# Every published <arch>/<os> slot. The staging zone enables per-minor routing
# for all of them at once; production adopts the same list only after
# `scripts/verify-r-minor-routing.sh --live` passes against staging.
# A second copy of the same router, bound to the same B2 origin, so UNION_SLOTS
# can be exercised end to end before production is touched.
resource "bunnynet_compute_script" "rpkgs_router_test" {
type = "middleware"
name = "rpkgs-router-test"
content = file("${path.module}/edge/rpkgs-router.ts")
}
resource "bunnynet_compute_script_variable" "rpkgs_router_test_union_slots" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "UNION_SLOTS"
default_value = join(",", local.rpkgs_slots)
required = false
}
# Without this the staging zone rewrites to PUBLIC_CDN_ORIGIN, so its redirects
# land on production and the test silently measures the wrong system.
resource "bunnynet_compute_script_variable" "rpkgs_router_test_extra_hosts" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "EXTRA_PUBLIC_HOSTS"
default_value = local.rpkgs_test_hostname
required = false
}
resource "bunnynet_compute_script_variable" "rpkgs_router_test_known_minors" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "KNOWN_MINORS"
default_value = join(",", local.rpkgs_supported_minors)
required = false
}
resource "bunnynet_pullzone" "cran_rpkgs_test" {
name = "cran-rpkgs-test"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router_test.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
cache_vary_headers = ["User-Agent"]
# Staging carries only synthetic verification traffic, so the production
# ceilings would be pure headroom.
limit_requests = 500
limit_connections = 100
safehop_enabled = true
add_canonical_header = true
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 1 TB
limit_bandwidth = 1000000000000
block_root_path = true
}
# Alliance SwissPass historically used a separate, manually configured pull
# zone. Adopt it so both public repositories use the same B2 origin, middleware
# release and cache behavior.
import {
to = bunnynet_pullzone.cran_allianceswisspass
id = "3265648"
}
resource "bunnynet_pullzone" "cran_allianceswisspass" {
name = "cran-allianceswisspass"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
cache_vary_headers = ["User-Agent"]
limit_requests = 5000
limit_connections = 1000
safehop_enabled = true
add_canonical_header = true
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 50 TB
limit_bandwidth = 50000000000000
block_root_path = true
}
resource "bunnynet_pullzone_hostname" "cran_allianceswisspass" {
pullzone = bunnynet_pullzone.cran_allianceswisspass.id
name = "cran.allianceswisspass.devxy.io"
force_ssl = true
tls_enabled = true
}
# resource "bunnynet_storage_zone" "devxy-r-binaries" {
# name = "devxy-r-binaries-storage"
# region = "DE"
# zone_tier = "Standard"
# # Los Angeles and Singapore
# replication_regions = ["LA", "SG"]
# }