Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 23:59:16 -05:00
Commit graph pkg-proxy/internal
Author SHA1 Message Date
Giles Westwood
9d0175b1e2 Try configured generic_artifacts before built-in patterns
The first matching pattern names the package, so an upstream's own
patterns now take precedence over overlapping built-ins and keep the
package names, cache entries and rules already keyed on them.
2026-10-12 19:22:42 +01:00
Christian Heim
854182fa28 Concurrent clients could still end up with a database and a signature that don't belong together. Databases are now stored under their digest like signatures, so a cache row and its bytes always agree, also with several replicas on shared storage. Writes for one database are serialized, and the signature recovery shares the refresh with database requests, so a slow fetch can no longer store an older database over a newer one. When the cache write fails, the proxy serves the database the cache still describes. Unchanged databases are no longer rewritten, and a remembered missing signature is asked for again after the metadata TTL. The new tests force these interleavings with storage and upstream hooks. 2026-10-12 20:06:59 +02:00
Christian Heim
c58cdaece9 Refuse conda rules with more than a channel and a name
Downloads are routed as /{channel}/{arch}/{filename}, so the stored name is always {channel}/{name}. A key like pkg:conda/conda-forge/label/rc/numpy passed validation and matched nothing; it's now refused.

Refs #306
2026-10-12 19:59:35 +02:00
Christian Heim
c3c120401c Fix hex tarballs whose version contains a hyphen
parseTarballFilename split at the last hyphen before a digit, so amqp_client-4.1.5-1.tar was stored as package amqp_client-4.1.5. Hex names can't contain a hyphen, so it now splits at the first one. The docs caveat is gone.

Refs #306
2026-10-12 19:57:57 +02:00
Christian Heim
bffba28262 Enable storage retention for conda
The handler stores packages as {channel}/{name}, which purl writes as pkg:conda/conda-forge%2Fnumpy. Package rules name the channel as the namespace (pkg:conda/conda-forge/numpy) and get mapped to that form, with the package name lowercased the way conda treats it. A key without a channel or with an empty part could never match and is refused.

The mirror can't fetch conda packages today, so there's no mirror test.

Refs #306
2026-10-12 19:56:45 +02:00
Christian Heim
743fbe5026 Enable storage retention for hex
Tarballs are stored under the name from the tarball filename, and purl keeps hex names as they are, so package rules use the default mapping with the name as published.

The filename parser splits versions like 4.1.5-1 at the wrong hyphen and files them under a package like amqp_client-4.1.5, so package rules don't reach them; the docs row says so until the parser is fixed.

Refs #306
2026-10-12 19:56:45 +02:00
Christian Heim
5d7855ffc8 Store mirrored NuGet ids lowercased
The mirror passed ids through as written, so pkg:nuget/Newtonsoft.Json@13.0.3 was stored under a copy no client is served while the handler kept serving its lowercased one. GetOrFetchArtifact now lowercases nuget names, and the docs no longer ask for lowercase ids in mirror lists.

Refs #306
2026-10-12 19:56:38 +02:00
Christian Heim
680a2daba1 Fix gem filenames whose platform ends in a number
parseGemFilename split at the last hyphen before a digit, so sorbet-static-0.5.11002-universal-darwin-22.gem was stored as package sorbet-static-0.5.11002-universal-darwin. It now splits at the first one, which only misreads gem names with a -<digit> segment. The docs caveat is gone.

Refs #306
2026-10-12 19:55:10 +02:00
Andrew Nesbitt
093ae8295e
Merge pull request #445 from pinguinfuss/issue-306-retention-swift
Enable storage retention for swift
2026-10-12 17:44:37 +01:00
Simon Chrzanowski
b131db394d Isolate the recent failure history in tests
The history is process-wide, and tests cleared it only after running, and
not all of them. Records from earlier tests or an earlier -count repetition
reached later assertions: TestRecordUpstreamOperations left a record for
TestRecentFailuresNewestFirst, and the server tests never cleared it.

- Export ResetRecentFailures so tests outside the metrics package can use it
- Clear the history before and after every test that records or reads
  failures directly, in metrics, server and handler
- Assert exactly one failure in the handler tests instead of looking only
  at the newest
