fix(registry): preserve mirror URL path prefix when resolving/pulling backend images - #987
Conversation
… hosts RegistryHosts dropped the path component of a configured registry mirror and hardcoded the Registry v2 API root to "/v2" on the bare host. Mirrors served under a path prefix — notably a JFrog Artifactory repository path such as /artifactory/api/docker/<repo> — were therefore queried at the host root, missed, and the resolver fell back to registry-1.docker.io (403 behind a corporate proxy with no Hub egress). Preserve the mirror's path and append "/v2" to it, and re-parse scheme-less mirrors as https so the host and path are separated correctly. This applies to both tag resolution (ResolveDigest) and the image pull (PullPlatform), since both build hosts via RegistryHosts. Refs CSESC-1468. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request adds support for preserving path prefixes on registry mirrors (such as JFrog Artifactory) and improves the parsing of scheme-less mirror URLs, accompanied by comprehensive tests. The review feedback correctly identifies a critical parsing bug where scheme-less mirrors using IP addresses with ports (e.g., 127.0.0.1:5000) will fail to parse and be silently skipped. Refactoring the URL parsing logic to prepend the scheme before parsing and adding a corresponding test case is highly recommended to resolve this issue.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
A scheme-less mirror using an IP address with a port (e.g. "127.0.0.1:5000")
errored on the first url.Parse ("first path segment in URL cannot contain
colon") and was silently skipped. Prepend the https scheme before parsing
when the mirror has no scheme, so IP:port and host:port mirrors are parsed
uniformly, and skip only when the resulting host is empty.
Addresses review feedback on #987.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="pkg/internal/registryutil/mirrors.go" line_range="59" />
<code_context>
+ // repository path "/artifactory/api/docker/<repo>") and append the
+ // Registry v2 API root. Without this, a mirror configured with a path
+ // would be queried at the host root and fail.
+ path := strings.TrimRight(u.Path, "/") + "/v2"
hosts = append(hosts, docker.RegistryHost{
Client: mirrorClient,
</code_context>
<issue_to_address>
**issue (bug_risk):** Path handling may produce a duplicated `/v2` segment for mirrors already configured with a `/v2` suffix.
For mirrors whose configured path already ends with `/v2` (e.g. `https://host/artifactory/api/docker/repo/v2`), this will produce `/artifactory/api/docker/repo/v2/v2`. Consider guarding against this by only appending `/v2` when the path does not already have that suffix (e.g. via `strings.HasSuffix(u.Path, "/v2")`).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
A mirror configured with an explicit "/v2" suffix (e.g. https://host/artifactory/api/docker/repo/v2) would otherwise produce a duplicated "/v2/v2" path. Only append "/v2" when the configured path does not already end with it. Addresses review feedback on #987. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The scheme-less mirror handling and path suffix logic in
RegistryHostsis becoming a bit dense; consider extracting the parsing/normalization of the mirror URL into a small helper to make the behavior and edge cases (e.g., existing/v2suffixes, scheme defaults) easier to reason about and maintain.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The scheme-less mirror handling and path suffix logic in `RegistryHosts` is becoming a bit dense; consider extracting the parsing/normalization of the mirror URL into a small helper to make the behavior and edge cases (e.g., existing `/v2` suffixes, scheme defaults) easier to reason about and maintain.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
… helper Move the scheme-less parsing, path-prefix preservation and /v2 suffix handling out of the RegistryHosts closure into a documented parseMirror helper, keeping the host-building loop small and the normalization rules in one place. Addresses review feedback on #987. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @sourcery-ai — extracted the mirror URL normalization (scheme defaulting, path-prefix preservation, |
|
@sourcery-ai review |
Context
Follow-up to #974, which routed the llama.cpp backend tag→digest resolution through the mirror-aware containerd resolver. While diagnosing CSESC-1468 on the latest logs, the backend pull still failed with a
403 ForbiddenHEAD toregistry-1.docker.io, even though the mirror was configured and active.Root cause:
registryutil.RegistryHostsdropped the path component of the configured mirror URL and hardcoded the Registry v2 API root to/v2on the bare host. A mirror served under a path prefix — notably a JFrog Artifactory repository path like/artifactory/api/docker/<repo>— was therefore queried at the host root (<host>/v2/...), missed, and the resolver fell through to the upstreamregistry-1.docker.io, which 403s behind the corporate proxy. The upstream 403 masked the real (silent) mirror miss.This is independent of #974 and affects both code paths that build hosts via
RegistryHosts: tag resolution (ResolveDigest) and the image pull (PullPlatform).Changes
RegistryHostsnow buildsPath = strings.TrimRight(u.Path, "/") + "/v2"instead of forcing/v2on the bare host. A mirrorhttps://host/artifactory/api/docker/<repo>now resolves manifests athttps://host/artifactory/api/docker/<repo>/v2/<name>/manifests/<ref>.host:5000/path) parses with an emptyHostand the whole value inPath; re-parse it ashttps://…so host and path are separated correctly (the previous code put the entire string intoHost)./v2behavior.Tests
pkg/internal/registryutil/mirrors_test.go(new) — table-driven coverage of the resultingHost/Scheme/Pathacross mirror forms (path prefix, trailing slash, no path, scheme-less with/without path,httpscheme), plus the upstream fallback ordering and the non-Docker-Hub bypass.pkg/internal/dockerhub/download_test.go—TestResolveDigest_UsesMirrorPathPrefix: anhttptestregistry that serves only under/artifactory/docker/v2(404 at the host root), so it fails if the path-preserving behavior regresses.Verification
go build ./...,go vet ./pkg/internal/registryutil/... ./pkg/internal/dockerhub/...go test ./pkg/internal/registryutil/... ./pkg/internal/dockerhub/...(goleak clean)Note
This fix is necessary but not sufficient on its own for the CSESC-1468 customer: they still need to identify the exact URL at which their Artifactory exposes a compatible Docker Registry v2 API (their
/artifactory/docker/v2/returned 404). Once that base URL is configured as the mirror, this change ensures Model Runner honors its path.🤖 Generated with Claude Code