From 338d03363220a0e168834282237c776947ddaa97 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 13:02:08 +0200 Subject: [PATCH] A manifest HEAD says what it accepts, or the registry answers 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check that skips copying a base the mesh already holds asked with no Accept header, and a registry answers a manifest only in a media type the caller named: the same digest answered 200 with the manifest types and 404 without them. So the builder concluded it held nothing, copied every vendor base again, and exhausted the public hub's pull limit a second time today. The test could not have caught it, because the fake registry answered a manifest HEAD regardless of Accept — more permissive than the thing it stands in for. It is now as strict as a real registry, and fails without the fix. --- internal/builder/mirror.go | 2 +- internal/builder/mirror_test.go | 8 ++++++++ internal/builder/registry.go | 14 +++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/builder/mirror.go b/internal/builder/mirror.go index 83307fe..f5d5fb7 100644 --- a/internal/builder/mirror.go +++ b/internal/builder/mirror.go @@ -186,7 +186,7 @@ func (r Registry) MirrorImage(ctx context.Context, from, repository string) (str // on the first merge that rebuilt a whole catalogue (2026-09-28), and every module whose base // lives there failed on a copy it did not need. if strings.HasPrefix(where.reference, "sha256:") { - held, err := r.has(ctx, "http://"+r.Address+"/v2/"+repository+"/manifests/"+where.reference) + held, err := r.has(ctx, "http://"+r.Address+"/v2/"+repository+"/manifests/"+where.reference, manifestAccept) if err != nil { return "", fmt.Errorf("asking %s whether it holds %s: %w", r.Address, from, err) } diff --git a/internal/builder/mirror_test.go b/internal/builder/mirror_test.go index 7f8adbd..9a17b18 100644 --- a/internal/builder/mirror_test.go +++ b/internal/builder/mirror_test.go @@ -104,6 +104,14 @@ func (m *theMeshsRegistry) handler() http.Handler { defer m.mu.Unlock() switch { case r.Method == http.MethodHead && strings.Contains(r.URL.Path, "/manifests/"): + // **As strictly as a real registry.** A manifest is answered only in a media type the + // caller named; a request with no Accept is answered as if nothing were there. The fake + // used to answer regardless, which is why it could not catch a check that asked without + // one — and the mesh copied every base again (2026-09-28). + if !strings.Contains(r.Header.Get("Accept"), "manifest") && !strings.Contains(r.Header.Get("Accept"), "index") { + w.WriteHeader(http.StatusNotFound) + return + } if _, ok := m.manifests[r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]]; ok { w.WriteHeader(http.StatusOK) } else { diff --git a/internal/builder/registry.go b/internal/builder/registry.go index 481ce53..fd277d0 100644 --- a/internal/builder/registry.go +++ b/internal/builder/registry.go @@ -122,11 +122,23 @@ func (r Registry) PublishArchive(ctx context.Context, repository string, body [] return final, nil } -func (r Registry) has(ctx context.Context, url string) (bool, error) { +// has is whether this registry already holds what is at that URL. +// +// **A manifest HEAD must say what it accepts.** A registry answers a manifest request only in a media +// type the caller named, and a bare HEAD — no Accept at all — is answered 404 for a manifest it holds +// perfectly well. Measured against the mesh's own registry (2026-09-28): the same digest answered 200 +// with the manifest media types and 404 without them, so a check written without them concluded the +// registry held nothing, copied every base again, and exhausted the public hub's pull limit. A blob +// needs no Accept, which is why this went unnoticed: the same helper was right for blobs and wrong +// for manifests. +func (r Registry) has(ctx context.Context, url string, accept ...string) (bool, error) { request, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil) if err != nil { return false, err } + for _, media := range accept { + request.Header.Set("Accept", media) + } response, err := r.client().Do(request) if err != nil { return false, fmt.Errorf("cannot reach the registry at %s: %w", r.Address, err)