2026-10-12 18:44:12 +02:00
Andrew Nesbitt
7cb05b4cf6
Merge pull request #455 from pinguinfuss/issue-306-retention-conan
Enable storage retention for conan
2026-10-12 16:37:08 +01:00
Andrew Nesbitt
4273a308f7
Merge pull request #420 from simonchrz/metadata-validators
Send ETag and Last-Modified with rewritten npm and Composer metadata
2026-10-12 16:36:57 +01:00
Christian Heim
f8ca190274
Enable storage retention for swift
Source archives are stored under the registry identity, pkg:generic/swift-registry/{scope}.{name}, lowercased. Package rules write the scope as the namespace (pkg:swift/apple/swift-nio) or use the stored form, in any case. A source repository key like pkg:swift/github.com/apple/swift-nio doesn't name a registry identity and is refused.

The mirror can't fetch swift packages, so there's no mirror test.

Refs #306
2026-10-12 16:35:51 +01:00
Andrew Nesbitt
9f75e05e8c
Merge pull request #447 from pinguinfuss/issue-306-retention-julia
Enable storage retention for julia
2026-10-12 16:32:53 +01:00
Andrew Nesbitt
4958f22e46
Merge pull request #448 from pinguinfuss/issue-306-retention-rpm
Enable storage retention for rpm
2026-10-12 16:31:35 +01:00
Christian Heim
ff4d5ca10d
Enable storage retention for conan
Since #431 the handler stores Conan archives under pkg:conan/{name} with the version as the version and the revisions and any user/channel in the filename. purl keeps conan names as they are, so package rules use the default mapping, and pkg:conan/zlib covers every version, revision and user/channel of the recipe.

The mirror can't fetch conan packages, so there's no mirror test.

Refs #306
2026-10-12 16:29:59 +01:00
Andrew Nesbitt
4256aac92c
Merge pull request #449 from pinguinfuss/issue-306-retention-helm
Enable storage retention for helm
2026-10-12 16:29:24 +01:00
Andrew Nesbitt
f8c234fe1a
Merge pull request #451 from pinguinfuss/issue-306-retention-pub
Enable storage retention for pub
2026-10-12 16:29:18 +01:00
Andrew Nesbitt
3c1c118fd9
Merge pull request #453 from pinguinfuss/issue-306-retention-cran
Enable storage retention for cran
2026-10-12 16:29:11 +01:00
Christian Heim
9c75e59ef4
Enable storage retention for composer
Archives are stored under pkg:composer/{vendor}/{package}, which purl lowercases, so case in a package rule doesn't matter. A key without a vendor could never match and is refused. Packages that git-pkgs wrote as "packagist" fall under the composer rules too.

The mirror can't fetch composer packages today (its resolver has no composer support, and #415 would file them under packagist), so there's no mirror test.

Refs #306
2026-10-12 16:23:36 +01:00
Christian Heim
70ea9215c6
Enable storage retention for cargo
Crates are stored under the name they were published with, and purl keeps cargo names as they are. Package rules have to match that name exactly, so pkg:cargo/inflector doesn't cover Inflector.

Refs #306
2026-10-12 16:16:22 +01:00
Andrew Nesbitt
adf844cb26
Merge pull request #458 from pinguinfuss/issue-306-retention-alpine
Enable storage retention for alpine
2026-10-12 16:14:14 +01:00
Christian Heim
59f0203b00 When a signature has to be fetched on request because its fetch failed during the database refresh, it is now only served if it belongs to the database the proxy had cached at that point. If the upstream has published a new database in the meantime, the request fails with 502 instead of handing out a signature that cannot verify the database the client already has. The next sync gets the new pair. 2026-10-12 17:08:17 +02:00
Giles Westwood
fdd0c814eb Build in version-pinned paths for well-known download hosts
Generic upstreams pointed at archive.apache.org, nodejs.org or
dl.google.com/go now cache their versioned downloads as artifacts without
any generic_artifacts patterns. Configured patterns add to the built-in ones.
2026-10-12 15:39:23 +01:00
simon chrzanowski
9f42425646
Test upgrading cached metadata through the metadata_dates migration
- Start from the schema before migration 011 on SQLite and PostgreSQL, with
  npm and Composer metadata cached, and check the rows and stored bytes
  survive the migration unchanged
- After the upgrade, check two replicas serve the cached document with
  matching ETag and Last-Modified and answer conditional requests with 304
- Change the upstream document and check neither replica answers the old
  validators with 304, and both agree on the new ones
- Give the PostgreSQL handler test a database of its own, so it shares no
  tables with the database package tests on the same server
