feat(cdn): derive Ubuntu routes from codenames (#174)
All checks were successful
ci/crow/cron/process-updates/7 Pipeline was successful
ci/crow/cron/process-updates/1 Pipeline was successful
ci/crow/cron/process-updates/2 Pipeline was successful

## Motivation

Ubuntu release codenames cannot be derived from version numbers, so the edge router currently needs a code change for every Ubuntu release.

The generic repository setup can already read `VERSION_CODENAME` from `/etc/os-release` and send it in the user agent.

## Changes

- Prefer a strictly validated `codename=<name>` token for Ubuntu slot routing.
- Retain the existing version-to-codename table for clients using the previous user-agent format.
- Add a future Ubuntu 28.04 regression case whose codename does not exist in the middleware table.

## User-agent format

`R/4.6.1 (Ubuntu 26.04; codename=resolute) (aarch64-unknown-linux-gnu)`

## Verification

- `just edge-test`, 1 test with 16 steps passed.
- `git diff --check`

Reviewed-on: #174
This commit is contained in:
Patrick Schratz 2026-08-30 12:25:22 +00:00 committed by Patrick Schratz
commit b0d58f3f7c
2 changed files with 16 additions and 0 deletions

View file

@ -19,6 +19,8 @@ const UA_R45_MUSL = 'R (4.5.3 x86_64-pc-linux-musl x86_64 linux-musl)';
const UA_R46_MUSL = 'R (4.6.0 x86_64-pc-linux-musl x86_64 linux-musl)';
const UA_R45_ALPINE = 'R/4.5.3 R (4.5.3 x86_64-pc-linux-musl x86_64 linux-musl) Alpine Linux 3.24';
const UA_R45_RESOLUTE = 'R/4.5.3 (Ubuntu 26.04) (aarch64-unknown-linux-gnu aarch64 linux-gnu)';
const UA_R45_FUTURE_UBUNTU =
'R/4.5.3 (Ubuntu 28.04; codename=dynamic-dugong) (aarch64-unknown-linux-gnu aarch64 linux-gnu)';
const UA_R45_DARWIN = 'R (4.5.1 aarch64-apple-darwin20 aarch64 darwin20)';
const UA_CURL = 'curl/8.0.1';
@ -150,6 +152,14 @@ Deno.test('rpkgs-router', async (t) => {
);
});
await t.step('resolves a future Ubuntu release from its codename', async () => {
const res = await probe('/src/contrib/PACKAGES.gz', UA_R45_FUTURE_UBUNTU);
assertEquals(
res.location,
'https://cran.rpkgs.com/arm64/dynamic-dugong/latest/src/contrib/PACKAGES.gz',
);
});
await t.step('sends an unidentifiable distro to CRAN', async () => {
const res = await probe('/src/contrib/PACKAGES.gz', UA_R45_MUSL);
assertEquals(res.location, 'https://cran.r-project.org/src/contrib/PACKAGES.gz');

View file

@ -58,6 +58,7 @@ const MACOS_BIN_REGEX =
const RHEL_REGEX = /(almalinux|rocky)[^\d]*(\d+)/i;
const UBUNTU_REGEX = /Ubuntu ([\d.]+)/i;
const UBUNTU_CODENAME_REGEX = /Ubuntu [\d.]+;\s*codename=([a-z][a-z0-9-]*)/i;
const UBUNTU_CODENAMES: Record<string, string> = {
'26.04': 'resolute',
'24.04': 'noble',
@ -137,6 +138,11 @@ function parseSlot(userAgent: string): string | null {
const ubuntu = userAgent.match(UBUNTU_REGEX);
if (ubuntu) {
const codenameMatch = userAgent.match(UBUNTU_CODENAME_REGEX);
if (codenameMatch) {
return `${arch}/${codenameMatch[1].toLowerCase()}`;
}
const codename = UBUNTU_CODENAMES[ubuntu[1]];
if (codename) {
return `${arch}/${codename}`;