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
8 changes: 8 additions & 0 deletions image/docker/docker_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ type dockerClient struct {
signatureBase lookasideStorageBase
useSigstoreAttachments bool
scope authScope
// namespaceProxy enables OCI distribution-spec registry proxying.
// When set, an "ns" query parameter is appended to all requests.
namespaceProxy string

// The following members are detected registry properties:
// They are set after a successful detectProperties(), and never change afterwards.
Expand Down Expand Up @@ -491,6 +494,11 @@ func (c *dockerClient) resolveRequestURL(path string) (*url.URL, error) {
if err != nil {
return nil, err
}
if c.namespaceProxy != "" {
q := res.Query()
q.Set("ns", c.namespaceProxy)
res.RawQuery = q.Encode()
}
return res, nil
}

Expand Down
55 changes: 55 additions & 0 deletions image/docker/docker_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,58 @@ func TestIsManifestUnknownError(t *testing.T) {
assert.True(t, res, "%s: %#v", c.name, err)
}
}

func TestResolveRequestURLWithNamespaceProxy(t *testing.T) {
for _, c := range []struct {
name string
registry string
scheme string
path string
namespaceProxy string
expected string
}{
{
name: "no namespace proxy",
registry: "registry.example.com",
scheme: "https",
path: "/v2/library/nginx/manifests/latest",
namespaceProxy: "",
expected: "https://registry.example.com/v2/library/nginx/manifests/latest",
},
{
name: "with namespace proxy for docker.io",
registry: "proxy.example.com",
scheme: "https",
path: "/v2/library/nginx/manifests/latest",
namespaceProxy: "docker.io",
expected: "https://proxy.example.com/v2/library/nginx/manifests/latest?ns=docker.io",
},
{
name: "with namespace proxy for quay.io",
registry: "proxy.example.com",
scheme: "https",
path: "/v2/coreos/etcd/blobs/sha256:abc123",
namespaceProxy: "quay.io",
expected: "https://proxy.example.com/v2/coreos/etcd/blobs/sha256:abc123?ns=quay.io",
},
{
name: "with namespace proxy and port",
registry: "proxy.example.com:5000",
scheme: "http",
path: "/v2/myimage/manifests/v1.0",
namespaceProxy: "gcr.io",
expected: "http://proxy.example.com:5000/v2/myimage/manifests/v1.0?ns=gcr.io",
},
} {
t.Run(c.name, func(t *testing.T) {
client := &dockerClient{
registry: c.registry,
scheme: c.scheme,
namespaceProxy: c.namespaceProxy,
}
result, err := client.resolveRequestURL(c.path)
require.NoError(t, err)
assert.Equal(t, c.expected, result.String())
})
}
}
Comment on lines +449 to +502
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The test suite should include a test case that verifies the behavior when the path already contains query parameters. This is important because the search functionality (lines 466-470 in docker_client.go) can pass paths with existing query parameters like "?last=xyz&n=100" from pagination links. A test case should verify that the namespace proxy parameter is correctly appended to existing parameters, for example: path "/v2/_catalog?last=xyz&n=100" with namespaceProxy "docker.io" should result in "/v2/_catalog?last=xyz&n=100&ns=docker.io" (or with parameters in a different order since url.Values.Encode() doesn't guarantee order).

Copilot uses AI. Check for mistakes.
1 change: 1 addition & 0 deletions image/docker/docker_image_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica
return nil, err
}
client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure
client.namespaceProxy = pullSource.Endpoint.NamespaceProxy

s := &dockerImageSource{
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
Expand Down
5 changes: 5 additions & 0 deletions image/pkg/sysregistriesv2/system_registries_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ type Endpoint struct {
// This can only be set in a registry's Mirror field, not in the registry's primary Endpoint.
// This per-mirror setting is allowed only when mirror-by-digest-only is not configured for the primary registry.
PullFromMirror string `toml:"pull-from-mirror,omitempty"`
// NamespaceProxy enables OCI distribution-spec registry proxying.
// When set, an "ns" query parameter with this value is appended to all requests,
// allowing a single proxy to route to multiple upstream registries.
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#registry-proxying
Comment on lines +74 to +77
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The new namespace-proxy configuration field should be documented in the containers-registries.conf.5.md file. The documentation should explain the purpose of this field, its format (the value to be used as the namespace), and provide an example configuration showing how to configure a registry proxy that routes to multiple upstream registries. For consistency with other fields like pull-from-mirror, the documentation should be added in the "Per-namespace settings" or "Remapping and mirroring registries" section of the document.

Suggested change
// NamespaceProxy enables OCI distribution-spec registry proxying.
// When set, an "ns" query parameter with this value is appended to all requests,
// allowing a single proxy to route to multiple upstream registries.
// See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#registry-proxying
// NamespaceProxy corresponds to the `namespace-proxy` configuration field and
// enables OCI distribution-spec registry proxying.
//
// When set, the value is used as the namespace and an `ns` query parameter
// with this value is appended to all requests sent to the endpoint. This
// allows a single proxy registry to route to multiple upstream registries
// based on the `ns` value, as defined by the OCI distribution-spec
// registry proxying extension.
//
// Example configuration (TOML) for a proxy registry that can route to
// multiple upstream registries:
//
// [[registry]]
// prefix = "proxy.example.com"
//
// # Images under proxy.example.com/registry1/* are forwarded to
// # an upstream that interprets ns=registry1
// [[registry.mirror]]
// location = "proxy.example.com"
// namespace-proxy = "registry1"
//
// # Images under proxy.example.com/registry2/* are forwarded to
// # a different upstream that interprets ns=registry2
// [[registry.mirror]]
// location = "proxy.example.com"
// namespace-proxy = "registry2"
//
// For the full specification, see:
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#registry-proxying

Copilot uses AI. Check for mistakes.
NamespaceProxy string `toml:"namespace-proxy,omitempty"`
}

// userRegistriesFile is the path to the per user registry configuration file.
Expand Down
Loading