2026-10-12 16:11:49 +02:00
Christian Heim
dcfb9a820d Enable storage retention for nuget
The handler stores NuGet ids lowercased, but purl keeps nuget names as written, so package rules get lowercased before they're matched. Case in a rule doesn't matter.

The mirror stores ids as given, so a mixed-case PURL ends up as a copy clients never get. Until that's fixed, the docs ask for lowercase ids in mirror lists.

Refs #306
2026-10-12 15:24:21 +02:00
Christian Heim
42f459e961 Enable storage retention for rpm
Packages are stored as pkg:rpm/{name}, without a vendor and with the name's case kept. Package rules may name a vendor the way PURLs usually do (pkg:rpm/fedora/curl); it's dropped. The docs row warns that Fedora and EPEL update repositories only keep the latest build.

The mirror can't fetch rpm packages, so there's no mirror test.

Refs #306
2026-10-12 15:24:21 +02:00
Christian Heim
48edbb8ba0 Enable storage retention for pub
Packages are stored under the name from the download path, and purl keeps pub names as they are, so package rules use the default mapping with the name as published.

Refs #306
2026-10-12 15:24:21 +02:00
Christian Heim
20855cae21 Enable storage retention for gem
Gems are stored under the name from the gem filename, and purl keeps gem names as they are, so package rules use the default mapping and match the name as published, case included. Packages that git-pkgs wrote as "rubygems" fall under the gem rules too.

The filename parser splits platforms that end in a number (universal-darwin-22) at the wrong hyphen and files those gems under a package like sorbet-static-0.5.11002-universal-darwin, so package rules don't reach them; the docs row says so until the parser is fixed.

The mirror can't fetch gems today (it asks the resolver for "rubygems", see #415), so there's no mirror test.

Refs #306
2026-10-12 15:24:20 +02:00
Christian Heim
8f30c6d478 Enable storage retention for julia
Julia takes no package rules, since its PURLs need a uuid qualifier the cache doesn't store; configuring one already fails validation. The julia rule and the default cover package tarballs, artifacts and registry snapshots alike. Everything there is content-addressed, so an evicted tarball comes back unchanged.

Refs #306
2026-10-12 15:24:20 +02:00
Christian Heim
f3505ad557 Enable storage retention for helm
Helm takes no package rules: charts are cached under the configured repository's name and the chart digest, not under the chart name, so a key like pkg:helm/nginx would name a repository. The helm rule and the default cover charts from every HTTP repository; charts pulled over OCI go through the container handler and fall under oci.

Refs #306
2026-10-12 15:24:20 +02:00
Simon Chrzanowski
054c45c5a7
Share the dates of rewritten metadata across replicas
Dates were kept in each process's rewrite cache, so replicas sharing a
database and storage dated independently: one dated a document, upstream
changed in that second, and another gave the new document the same date,
so a client holding it got 304 and kept the old document.

- Date from metadata_dates instead of process memory: a package's date is
  the second it started serving its current representation, recorded once
  for every replica
- Switch a package to another representation only in a second later than
  its recorded date, and serve it undated until then
- Date nothing in the second the process started, so dates clients hold
  from before this change, or from the process it replaced, stay older
- Serve no date ahead of the local clock, recorded by a replica whose clock
  is ahead
- Date rewrites without the rewrite cache too; the cache keeps only ETags
- Add two-replica, pre-fix date and clock skew regression tests
2026-10-12 15:16:51 +02:00
Simon Chrzanowski
599b2e1473
Add a metadata_dates table for rewritten metadata dates
Replicas sharing a database need one place to agree on the Last-Modified
of rewritten metadata, which process memory cannot give them.

- Add metadata_dates, keyed by ecosystem, proxy URL and package, holding the
  ETag being dated and its date in Unix seconds, so no zone is involved
- Insert a package's first date only if no replica recorded one first
- Swap a date only if it is still the one read, so of two replicas
  switching a package at once only one succeeds
- Create it in both schemas and in migration 011
2026-10-12 15:16:51 +02:00
Simon Chrzanowski
cff4b5188d
Date rewritten metadata by when its package started serving it
Two false 304s remained. A rewrite kept the date it was made, so when
upstream went from A to B and back to A, A's cached rewrite came back with
a date earlier than B's and a client holding B got 304. And a second
rewrite in the same second was dated a second ahead of the clock, a date
only that process remembered: after a restart a changed document could be
dated earlier than one the client held.

