Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-10 08:09:17 -05:00
Commit graph

1,145 commits

Author SHA1 Message Date
montehurd
dc1fc5baf3
Coalesce concurrent metadata fetches (#400)
* Coalesce concurrent metadata fetches

Concurrent cache misses for one artifact already share a single upstream
fetch (#329), but metadata misses did not: every request for a package's
metadata went to the registry, even when the same document was already
being fetched. In CI that is the common case. Composer resolves versions
on every job, since MediaWiki core and its extensions commit no
composer.lock, and each package is one /p2/ document, so a burst of jobs
fetched each document once per job.

Metadata misses now follow the same pattern as artifacts: the first caller
fetches, the rest wait for its result, and the caller that takes the key
rechecks the cache first so a fetch that just finished is not repeated.
The key includes the Accept and Accept-Encoding headers, which change the
bytes upstream returns (npm's abbreviated and full documents), and whether
the caller validates the response.

One difference from artifacts: the shared fetch is detached from the first
caller's cancellation. Artifact fetches keep it for scanning and mirroring,
neither of which applies to metadata, and otherwise one job disconnecting
would fail every job waiting on the same document. runScan detaches the
same way, and the HTTP client's timeout still bounds the fetch.

This covers every ecosystem that goes through fetchOrCacheMetadata
(Composer, npm, PyPI, Maven, Cargo, Swift, Pub, Helm, NuGet), with
metadata caching on or off. Streamed metadata with caching off is not
coalesced.

On an 8 vCPU test instance with caching off, 10 concurrent clients
requesting the npm metadata of 1,263 packages went from 12,630 upstream
requests to 1,898. With cache_metadata on, 20 clients requesting 1,538
packages made exactly 1,538.

* Run each caller's validate on a shared metadata fetch

A caller that joined another's metadata fetch got the shared bytes
without running its own validate. NuGet's cooldown path uses validate to
decode the document for the request, so a joined download found no
publication date and was allowed, bypassing the cooldown.

coalesceMetadata now reports whether the caller joined, and
coalescedMetadataMiss runs that caller's validate on the shared bytes.
Validation before caching is unchanged: the first caller still runs it
inside the fetch.

TestNuGetCooldownConcurrentDownloads reproduces the bypass through
NuGetHandler.Routes() with two overlapping downloads of a version inside
its cooldown.
2026-10-04 17:50:45 +01:00
PhantomPhoton
f60772d2d8
Support HTTP byte-range requests for local file cached artifact downloads (#404)
* feat(storage): expose efficient seek for local files

Open file-backed artifacts with os.Open so local readers support
efficient seeking. Leave cloud storage readers unchanged.

Keep the legacy sidecar mapping test focused on fileblob's reader:
Blob.Open now reads local files directly and no longer consults the
sidecar. Continue checking sidecar cleanup and post-store reads.

* feat(handler): preserve efficient seek through integrity checks

Keep the cache result's single Reader field and preserve io.Seeker
when the underlying storage reader supports efficient seeking.

Sequential reads continue through whole-object integrity verification.
A successful seek switches subsequent reads to the source for range
responses, where full-object verification is not possible.

Test both modes and verify the source is closed once.

* feat(handler): add request-aware byte range responses

Add a request-aware artifact serving helper for bounded, open-ended,
and suffix byte ranges, including If-Range handling, 206 responses,
and 416 responses for valid unsatisfiable ranges.

Ignore malformed and multi-range requests by serving the full artifact.
Advertise byte-range support only for seekable readers, preserving HEAD,
redirect, and non-seekable behavior. Clarify that net/http recovers
ErrAbortHandler per request and keeps the server running.

* feat(handler): enable byte ranges across artifact downloads

Forward each artifact request to the shared range-aware response helper,
including cached generic release assets and OCI blobs. This lets the
handler honor ranges only when its reader supports efficient seeking,
without changing direct-storage redirect behavior.

Add warm-cache endpoint tests that assert partial response headers and
bytes without an upstream refetch. Document supported range behavior,
unsupported reader fallback, and the integrity limitation of partial reads.
2026-10-04 17:48:41 +01:00
montehurd
4b1625b2eb
Copy inherited Composer fields shallowly when expanding (#401)
expandMinifiedVersions deep-copied every inherited field into every
version (b68184c) so that rewriting one version's dist URL in place
could not change the versions that inherited it. For a package with a
long history that means recursively copying require, autoload and the
rest hundreds of times per document, on every metadata request.

dist is the only field the proxy changes after expansion, so copy just
that: rewriteDistURL now gives the version its own dist map before
setting the URL, and expansion shares the other inherited values.
TestComposerExpandMinifiedSharedDistReferences still guards the original
bug, and fails if the copy in rewriteDistURL is removed.

On the symfony/console metadata from Packagist the rewritten output is
byte-identical, and the rewrite drops from about 20.7 ms to 13.8 ms,
with allocations down from 13.6 MB to 9 MB.
2026-10-03 12:10:44 +01:00
montehurd
8cc63c40ee
Cache rewritten npm and Composer metadata (#402)
The npm and Composer handlers rewrite every metadata document they serve
so that download URLs point at the proxy: decode the whole document into
generic maps, change the URLs, encode it again, and for Composer expand
the minified format first. That ran on every request, cached metadata
included. With metadata caching on and upstream out of the picture, a
cached request still cost 7 ms for @babel/core, 17 ms for
symfony/console and 155 ms and 134 MB of allocations for typescript, and
an 8 vCPU VM serving cached npm packuments to 20 clients ran the proxy
at 450% CPU for about 1,100 requests a second.

Rewritten documents are now kept in memory, keyed by the ecosystem,
proxy URL, package and a SHA-256 of the raw document, so new bytes from
upstream are rewritten again and nothing is served stale. Requests that
arrive while a document is being rewritten wait for that rewrite rather
than running their own; a waiter leaves when its client does, and the
rewrite still completes and is cached. The denylist is fixed at startup,
so it needs no place in the key. Cooldown filtering depends on the
current time, so with cooldown on the cache is bypassed.

metadata_rewrite_cache_size bounds the cache (default "256MB", least
recently used out first, "0" to rewrite on every request). NewProxy
callers keep the old behaviour unless they set it.

Whole cached requests, measured locally:
  @babel/core      7.1 ms -> 0.38 ms, 54,489 -> 119 allocations
  typescript       155 ms -> 10.5 ms, 1.37M -> 133 allocations
  symfony/console  17.4 ms -> 0.48 ms, 165,909 -> 123 allocations
What remains is reading the raw document from storage and hashing it.
2026-10-03 12:09:15 +01:00
Andrew Nesbitt
739bba316c
Drop unused registry from TestUpdateEcosystemStatsSumsAliasedRows 2026-10-02 10:42:59 +01:00
Andrew Nesbitt
e7817dc86c
Merge pull request #393 from wickedOne/analytics-6-source-attribution
[6/6] Attribute requests to a caller and a client tool
2026-10-02 10:38:16 +01:00
Andrew Nesbitt
4261bcbc3f
Show the by-client table regardless of ui_request_sources
The flag exists to withhold caller addresses. The by-client table carries
none, and the same figures are already public at /metrics, so gating it
left two metrics with no tile on the page in the default config.
2026-10-02 10:34:41 +01:00
Andrew Nesbitt
5fce1871d2
Merge pull request #392 from wickedOne/analytics-5-grafana
[5/6] Add a Grafana dashboard
2026-10-02 10:20:16 +01:00
Andrew Nesbitt
7c129f9adb
Merge pull request #391 from wickedOne/analytics-4-stats-breakdown
[4/6] Report the per-ecosystem breakdown from GET /stats
2026-10-02 10:16:25 +01:00
Andrew Nesbitt
31b73784f5
Merge pull request #390 from wickedOne/analytics-3-page
[3/6] Add the /ui/analytics page
2026-10-02 10:13:30 +01:00
Andrew Nesbitt
f40f839b80
Merge pull request #389 from wickedOne/analytics-2-ecosystem-stats
[2/6] Per-ecosystem cache and download statistics
2026-10-02 10:02:29 +01:00
Andrew Nesbitt
9dff45d36c
Merge remote-tracking branch 'origin/main' into analytics-2-ecosystem-stats
# Conflicts:
#	README.md
2026-10-02 09:57:00 +01:00
Andrew Nesbitt
82f5e66d8f
Merge pull request #388 from wickedOne/analytics-1-route-coverage
[1/6] Name every mounted route in requestEcosystem
2026-10-02 09:41:41 +01:00
Andrew Nesbitt
2124e8c8ca
Merge pull request #383 from montehurd/batch-hit-updates
Batch cache hit writes
2026-10-02 09:33:11 +01:00
Daniil Skripchenko
b40a50aad8
Add storage.cache_artifacts to serve artifacts without storing them (#387)
* Add storage.passthrough to serve artifacts without storing them

With storage.passthrough enabled the proxy streams every artifact from
upstream to the client and never writes it to storage or the cache
database. Metadata filtering, cooldown and the denylist work as before,
so the proxy can sit behind another cache (e.g. an Artifactory remote)
purely as a policy layer without holding a second copy of every package.

Artifacts whose digest is known up front (OCI, Swift, Helm) are verified
while streaming. Their responses are sent chunked and the connection is
aborted on a mismatch, so a client never receives a tampered artifact as
a complete response.

Passthrough is rejected together with scanning, direct_serve and
mirror_api, and the mirror command refuses to run with it, since all of
them depend on stored artifacts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: abort truncated streams, guard every cache read

- serveArtifact aborts the response on any body read error, and when fewer
  bytes than the declared size were written, not only on a digest mismatch.
  A failed or short upstream body in passthrough mode no longer reaches the
  client as a complete 200.
- Streamed upstream read failures are logged and counted as
  stream_failed upstream errors before the response is aborted.
- checkCache reports a miss when artifact caching is off, so every cache
  read path, including Swift archive HEAD requests, ignores entries stored
  before the mode was enabled. Denylisted versions are still rejected.
- Rename storage.passthrough to storage.cache_artifacts (default true,
  PROXY_STORAGE_CACHE_ARTIFACTS), which names what actually changes.

Tests cover an OCI blob shorter than its Content-Length and an npm tarball
whose chunked response ends without the final chunk, both served through
the HTTP handlers, plus a Swift HEAD request against a cached archive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rename Proxy.Passthrough to StreamArtifacts

Match the storage.cache_artifacts option: the field says what changes
(artifacts are streamed instead of stored) and its zero value keeps the
usual caching behaviour for every Proxy built without the server config,
such as the mirror command and tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:28:20 +01:00
Andrew Nesbitt
76db94552f
Install development tools directly in CI (#398) 2026-10-01 19:48:27 +01:00
dependabot[bot]
107301c984
build(deps): bump alpine from 3.24.1 to 3.24.2 (#397)
Bumps alpine from 3.24.1 to 3.24.2.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: 3.24.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 16:46:01 +01:00
montehurd
55ed6b9a16 Keep hit timestamps from moving backwards
With proxies sharing a database, a batch can flush after a newer hit is
already written, and overwriting last_accessed_at and updated_at with
its older time could reorder LRU eviction. Both now keep the later of
the stored and incoming times. The count still adds up.
2026-10-01 08:21:55 -07:00
Andrew Nesbitt
51caa17d5e
Update git-pkgs dependencies (#396) v0.9.0 2026-10-01 16:06:12 +01:00
Andrew Nesbitt
5f0f3b034a
Update enrichment to v0.7.2 (#394)
* Update ecosystems-go to v0.5.0

* Update enrichment to v0.7.2
2026-10-01 15:25:11 +01:00
Wicliff Wolda
3655000b16
Attribute requests to a caller and a client tool
- Track per-caller request and byte counts in a bounded table
- Export proxy_client_requests_total and
  proxy_client_response_bytes_total over a closed label set; addresses
  are never labels
- Gate the sources card behind ui_request_sources, off by default,
  since /ui carries no authentication of its own
- Move access_log.trust_forwarded_for to a top-level key
- Keep remote as the TCP peer and add remote_ip alongside it

Part 6 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 16:15:03 +02:00
Wicliff Wolda
36fe6abef2
Add a Grafana dashboard
- 33 panels, no hardcoded data source UID
- Three ecosystem variables, because the label means three different
  things across /metrics: the package record, the request path, and
  the handler's own name

Part 5 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 16:15:03 +02:00
Wicliff Wolda
4b8c5a89fb
Report the per-ecosystem breakdown from GET /stats
- Add downloaded_bytes, downloads and an ecosystems array, served from
  the snapshot the gauges and the page already share
- Add stats_unavailable so a failed aggregation is distinguishable
  from an idle proxy
- Regenerate the OpenAPI spec

Part 4 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 16:15:03 +02:00
Wicliff Wolda
05015344cb
Add the /ui/analytics page
- Ring of download size by ecosystem, cache figures, per-ecosystem
  table, vulnerability overview and a Runtime card
- Add metrics.Gather so the page can render counters that were never
  in the database
- Add proxy_response_bytes_total and response-writer byte counting
- Serve a retained snapshot behind a staleness banner when the
  aggregation fails, rather than rendering old figures as current
- Extract the security overview into a shared component

Part 3 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 16:15:03 +02:00
Wicliff Wolda
4ebd805ffc
Per-ecosystem cache and download statistics
- Add GetEcosystemStats, aggregating packages, versions, artifacts,
  cache size, downloads and downloaded bytes per ecosystem
- Publish six proxy_ecosystem_* gauges from it on the existing
  one-minute cache-stats tick
- Set and selectively delete rather than Reset, so no scrape lands on
  a half-populated vector
- Report artifacts with no package row under "unattributed" rather
  than dropping them, so the figures still add up

Part 2 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 16:15:03 +02:00
Andrew Nesbitt
fb1630a27b
Merge pull request #395 from wickedOne/relay-test-flake
test(handler): fix TestRelayRoutes flake on /hex/packages and /gem/info
2026-10-01 15:12:59 +01:00
Wicliff Wolda
e738a21a2a
test(handler): stop asserting on an upstream connection the proxy abandons
TestRelayRoutes fails intermittently on CI with "write: connection reset
by peer" at the upstream handler's final Flush, on /hex/packages/demo and
/gem/info/demo.

Those two routes are the only ones in the table that fan out a second
upstream request: with cooldown enabled, the hex and gem handlers fetch
version timestamps concurrently with the artifact. The test server answers
that sidecar request with the same 64 KiB chunked 502, but the handler
returns as soon as it sees a non-200 and closes the body unread, so the
proxy drops the connection while the test server is still writing to it.
Whether the reset lands before or after the Flush is a scheduling race,
which is why it only surfaces on loaded runners.

The flush error says nothing about the response under test, which is
asserted on the downstream side, so stop reporting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 15:30:22 +02:00
Wicliff Wolda
fb04bb8dcb
Name every mounted route in requestEcosystem
- Map /apk to alpine, matching what the handler writes to the database
- Report /helm, /homebrew, /generic and /swift under their own names
- Test every one of the 23 mounts, so the next route added cannot
  quietly fall through to "other"

Part 1 of 6 splitting #381 up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 14:32:20 +02:00
Andrew Nesbitt
b6f11db34c
Merge pull request #382 from wickedOne/npm-audit
fix pnpm audit
2026-10-01 12:09:27 +01:00
wickedOne
d8a811148c
apply review changes 2026-10-01 10:09:33 +02:00
wickedOne
c4f34b07b4
fix pnpm audit 2026-10-01 10:08:26 +02:00
montehurd
4827cb9d76 Batch cache hit writes
Every cached download wrote its hit in its own UPDATE. SQLite runs on
one connection, so each download was a write transaction the rest
queued behind, holding a warm npm cache near 1,000 requests a second
with the CPU two thirds idle.

Hits are now counted in memory and written in one transaction every
database.hit_flush_interval, "1s" by default, "0" for a write per hit.
A failed write keeps the hits for the next flush, and Close writes
what is pending. The same test then served about 2,200 requests a
second.

Closes #322.
2026-10-01 00:13:58 -07:00
Andrew Nesbitt
c3540fbcdf
Merge pull request #380 from abhinavgautam01/fix/response-relay-326
fix: centralize pass-through response relaying
2026-10-01 06:35:46 +01:00
Andrew Nesbitt
6f2bc9a9ce
Merge pull request #367 from montehurd/per-fetch-storage-path
Give each fetch its own storage path
2026-10-01 06:35:29 +01:00
Andrew Nesbitt
eb4350568c
Log and count requests aborted mid-relay
A truncated relay panics with http.ErrAbortHandler, which skipped the
request log line, the request metrics and the access log entry.
2026-10-01 06:31:00 +01:00
montehurd
436155e50c Clear before deleting on eviction, and requeue failed reclaims
Eviction deleted an object before checking its record still pointed at
it. If a refetch had moved the record, that object was queued and a
request that read the record earlier could still be opening it. Eviction
now clears first, skips the delete if the record moved, and queues the
object when the delete fails after a clear.

Reclaim queues a failed delete again, which moves it behind the rest, so
objects the backend keeps refusing cannot fill every batch.

Also drop the clearArtifactCache passthrough and ClearCachedArtifact's
unused context, note that only tests call ArtifactPath, and document that
max_size does not count objects waiting to be reclaimed.
2026-09-29 10:24:22 -07:00
abhinavgautam01
ac261cc34d
fix: centralize pass-through response relaying 2026-09-26 15:44:28 +05:30
Andrew Nesbitt
b00da65ffd
Merge pull request #379 from abhinavgautam01/fix/conda-repodata-gzip-321
fix(conda): preserve gzip for large repodata indexes
2026-09-26 09:25:22 +01:00
abhinavgautam01
e40c78f72a
fix(conda): preserve gzip for large repodata indexes 2026-09-25 21:12:00 +05:30
Andrew Nesbitt
b5fcee5c7e
Merge pull request #375 from git-pkgs/dependabot/go_modules/github.com/git-pkgs/archives-0.7.1
build(deps): bump github.com/git-pkgs/archives from 0.7.0 to 0.7.1
2026-09-24 17:35:29 +01:00
dependabot[bot]
eedc2908df
build(deps): bump github.com/git-pkgs/archives from 0.7.0 to 0.7.1
Bumps [github.com/git-pkgs/archives](https://github.com/git-pkgs/archives) from 0.7.0 to 0.7.1.
- [Commits](https://github.com/git-pkgs/archives/compare/v0.7.0...v0.7.1)

---
updated-dependencies:
- dependency-name: github.com/git-pkgs/archives
  dependency-version: 0.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-24 16:22:59 +00:00
Andrew Nesbitt
36556945ed
Merge pull request #378 from git-pkgs/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/config-1.33.5
build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.33.4 to 1.33.5
2026-09-24 17:20:57 +01:00
dependabot[bot]
ccc80f8449
build(deps): bump github.com/aws/aws-sdk-go-v2/config
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.33.4 to 1.33.5.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.33.4...config/v1.33.5)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.33.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-24 16:01:35 +00:00
Andrew Nesbitt
10ebb52aa2
Merge pull request #377 from git-pkgs/dependabot/go_modules/github.com/git-pkgs/vers-0.7.1
build(deps): bump github.com/git-pkgs/vers from 0.7.0 to 0.7.1
2026-09-24 17:00:20 +01:00
Andrew Nesbitt
7379f4721e
Merge pull request #376 from git-pkgs/dependabot/go_modules/modernc.org/sqlite-1.59.0
build(deps): bump modernc.org/sqlite from 1.58.0 to 1.59.0
2026-09-24 16:59:53 +01:00
Andrew Nesbitt
43a91ec20d
Merge pull request #374 from git-pkgs/dependabot/go_modules/github.com/git-pkgs/magic-0.4.0
build(deps): bump github.com/git-pkgs/magic from 0.3.1 to 0.4.0
2026-09-24 16:59:08 +01:00
Andrew Nesbitt
4f80102529
Merge pull request #373 from git-pkgs/dependabot/go_modules/github.com/git-pkgs/registries-0.9.2
build(deps): bump github.com/git-pkgs/registries from 0.9.1 to 0.9.2
2026-09-24 16:58:40 +01:00
Andrew Nesbitt
e4682c8ec1
Merge pull request #372 from git-pkgs/dependabot/go_modules/golang.org/x/net-0.59.0
build(deps): bump golang.org/x/net from 0.58.0 to 0.59.0
2026-09-24 16:58:01 +01:00
Andrew Nesbitt
a4702d0814
Merge pull request #371 from git-pkgs/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/service/ecr-1.66.0
build(deps): bump github.com/aws/aws-sdk-go-v2/service/ecr from 1.65.0 to 1.66.0
2026-09-24 16:57:32 +01:00
Andrew Nesbitt
e26c2d13a7
Merge pull request #370 from git-pkgs/dependabot/github_actions/docker/build-push-action-7.4.0
build(deps): bump docker/build-push-action from 7.3.0 to 7.4.0
2026-09-24 16:56:43 +01:00