Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ HTTP protocol handlers for each registry type.
- `handleIndex()` - Proxy sparse index
- `handleDownload()` - Serve cached crate

**DebianHandler:**
- Serves `/debian` and `/debian/{name}` mirrors of APT archives (`upstream.debian_repositories`)
- Routes `.deb` packages to the artifact cache, `dists/…` to the metadata cache, and proxies all other paths
- Keys the artifact cache on name, version, and filename, scoped by repository for named archives

**SwiftHandler:**
- Proxies the Swift Package Registry v1 read endpoints
- Rewrites release URLs and caches source archives
Expand Down
17 changes: 8 additions & 9 deletions internal/handler/debian.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ func (h *DebianHandler) Routes() http.Handler {
}
}

// Route based on path type
// Route by filename rather than path prefix because repositories such as
// Docker's nest the pool under dists/<suite>/.
switch {
case strings.HasPrefix(rest, "pool/"):
// Package downloads - cache these
case isDebPackageFile(rest):
h.handlePackageDownload(w, r, repository, upstreamURL, rest)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
case strings.HasPrefix(rest, "dists/"):
// Repository metadata - served through the metadata cache
Expand All @@ -123,6 +123,10 @@ func (h *DebianHandler) Routes() http.Handler {
}))
}

func isDebPackageFile(path string) bool {
return strings.HasSuffix(path, ".deb")
}

// repositoryNotFound answers a request addressed to a repository that is not
// configured, naming the ones that are so a misspelling is self-evident.
func (h *DebianHandler) repositoryNotFound(w http.ResponseWriter, name string) {
Expand Down Expand Up @@ -226,12 +230,7 @@ var debPackagePattern = regexp.MustCompile(`^(.+)_([^_]+)_([^_]+)\.deb$`)
// parsePoolPath extracts package info from a pool path.
// Example: pool/main/n/nginx/nginx_1.18.0-6_amd64.deb
func (h *DebianHandler) parsePoolPath(path string) (name, version, arch string) {
// Get the filename
idx := strings.LastIndex(path, "/")
if idx < 0 {
return "", "", ""
}
filename := path[idx+1:]
filename := path[strings.LastIndex(path, "/")+1:]

// Parse the filename
matches := debPackagePattern.FindStringSubmatch(filename)
Expand Down
39 changes: 39 additions & 0 deletions internal/handler/debian_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestDebianHandler_parsePoolPath(t *testing.T) {
"nmap", "7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1", "amd64",
},
{"pool/main/o/openssl/openssl_3.0.2-0ubuntu1.15~build1_amd64.deb", "openssl", "3.0.2-0ubuntu1.15~build1", "amd64"},
{"nginx_1.18.0-6_amd64.deb", "nginx", "1.18.0-6", "amd64"},
{"invalid/path", "", "", ""},
{"pool/main/n/nginx/nginx.deb", "", "", ""},
})
Expand Down Expand Up @@ -251,6 +252,44 @@ func TestDebianHandler_PackageCacheScopedPerRepository(t *testing.T) {
}
}

func TestDebianHandler_FlatArchivePackageIsCached(t *testing.T) {
const pkgBody = "docker-ce package bytes"

var upstreamHits int

archive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
upstreamHits++
_, _ = fmt.Fprint(w, pkgBody)
}))
defer archive.Close()

proxy, _, _, _ := setupTestProxy(t)
fetcher := fetch.NewFetcher(fetch.WithHTTPClient(archive.Client()), fetch.WithMaxRetries(0))
proxy.Fetcher = fetcher
t.Cleanup(func() { _ = fetcher.Close() })

h := NewDebianHandler(proxy, "http://proxy.example", "https://archive.test", map[string]string{
"docker": archive.URL,
})

for _, path := range []string{
"/docker/dists/trixie/pool/stable/amd64/docker-ce_5.0.1-1~debian.13~trixie_amd64.deb",
"/docker/foo_1.0_amd64.deb",
} {
for attempt := 1; attempt <= 2; attempt++ {
w := serveDebianRequest(h, path)
if w.Code != http.StatusOK || w.Body.String() != pkgBody {
t.Fatalf("%s request %d: status = %d, body = %q, want 200 %q",
path, attempt, w.Code, w.Body.String(), pkgBody)
}
}
}

if upstreamHits != 2 {
t.Errorf("upstream fetched %d times for four requests, want 2: the package is not cached", upstreamHits)
}
}

func serveDebianRequest(h *DebianHandler, target string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
h.Routes().ServeHTTP(w, httptest.NewRequest(http.MethodGet, target, nil))
Expand Down