Skip to content

fix(registry): preserve mirror URL path prefix when resolving/pulling backend images - #987

Merged
ilopezluna merged 4 commits into
mainfrom
fix/registry-mirror-preserve-path
Jun 25, 2026
Merged

fix(registry): preserve mirror URL path prefix when resolving/pulling backend images#987
ilopezluna merged 4 commits into
mainfrom
fix/registry-mirror-preserve-path

Conversation

@ilopezluna

@ilopezluna ilopezluna commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

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 Forbidden HEAD to registry-1.docker.io, even though the mirror was configured and active.

Root cause: registryutil.RegistryHosts dropped the path component of the configured mirror URL and hardcoded the Registry v2 API root to /v2 on 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 upstream registry-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

  • Preserve the mirror pathRegistryHosts now builds Path = strings.TrimRight(u.Path, "/") + "/v2" instead of forcing /v2 on the bare host. A mirror https://host/artifactory/api/docker/<repo> now resolves manifests at https://host/artifactory/api/docker/<repo>/v2/<name>/manifests/<ref>.
  • Robust scheme-less parsing — a mirror given without a scheme (e.g. host:5000/path) parses with an empty Host and the whole value in Path; re-parse it as https://… so host and path are separated correctly (the previous code put the entire string into Host).
  • Mirrors with no path keep the prior /v2 behavior.

Tests

  • pkg/internal/registryutil/mirrors_test.go (new) — table-driven coverage of the resulting Host/Scheme/Path across mirror forms (path prefix, trailing slash, no path, scheme-less with/without path, http scheme), plus the upstream fallback ordering and the non-Docker-Hub bypass.
  • pkg/internal/dockerhub/download_test.goTestResolveDigest_UsesMirrorPathPrefix: an httptest registry 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

… 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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pkg/internal/registryutil/mirrors.go Outdated
Comment thread pkg/internal/registryutil/mirrors_test.go
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>
@ilopezluna
ilopezluna marked this pull request as ready for review June 25, 2026 12:46

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread pkg/internal/registryutil/mirrors.go Outdated
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>
@ilopezluna

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • 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.
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.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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>
@ilopezluna

Copy link
Copy Markdown
Contributor Author

Thanks @sourcery-ai — extracted the mirror URL normalization (scheme defaulting, path-prefix preservation, /v2 suffix handling) into a documented parseMirror helper in db8f0c4, keeping the RegistryHosts loop small. No behavior change; existing tests still cover the edge cases.

@ilopezluna

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ilopezluna
ilopezluna merged commit 0604f66 into main Jun 25, 2026
14 checks passed
@ilopezluna
ilopezluna deleted the fix/registry-mirror-preserve-path branch June 25, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants