registry/search: pass User-Agent through headers

Commit 3991faf464 moved search into the registry
package, which also made the `dockerversion` package a dependency for registry,
which brings additional (indirect) dependencies, such as `pkg/parsers/kernel`,
and `golang.org/x/sys/windows/registry`.

Client code, such as used in docker/cli may depend on the `registry` package,
but should not depend on those additional dependencies.

This patch moves setting the userAgent to the API router, and instead of
passing it as a separate argument, includes it into the "headers".

As these headers now not only contain the `X-Meta-...` headers, the variables
were renamed accordingly.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2023-03-21 14:40:33 +01:00
parent 7489b51f61
commit cb76638699
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
6 changed files with 28 additions and 25 deletions

View file

@ -42,5 +42,5 @@ type registryBackend interface {
}
type Searcher interface {
Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, metaHeaders map[string][]string) ([]registry.SearchResult, error)
Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error)
}

View file

@ -18,6 +18,7 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/builder/remotecontext"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/ioutils"
@ -392,13 +393,6 @@ func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWrite
return err
}
var headers = map[string][]string{}
for k, v := range r.Header {
if strings.HasPrefix(k, "X-Meta-") {
headers[k] = v
}
}
var limit int
if r.Form.Get("limit") != "" {
var err error
@ -415,6 +409,15 @@ func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWrite
// For a search it is not an error if no auth was given. Ignore invalid
// AuthConfig to increase compatibility with the existing API.
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
var headers = http.Header{}
for k, v := range r.Header {
k = http.CanonicalHeaderKey(k)
if strings.HasPrefix(k, "X-Meta-") {
headers[k] = v
}
}
headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx))
res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
if err != nil {
return err

View file

@ -20,7 +20,7 @@ func TestEndpointParse(t *testing.T) {
{"http://0.0.0.0:5000/v0/", "http://0.0.0.0:5000/v0/v1/"},
}
for _, td := range testData {
e, err := newV1EndpointFromStr(td.str, nil, "", nil)
e, err := newV1EndpointFromStr(td.str, nil, nil)
if err != nil {
t.Errorf("%q: %s", td.str, err)
}
@ -39,7 +39,7 @@ func TestEndpointParseInvalid(t *testing.T) {
"http://0.0.0.0:5000/v2/",
}
for _, td := range testData {
e, err := newV1EndpointFromStr(td, nil, "", nil)
e, err := newV1EndpointFromStr(td, nil, nil)
if err == nil {
t.Errorf("expected error parsing %q: parsed as %q", td, e)
}

View file

@ -35,13 +35,13 @@ type v1Endpoint struct {
// newV1Endpoint parses the given address to return a registry endpoint.
// TODO: remove. This is only used by search.
func newV1Endpoint(index *registry.IndexInfo, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
func newV1Endpoint(index *registry.IndexInfo, headers http.Header) (*v1Endpoint, error) {
tlsConfig, err := newTLSConfig(index.Name, index.Secure)
if err != nil {
return nil, err
}
endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders)
endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers)
if err != nil {
return nil, err
}
@ -100,7 +100,7 @@ func trimV1Address(address string) (string, error) {
return address, nil
}
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, headers http.Header) (*v1Endpoint, error) {
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
address = "https://" + address
}
@ -121,7 +121,7 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
return &v1Endpoint{
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
URL: uri,
client: httpClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
client: httpClient(transport.NewTransport(tr, Headers("", headers)...)),
}, nil
}

View file

@ -17,7 +17,7 @@ import (
func spawnTestRegistrySession(t *testing.T) *session {
authConfig := &registry.AuthConfig{}
endpoint, err := newV1Endpoint(makeIndex("/v1/"), "", nil)
endpoint, err := newV1Endpoint(makeIndex("/v1/"), nil)
if err != nil {
t.Fatal(err)
}
@ -48,7 +48,7 @@ func spawnTestRegistrySession(t *testing.T) *session {
func TestPingRegistryEndpoint(t *testing.T) {
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
testPing := func(index *registry.IndexInfo, expectedStandalone bool, assertMessage string) {
ep, err := newV1Endpoint(index, "", nil)
ep, err := newV1Endpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@ -69,7 +69,7 @@ func TestEndpoint(t *testing.T) {
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
// Simple wrapper to fail test if err != nil
expandEndpoint := func(index *registry.IndexInfo) *v1Endpoint {
endpoint, err := newV1Endpoint(index, "", nil)
endpoint, err := newV1Endpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@ -78,14 +78,14 @@ func TestEndpoint(t *testing.T) {
assertInsecureIndex := func(index *registry.IndexInfo) {
index.Secure = true
_, err := newV1Endpoint(index, "", nil)
_, err := newV1Endpoint(index, nil)
assert.ErrorContains(t, err, "insecure-registry", index.Name+": Expected insecure-registry error for insecure index")
index.Secure = false
}
assertSecureIndex := func(index *registry.IndexInfo) {
index.Secure = true
_, err := newV1Endpoint(index, "", nil)
_, err := newV1Endpoint(index, nil)
assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index")
index.Secure = false
}
@ -132,7 +132,7 @@ func TestEndpoint(t *testing.T) {
}
for _, address := range badEndpoints {
index.Name = address
_, err := newV1Endpoint(index, "", nil)
_, err := newV1Endpoint(index, nil)
assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address)
}
}

View file

@ -8,7 +8,6 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/errdefs"
"github.com/docker/distribution/registry/client/auth"
@ -52,7 +51,7 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
}
}
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, dockerversion.DockerUserAgent(ctx), headers)
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
if err != nil {
return nil, err
}
@ -80,7 +79,7 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
return filteredResults, nil
}
func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) {
// TODO Use ctx when searching for repositories
if hasScheme(term) {
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
@ -101,7 +100,7 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int,
remoteName = strings.TrimPrefix(remoteName, "library/")
}
endpoint, err := newV1Endpoint(index, userAgent, headers)
endpoint, err := newV1Endpoint(index, headers)
if err != nil {
return nil, err
}
@ -116,7 +115,8 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int,
},
}
modifiers := Headers(userAgent, nil)
// TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
modifiers := Headers(headers.Get("User-Agent"), nil)
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
if err != nil {
return nil, err