From 0c73c459b46aea2a524f214e7d8d646166911f1f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 1 Aug 2025 15:23:08 +0200 Subject: [PATCH 1/5] daemon/pkg/registry: un-export GetAuthConfigKey We want to get rid of the use of using "registry.IndexInfo". Make the function un-exported to discourage additional use. Signed-off-by: Sebastiaan van Stijn --- daemon/pkg/registry/auth.go | 2 +- daemon/pkg/registry/config.go | 4 ++-- daemon/pkg/registry/search_endpoint_v1.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/pkg/registry/auth.go b/daemon/pkg/registry/auth.go index a361a04fe2..90cc4c8efd 100644 --- a/daemon/pkg/registry/auth.go +++ b/daemon/pkg/registry/auth.go @@ -147,7 +147,7 @@ func ConvertToHostname(maybeURL string) string { // ResolveAuthConfig matches an auth configuration to a server address or a URL func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig { - configKey := GetAuthConfigKey(index) + configKey := getAuthConfigKey(index) // First try the happy case if c, found := authConfigs[configKey]; found || index.Official { return c diff --git a/daemon/pkg/registry/config.go b/daemon/pkg/registry/config.go index ac13a31a82..e5f36904e9 100644 --- a/daemon/pkg/registry/config.go +++ b/daemon/pkg/registry/config.go @@ -359,9 +359,9 @@ func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { } } -// GetAuthConfigKey special-cases using the full index address of the official +// getAuthConfigKey special-cases using the full index address of the official // index as the AuthConfig key, and uses the (host)name[:port] for private indexes. -func GetAuthConfigKey(index *registry.IndexInfo) string { +func getAuthConfigKey(index *registry.IndexInfo) string { if index.Official { return IndexServer } diff --git a/daemon/pkg/registry/search_endpoint_v1.go b/daemon/pkg/registry/search_endpoint_v1.go index d6a6630125..e1053638ee 100644 --- a/daemon/pkg/registry/search_endpoint_v1.go +++ b/daemon/pkg/registry/search_endpoint_v1.go @@ -39,7 +39,7 @@ func newV1Endpoint(ctx context.Context, index *registry.IndexInfo, headers http. return nil, err } - endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers) + endpoint, err := newV1EndpointFromStr(getAuthConfigKey(index), tlsConfig, headers) if err != nil { return nil, err } From 17d0ac56f3173e95fc83b5139c2da3ad20aad4da Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 1 Aug 2025 15:28:19 +0200 Subject: [PATCH 2/5] daemon/pkg/registry: remove session; make searchRepositories a func The `session` struct was just bundling a http.Client with a v1Endpoint. It was never a long-lived service; every use initialized the session, only to call the `searchRepositories` method on it. Dismantle it, and make it a regular function that gets a http.Client and a v1Endpoint passed as argument. Signed-off-by: Sebastiaan van Stijn --- daemon/pkg/registry/search.go | 2 +- daemon/pkg/registry/search_session.go | 19 +++---------------- daemon/pkg/registry/search_test.go | 14 ++++++-------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/daemon/pkg/registry/search.go b/daemon/pkg/registry/search.go index fd8a7c7542..429085dc95 100644 --- a/daemon/pkg/registry/search.go +++ b/daemon/pkg/registry/search.go @@ -129,7 +129,7 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, } } - return newSession(client, endpoint).searchRepositories(ctx, remoteName, limit) + return searchRepositories(ctx, client, endpoint, remoteName, limit) } // splitReposSearchTerm breaks a search term into an index name and remote name diff --git a/daemon/pkg/registry/search_session.go b/daemon/pkg/registry/search_session.go index 51d3e990ab..cfdaa193e7 100644 --- a/daemon/pkg/registry/search_session.go +++ b/daemon/pkg/registry/search_session.go @@ -19,12 +19,6 @@ import ( "github.com/pkg/errors" ) -// A session is used to communicate with a V1 registry -type session struct { - indexEndpoint *v1Endpoint - client *http.Client -} - type authTransport struct { base http.RoundTripper authConfig *registry.AuthConfig @@ -202,25 +196,18 @@ func authorizeClient(ctx context.Context, client *http.Client, authConfig *regis return nil } -func newSession(client *http.Client, endpoint *v1Endpoint) *session { - return &session{ - client: client, - indexEndpoint: endpoint, - } -} - // defaultSearchLimit is the default value for maximum number of returned search results. const defaultSearchLimit = 25 // searchRepositories performs a search against the remote repository -func (r *session) searchRepositories(ctx context.Context, term string, limit int) (*registry.SearchResults, error) { +func searchRepositories(ctx context.Context, client *http.Client, ep *v1Endpoint, term string, limit int) (*registry.SearchResults, error) { if limit == 0 { limit = defaultSearchLimit } if limit < 1 || limit > 100 { return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit) } - u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) + u := ep.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) log.G(ctx).WithField("url", u).Debug("searchRepositories") req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) @@ -229,7 +216,7 @@ func (r *session) searchRepositories(ctx context.Context, term string, limit int } // Have the AuthTransport send authentication, when logged in. req.Header.Set("X-Docker-Token", "true") - res, err := r.client.Do(req) + res, err := client.Do(req) if err != nil { return nil, systemErr{err} } diff --git a/daemon/pkg/registry/search_test.go b/daemon/pkg/registry/search_test.go index 7af875767f..476d57ae4d 100644 --- a/daemon/pkg/registry/search_test.go +++ b/daemon/pkg/registry/search_test.go @@ -15,13 +15,13 @@ import ( "gotest.tools/v3/assert" ) -func spawnTestRegistrySession(t *testing.T) *session { +func spawnTestRegistrySession(t *testing.T) (*http.Client, *v1Endpoint) { t.Helper() - authConfig := ®istry.AuthConfig{} endpoint, err := newV1Endpoint(context.Background(), makeIndex("/v1/"), nil) if err != nil { t.Fatal(err) } + authConfig := ®istry.AuthConfig{} userAgent := "docker test client" var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log} tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...) @@ -30,8 +30,6 @@ func spawnTestRegistrySession(t *testing.T) *session { if err := authorizeClient(context.Background(), client, authConfig, endpoint); err != nil { t.Fatal(err) } - r := newSession(client, endpoint) - // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true` // header while authenticating, in order to retrieve a token that can be later used to // perform authenticated actions. @@ -42,8 +40,8 @@ func spawnTestRegistrySession(t *testing.T) *session { // Because we know that the client's transport is an `*authTransport` we simply cast it, // in order to set the internal cached token to the fake token, and thus send that fake token // upon every subsequent requests. - r.client.Transport.(*authTransport).token = []string{"fake-token"} - return r + client.Transport.(*authTransport).token = []string{"fake-token"} + return client, endpoint } type debugTransport struct { @@ -70,8 +68,8 @@ func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { } func TestSearchRepositories(t *testing.T) { - r := spawnTestRegistrySession(t) - results, err := r.searchRepositories(context.Background(), "fakequery", 25) + client, ep := spawnTestRegistrySession(t) + results, err := searchRepositories(context.Background(), client, ep, "fakequery", 25) if err != nil { t.Fatal(err) } From 6a7f0008a33e68c31c32f521b292d04d43a62925 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 1 Aug 2025 15:29:58 +0200 Subject: [PATCH 3/5] daemon/pkg/registry: move searchRepositories to where it's used Signed-off-by: Sebastiaan van Stijn --- daemon/pkg/registry/search.go | 41 ++++++++++++++++++++++++++ daemon/pkg/registry/search_session.go | 42 --------------------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/daemon/pkg/registry/search.go b/daemon/pkg/registry/search.go index 429085dc95..b47825ae1e 100644 --- a/daemon/pkg/registry/search.go +++ b/daemon/pkg/registry/search.go @@ -2,7 +2,10 @@ package registry import ( "context" + "encoding/json" + "fmt" "net/http" + "net/url" "strconv" "strings" @@ -143,3 +146,41 @@ func splitReposSearchTerm(reposName string) (string, string) { } return nameParts[0], nameParts[1] } + +// defaultSearchLimit is the default value for maximum number of returned search results. +const defaultSearchLimit = 25 + +// searchRepositories performs a search against the remote repository +func searchRepositories(ctx context.Context, client *http.Client, ep *v1Endpoint, term string, limit int) (*registry.SearchResults, error) { + if limit == 0 { + limit = defaultSearchLimit + } + if limit < 1 || limit > 100 { + return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit) + } + u := ep.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) + log.G(ctx).WithField("url", u).Debug("searchRepositories") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) + if err != nil { + return nil, invalidParamWrapf(err, "error building request") + } + // Have the AuthTransport send authentication, when logged in. + req.Header.Set("X-Docker-Token", "true") + res, err := client.Do(req) + if err != nil { + return nil, systemErr{err} + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + // TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286). + // TODO(thaJeztah): handle other status-codes to return correct error-type + return nil, errUnknown{fmt.Errorf("unexpected status code %d", res.StatusCode)} + } + result := ®istry.SearchResults{} + err = json.NewDecoder(res.Body).Decode(result) + if err != nil { + return nil, systemErr{errors.Wrap(err, "error decoding registry search results")} + } + return result, nil +} diff --git a/daemon/pkg/registry/search_session.go b/daemon/pkg/registry/search_session.go index cfdaa193e7..7f465e0449 100644 --- a/daemon/pkg/registry/search_session.go +++ b/daemon/pkg/registry/search_session.go @@ -4,13 +4,9 @@ import ( // this is required for some certificates "context" _ "crypto/sha512" - "encoding/json" - "fmt" "io" "net/http" "net/http/cookiejar" - "net/url" - "strconv" "strings" "sync" @@ -195,41 +191,3 @@ func authorizeClient(ctx context.Context, client *http.Client, authConfig *regis return nil } - -// defaultSearchLimit is the default value for maximum number of returned search results. -const defaultSearchLimit = 25 - -// searchRepositories performs a search against the remote repository -func searchRepositories(ctx context.Context, client *http.Client, ep *v1Endpoint, term string, limit int) (*registry.SearchResults, error) { - if limit == 0 { - limit = defaultSearchLimit - } - if limit < 1 || limit > 100 { - return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit) - } - u := ep.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) - log.G(ctx).WithField("url", u).Debug("searchRepositories") - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) - if err != nil { - return nil, invalidParamWrapf(err, "error building request") - } - // Have the AuthTransport send authentication, when logged in. - req.Header.Set("X-Docker-Token", "true") - res, err := client.Do(req) - if err != nil { - return nil, systemErr{err} - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - // TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286). - // TODO(thaJeztah): handle other status-codes to return correct error-type - return nil, errUnknown{fmt.Errorf("unexpected status code %d", res.StatusCode)} - } - result := ®istry.SearchResults{} - err = json.NewDecoder(res.Body).Decode(result) - if err != nil { - return nil, systemErr{errors.Wrap(err, "error decoding registry search results")} - } - return result, nil -} From 5fbf680f5d3bc96120cda5a0ccd2e25a4f0dcabf Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 1 Aug 2025 15:36:41 +0200 Subject: [PATCH 4/5] daemon/pkg/registry: move newIndexInfo to search It's the only user of it. Signed-off-by: Sebastiaan van Stijn --- daemon/pkg/registry/config.go | 17 -- daemon/pkg/registry/registry_test.go | 229 --------------------------- daemon/pkg/registry/search.go | 17 ++ daemon/pkg/registry/search_test.go | 229 +++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 246 deletions(-) diff --git a/daemon/pkg/registry/config.go b/daemon/pkg/registry/config.go index e5f36904e9..f828936bb4 100644 --- a/daemon/pkg/registry/config.go +++ b/daemon/pkg/registry/config.go @@ -342,23 +342,6 @@ func validateHostPort(s string) error { return nil } -// newIndexInfo returns IndexInfo configuration from indexName -func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { - indexName = normalizeIndexName(indexName) - - // Return any configured index info, first. - if index, ok := config.IndexConfigs[indexName]; ok { - return index - } - - // Construct a non-configured index info. - return ®istry.IndexInfo{ - Name: indexName, - Mirrors: []string{}, - Secure: config.isSecureIndex(indexName), - } -} - // getAuthConfigKey special-cases using the full index address of the official // index as the AuthConfig key, and uses the (host)name[:port] for private indexes. func getAuthConfigKey(index *registry.IndexInfo) string { diff --git a/daemon/pkg/registry/registry_test.go b/daemon/pkg/registry/registry_test.go index bb2a584f3b..9ad2d78ae7 100644 --- a/daemon/pkg/registry/registry_test.go +++ b/daemon/pkg/registry/registry_test.go @@ -6,9 +6,7 @@ import ( "testing" "github.com/distribution/reference" - "github.com/moby/moby/api/types/registry" "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" ) // overrideLookupIP overrides net.LookupIP for testing. @@ -34,233 +32,6 @@ func overrideLookupIP(t *testing.T) { }) } -func TestNewIndexInfo(t *testing.T) { - overrideLookupIP(t) - - // ipv6Loopback is the CIDR for the IPv6 loopback address ("::1"); "::1/128" - ipv6Loopback := &net.IPNet{ - IP: net.IPv6loopback, - Mask: net.CIDRMask(128, 128), - } - - // ipv4Loopback is the CIDR for IPv4 loopback addresses ("127.0.0.0/8") - ipv4Loopback := &net.IPNet{ - IP: net.IPv4(127, 0, 0, 0), - Mask: net.CIDRMask(8, 32), - } - - // emptyServiceConfig is a default service-config for situations where - // no config-file is available (e.g. when used in the CLI). It won't - // have mirrors configured, but does have the default insecure registry - // CIDRs for loopback interfaces configured. - emptyServiceConfig := &serviceConfig{ - IndexConfigs: map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Mirrors: []string{}, - Secure: true, - Official: true, - }, - }, - InsecureRegistryCIDRs: []*registry.NetIPNet{ - (*registry.NetIPNet)(ipv6Loopback), - (*registry.NetIPNet)(ipv4Loopback), - }, - } - - expectedIndexInfos := map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{}, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{}, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - } - t.Run("no mirrors", func(t *testing.T) { - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(emptyServiceConfig, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) - - expectedIndexInfos = map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.255.255.255": { - Name: "127.255.255.255", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.255.255.255:5000": { - Name: "127.255.255.255:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "::1": { - Name: "::1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "[::1]:5000": { - Name: "[::1]:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - // IPv6 only has a single loopback address, so ::2 is not a loopback, - // hence not marked "insecure". - "::2": { - Name: "::2", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - // IPv6 only has a single loopback address, so ::2 is not a loopback, - // hence not marked "insecure". - "[::2]:5000": { - Name: "[::2]:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - } - t.Run("mirrors", func(t *testing.T) { - // Note that newServiceConfig calls ValidateMirror internally, which normalizes - // mirror-URLs to have a trailing slash. - config, err := newServiceConfig(ServiceOptions{ - Mirrors: []string{"http://mirror1.local", "http://mirror2.local"}, - InsecureRegistries: []string{"example.com"}, - }) - assert.NilError(t, err) - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(config, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) - - expectedIndexInfos = map[string]*registry.IndexInfo{ - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "42.42.0.1:5000": { - Name: "42.42.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "42.43.0.1:5000": { - Name: "42.43.0.1:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - } - t.Run("custom insecure", func(t *testing.T) { - config, err := newServiceConfig(ServiceOptions{ - InsecureRegistries: []string{"42.42.0.0/16"}, - }) - assert.NilError(t, err) - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(config, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) -} - func TestMirrorEndpointLookup(t *testing.T) { containsMirror := func(endpoints []APIEndpoint) bool { for _, pe := range endpoints { diff --git a/daemon/pkg/registry/search.go b/daemon/pkg/registry/search.go index b47825ae1e..39cd5f7aed 100644 --- a/daemon/pkg/registry/search.go +++ b/daemon/pkg/registry/search.go @@ -147,6 +147,23 @@ func splitReposSearchTerm(reposName string) (string, string) { return nameParts[0], nameParts[1] } +// newIndexInfo returns IndexInfo configuration from indexName +func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { + indexName = normalizeIndexName(indexName) + + // Return any configured index info, first. + if index, ok := config.IndexConfigs[indexName]; ok { + return index + } + + // Construct a non-configured index info. + return ®istry.IndexInfo{ + Name: indexName, + Mirrors: []string{}, + Secure: config.isSecureIndex(indexName), + } +} + // defaultSearchLimit is the default value for maximum number of returned search results. const defaultSearchLimit = 25 diff --git a/daemon/pkg/registry/search_test.go b/daemon/pkg/registry/search_test.go index 476d57ae4d..bbd8da6f0d 100644 --- a/daemon/pkg/registry/search_test.go +++ b/daemon/pkg/registry/search_test.go @@ -3,6 +3,7 @@ package registry import ( "context" "encoding/json" + "net" "net/http" "net/http/httptest" "net/http/httputil" @@ -13,6 +14,7 @@ import ( "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/registry" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) func spawnTestRegistrySession(t *testing.T) (*http.Client, *v1Endpoint) { @@ -414,3 +416,230 @@ func TestSearch(t *testing.T) { }) } } + +func TestNewIndexInfo(t *testing.T) { + overrideLookupIP(t) + + // ipv6Loopback is the CIDR for the IPv6 loopback address ("::1"); "::1/128" + ipv6Loopback := &net.IPNet{ + IP: net.IPv6loopback, + Mask: net.CIDRMask(128, 128), + } + + // ipv4Loopback is the CIDR for IPv4 loopback addresses ("127.0.0.0/8") + ipv4Loopback := &net.IPNet{ + IP: net.IPv4(127, 0, 0, 0), + Mask: net.CIDRMask(8, 32), + } + + // emptyServiceConfig is a default service-config for situations where + // no config-file is available (e.g. when used in the CLI). It won't + // have mirrors configured, but does have the default insecure registry + // CIDRs for loopback interfaces configured. + emptyServiceConfig := &serviceConfig{ + IndexConfigs: map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Mirrors: []string{}, + Secure: true, + Official: true, + }, + }, + InsecureRegistryCIDRs: []*registry.NetIPNet{ + (*registry.NetIPNet)(ipv6Loopback), + (*registry.NetIPNet)(ipv4Loopback), + }, + } + + expectedIndexInfos := map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{}, + }, + "index." + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{}, + }, + "example.com": { + Name: "example.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + } + t.Run("no mirrors", func(t *testing.T) { + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(emptyServiceConfig, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) + + expectedIndexInfos = map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, + }, + "index." + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, + }, + "example.com": { + Name: "example.com", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "example.com:5000": { + Name: "example.com:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "127.0.0.1": { + Name: "127.0.0.1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.255.255.255": { + Name: "127.255.255.255", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.255.255.255:5000": { + Name: "127.255.255.255:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "::1": { + Name: "::1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "[::1]:5000": { + Name: "[::1]:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "::2": { + Name: "::2", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "[::2]:5000": { + Name: "[::2]:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "other.com": { + Name: "other.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + } + t.Run("mirrors", func(t *testing.T) { + // Note that newServiceConfig calls ValidateMirror internally, which normalizes + // mirror-URLs to have a trailing slash. + config, err := newServiceConfig(ServiceOptions{ + Mirrors: []string{"http://mirror1.local", "http://mirror2.local"}, + InsecureRegistries: []string{"example.com"}, + }) + assert.NilError(t, err) + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(config, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) + + expectedIndexInfos = map[string]*registry.IndexInfo{ + "example.com": { + Name: "example.com", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "example.com:5000": { + Name: "example.com:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1": { + Name: "127.0.0.1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "42.42.0.1:5000": { + Name: "42.42.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "42.43.0.1:5000": { + Name: "42.43.0.1:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "other.com": { + Name: "other.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + } + t.Run("custom insecure", func(t *testing.T) { + config, err := newServiceConfig(ServiceOptions{ + InsecureRegistries: []string{"42.42.0.0/16"}, + }) + assert.NilError(t, err) + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(config, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) +} From a43198845b1e5a31c964f0867416e78838786ee6 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 1 Aug 2025 16:47:47 +0200 Subject: [PATCH 5/5] daemon/pkg/registry: un-export ResolveAuthConfig It's now only used to back the `Service.ResolveAuthConfig` method, and not used outside of the package currently. Signed-off-by: Sebastiaan van Stijn --- daemon/pkg/registry/auth.go | 4 ++-- daemon/pkg/registry/auth_test.go | 8 ++++---- daemon/pkg/registry/service.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/daemon/pkg/registry/auth.go b/daemon/pkg/registry/auth.go index 90cc4c8efd..6296fc3cf8 100644 --- a/daemon/pkg/registry/auth.go +++ b/daemon/pkg/registry/auth.go @@ -145,8 +145,8 @@ func ConvertToHostname(maybeURL string) string { return stripped } -// ResolveAuthConfig matches an auth configuration to a server address or a URL -func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig { +// resolveAuthConfig matches an auth configuration to a server address or a URL +func resolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig { configKey := getAuthConfigKey(index) // First try the happy case if c, found := authConfigs[configKey]; found || index.Official { diff --git a/daemon/pkg/registry/auth_test.go b/daemon/pkg/registry/auth_test.go index 39a29d9080..461f1f140d 100644 --- a/daemon/pkg/registry/auth_test.go +++ b/daemon/pkg/registry/auth_test.go @@ -31,10 +31,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { Official: false, } - resolved := ResolveAuthConfig(authConfigs, officialIndex) + resolved := resolveAuthConfig(authConfigs, officialIndex) assert.Equal(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") - resolved = ResolveAuthConfig(authConfigs, privateIndex) + resolved = resolveAuthConfig(authConfigs, privateIndex) assert.Check(t, resolved != indexConfig, "Expected ResolveAuthConfig to not return IndexServer") } @@ -92,12 +92,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { } for _, reg := range registries { authConfigs[reg] = configured - resolved := ResolveAuthConfig(authConfigs, index) + resolved := resolveAuthConfig(authConfigs, index) if resolved.Username != configured.Username || resolved.Password != configured.Password { t.Errorf("%s -> %v != %v\n", reg, resolved, configured) } delete(authConfigs, reg) - resolved = ResolveAuthConfig(authConfigs, index) + resolved = resolveAuthConfig(authConfigs, index) if resolved.Username == configured.Username || resolved.Password == configured.Password { t.Errorf("%s -> %v == %v\n", reg, resolved, configured) } diff --git a/daemon/pkg/registry/service.go b/daemon/pkg/registry/service.go index 8a298cc0da..d782446509 100644 --- a/daemon/pkg/registry/service.go +++ b/daemon/pkg/registry/service.go @@ -122,7 +122,7 @@ func (s *Service) ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, if !ok { registryInfo = ®istry.IndexInfo{Name: indexName} } - return ResolveAuthConfig(authConfigs, registryInfo) + return resolveAuthConfig(authConfigs, registryInfo) } // APIEndpoint represents a remote API endpoint