- Record which rewrite each package is serving and since when, and date a
  rewrite by the second its package switched to it, including back to one
  still cached
- Never date ahead of the clock: a rewrite switched to in a second that
  already dated another for the package goes undated until the next second
- Date nothing in the second the cache starts, the last second the previous
  process may have dated a rewrite in
- Drop a package's record when its current rewrite is evicted, so records
  stay bounded by the cache; forgetting one dated in the current second
  holds back dates for new packages until the next second
2026-10-12 15:16:51 +02:00
Simon Chrzanowski
8903a8c9c9
Date rewritten metadata by when the rewrite was made
Last-Modified was upstream's date clamped to the process start, so
different documents could share a date: after a restart, a cached document
and the newer one a refresh fetched were both dated at startup, and a client
holding the first got 304 for the second.

- Date each rewrite by when the rewrite cache first made it, and keep that
  date while the entry stays cached
- Give every new rewrite of a package a date later than the last one it got,
  even within the same second or after its entry was evicted, so no two
  representations share a date; dates are remembered only until the clock
  passes them
- Send no Last-Modified when the rewrite cache is off, since nothing records
  when an uncached rewrite was made; cooldown still sends none
- Drop the upstream Last-Modified from metadata lookups, which nothing uses
  now
2026-10-12 15:16:51 +02:00
simon chrzanowski
bf7d17d39a
Derive cooldown metadata ETags from the rewrite's inputs
- With cooldown on every rewrite runs, and hashing each rewritten body for
  its ETag made every npm metadata request about 20% more expensive
- Hash what fixes the output instead: the upstream digest, proxy URL,
  package name, the versions cooldown and the denylist kept, and a
  per-process nonce standing for the startup configuration
- Carry the stored document's digest and upstream Last-Modified with the
  metadata lookup result, so neither is read from a second cache row lookup
  that a concurrent refresh could have changed
- Fall back to hashing the body when the digest is unknown, and keep the
  body hash with cooldown off, where it is computed once per cached rewrite
2026-10-12 15:16:15 +02:00
simon chrzanowski
4f680ddbd0
Send ETag and Last-Modified with rewritten npm and Composer metadata
- Rewritten metadata went out with only Content-Type, so pnpm, npm and
  Composer could never revalidate and downloaded every document in full
- ETag is the SHA-256 of the rewritten body, computed once per rewrite and
  kept with the rewrite cache entry; exact under cooldown as well
- Last-Modified is the upstream Last-Modified, never earlier than the process
  start, since a restart can change the rewrite; omitted with cooldown on
- Answer If-None-Match, and If-Modified-Since when there is no If-None-Match,
  with 304 on the rewrite fast path and on the fetch path
2026-10-12 15:15:28 +02:00
Ondrej Kokes
6e334b4c42
npm: version lookup returns 502 (#419)
* npm: version lookup returns 502

Looking up a specific version will trigger a 502 instead of return the version metadata.

* scope only not handled explicitly

* honor cooldown in version lookups

tested (set default cooldown to 7d and 8.0.7 is released four days ago or something):

```
$ curl -sS 'http://localhost:8080/npm/@babel%2Fcore/8.0.7' | jq
{
  "error": "package not found"
}
```

* package not found -> version not found

* gocognit
2026-10-12 13:45:34 +01:00
Christian Heim
4b8678c2c5
Enable storage retention for npm (#440)
* Enable storage retention for npm

npm stores packages under the PURL purl builds from the full name, so package rules use the default mapping. Both pkg:npm/@babel/core and pkg:npm/%40babel/core work.

Refs #306

* Tighten npm retention tests and docs

The docs now say npm package rules match the name exactly, so pkg:npm/jsonstream doesn't cover JSONStream. The refetch, package rule and mirror tests were weaker than their names and now check what they claim.

Refs #306

---------

Co-authored-by: Andrew Nesbitt <andrewnez@gmail.com>
2026-10-12 13:21:38 +01:00
Christian Heim
18f410d021
Enable storage retention for pypi (#441)
* Enable storage retention for pypi

PyPI names are stored the way purl normalizes them, lowercased and with _ turned into -, so Typing_Extensions and typing-extensions hit the same package. The mirror can't fetch PyPI files today, so there's no mirror test.

Refs #306

* Correct the pypi package rule note

Dots in PyPI names aren't normalized, so zope.interface and zope_interface files end up under two packages. The docs now say to add a rule for each.

Refs #306

* Mention repeated separators in the pypi rule note

Runs like "__" aren't collapsed either, so a__b is stored as a--b.

Refs #306
2026-10-12 13:06:37 +01:00
Christian Heim
c61cb51a0e
Enable storage retention for golang (#442)
Module paths are stored lowercased, so package rules match whatever case they're written in. A major version like /v2 is its own module and needs its own rule. Keys in the !-escaped form from proxy URLs and the module cache are refused, since they could never match.

Refs #306
2026-10-12 13:04:16 +01:00
Christian Heim
b40a3f067a
Enable storage retention for oci (#443)
Blobs are stored under the repository name of the pull, which purl writes as one name (pkg:oci/library%2Fnginx). Package rules write it as a path, pkg:oci/library/nginx, and get mapped to that form; keys with an empty segment, a tag or digest, or a bare upstream/{name} prefix are refused. Named upstreams keep their prefix, also when containerd reaches them through ns. Homebrew bottles and Helm charts pulled over OCI fall under oci too, bottles under Homebrew's image name (openssl@3 is homebrew/core/openssl/3). Manifests live in the metadata cache and aren't touched.

Refs #306
2026-10-12 13:00:57 +01:00
Christian Heim
90989eb030
Enable storage retention for maven (#444)
Package rules use pkg:maven/{group}/{artifact} with the case as published. Keys without a group or with the group written as a path can never match and are refused. Files from the Gradle Plugin Portal fallback fall under the maven rule; the Gradle build cache isn't affected.

The mirror can't fetch Maven packages today (it passes group/artifact where the resolver wants group:artifact), so there's no mirror test.

Refs #306
2026-10-12 12:58:09 +01:00
Simon Chrzanowski
496f272fed
List recent failures under each failure count on /ui/analytics
- Add ui_failure_details / PROXY_UI_FAILURE_DETAILS, off by default
- Each failure row expands to its latest failures, newest first
- Show a note naming the config key when failures exist and the flag is off
- Document the flag in README, configuration reference and example config
2026-10-12 11:57:15 +02:00
Simon Chrzanowski
3b262f101b
Record the detail behind each failure counter
- Failure recorders take a FailureDetail: PURL, filename, URL, storage path, error
- Keep the last 20 failures per counter series in memory, reset on restart
- Strip userinfo, query and fragment from URLs, including those inside error text
- Pass the detail from upstream, storage, integrity, health probe and scan call sites
- Package names and URLs stay out of Prometheus labels
2026-10-12 11:55:13 +02:00
Christian Heim
086abebe6e A database refresh now fetches the signature as well and stores it for that exact database content, keyed by its digest. A signature requested later, after the upstream has moved on, is still the one for the database the client got, and a refresh that returns the same bytes keeps the cached signature, so it is served offline too. The database is fetched once more after its signature to catch a mirror that swapped both in between. A missing signature is remembered per database, so official repos answer 404 without going upstream. 2026-10-12 10:59:22 +02:00
Christian Heim
67fd1ad6c1 The mirror now resolves a tag without caching it, mirrors the image it points to, and stores the tag only once that image is complete, so a failed or interrupted mirror keeps the previously cached tag instead of pointing it at an image with missing layers. 2026-10-12 10:36:51 +02:00
Christian Heim
5b9a5fca6b When the shared tag entry holds a representation the client does not accept, the lookup now goes on to the client's own per-Accept entry instead of giving up, so a Docker-only client still gets its cached manifest list after an OCI index was cached for the same tag. 2026-10-12 10:29:12 +02:00
Christian Heim
42b4303b71 Enable storage retention for cran
Source, archived and binary packages are stored under pkg:cran/{name}, the name from the filename with its case kept, and purl keeps cran names as they are. Package rules use the default mapping and match the name as published, so pkg:cran/rcpp doesn't cover Rcpp.

The mirror can't fetch cran packages, so there's no mirror test.

Refs #306
2026-10-12 10:23:27 +02:00
Christian Heim
1d88be53ab Enable storage retention for alpine
Packages are stored as pkg:apk/alpine/{name} whichever configured repository they came from, so a package rule covers the package in all of them. Rules may leave out the alpine namespace (pkg:apk/curl); another namespace could never match and is refused. The docs row warns that Alpine mirrors only keep the current build, so an evicted older one can't come back.

The mirror can't fetch apk packages, so there's no mirror test.

Refs #306
2026-10-12 10:23:27 +02:00