瀏覽代碼

Merge pull request #46360 from thaJeztah/search_split_search_service_step1

registry: split search-related code to separate files
Sebastiaan van Stijn 1 年之前
父節點
當前提交
97206b7cba

+ 2 - 2
registry/auth.go

@@ -149,8 +149,8 @@ func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *regist
 
 	// Maybe they have a legacy config file, we will iterate the keys converting
 	// them to the new format and testing
-	for registry, ac := range authConfigs {
-		if configKey == ConvertToHostname(registry) {
+	for registryURL, ac := range authConfigs {
+		if configKey == ConvertToHostname(registryURL) {
 			return ac
 		}
 	}

+ 0 - 11
registry/config.go

@@ -435,14 +435,3 @@ func newRepositoryInfo(config *serviceConfig, name reference.Named) (*Repository
 func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
 	return newRepositoryInfo(emptyServiceConfig, reposName)
 }
-
-// ParseSearchIndexInfo will use repository name to get back an indexInfo.
-//
-// TODO(thaJeztah) this function is only used by the CLI, and used to get
-// information of the registry (to provide credentials if needed). We should
-// move this function (or equivalent) to the CLI, as it's doing too much just
-// for that.
-func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
-	indexName, _ := splitReposSearchTerm(reposName)
-	return newIndexInfo(emptyServiceConfig, indexName)
-}

+ 0 - 92
registry/endpoint_test.go

@@ -1,92 +0,0 @@
-package registry // import "github.com/docker/docker/registry"
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"net/url"
-	"testing"
-
-	"gotest.tools/v3/assert"
-	is "gotest.tools/v3/assert/cmp"
-)
-
-func TestV1EndpointParse(t *testing.T) {
-	tests := []struct {
-		address     string
-		expected    string
-		expectedErr string
-	}{
-		{
-			address:  IndexServer,
-			expected: IndexServer,
-		},
-		{
-			address:  "https://0.0.0.0:5000/v1/",
-			expected: "https://0.0.0.0:5000/v1/",
-		},
-		{
-			address:  "https://0.0.0.0:5000",
-			expected: "https://0.0.0.0:5000/v1/",
-		},
-		{
-			address:  "0.0.0.0:5000",
-			expected: "https://0.0.0.0:5000/v1/",
-		},
-		{
-			address:  "https://0.0.0.0:5000/nonversion/",
-			expected: "https://0.0.0.0:5000/nonversion/v1/",
-		},
-		{
-			address:  "https://0.0.0.0:5000/v0/",
-			expected: "https://0.0.0.0:5000/v0/v1/",
-		},
-		{
-			address:     "https://0.0.0.0:5000/v2/",
-			expectedErr: "search is not supported on v2 endpoints: https://0.0.0.0:5000/v2/",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.address, func(t *testing.T) {
-			ep, err := newV1EndpointFromStr(tc.address, nil, nil)
-			if tc.expectedErr != "" {
-				assert.Check(t, is.Error(err, tc.expectedErr))
-				assert.Check(t, is.Nil(ep))
-			} else {
-				assert.NilError(t, err)
-				assert.Check(t, is.Equal(ep.String(), tc.expected))
-			}
-		})
-	}
-}
-
-// Ensure that a registry endpoint that responds with a 401 only is determined
-// to be a valid v1 registry endpoint
-func TestValidateEndpoint(t *testing.T) {
-	requireBasicAuthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Add("WWW-Authenticate", `Basic realm="localhost"`)
-		w.WriteHeader(http.StatusUnauthorized)
-	})
-
-	// Make a test server which should validate as a v1 server.
-	testServer := httptest.NewServer(requireBasicAuthHandler)
-	defer testServer.Close()
-
-	testServerURL, err := url.Parse(testServer.URL)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	testEndpoint := v1Endpoint{
-		URL:    testServerURL,
-		client: httpClient(newTransport(nil)),
-	}
-
-	if err = validateEndpoint(&testEndpoint); err != nil {
-		t.Fatal(err)
-	}
-
-	if testEndpoint.URL.Scheme != "http" {
-		t.Fatalf("expecting to validate endpoint as http, got url %s", testEndpoint.String())
-	}
-}

+ 0 - 45
registry/registry.go

@@ -113,51 +113,6 @@ func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModif
 	return modifiers
 }
 
-// httpClient returns an HTTP client structure which uses the given transport
-// and contains the necessary headers for redirected requests
-func httpClient(transport http.RoundTripper) *http.Client {
-	return &http.Client{
-		Transport:     transport,
-		CheckRedirect: addRequiredHeadersToRedirectedRequests,
-	}
-}
-
-func trustedLocation(req *http.Request) bool {
-	var (
-		trusteds = []string{"docker.com", "docker.io"}
-		hostname = strings.SplitN(req.Host, ":", 2)[0]
-	)
-	if req.URL.Scheme != "https" {
-		return false
-	}
-
-	for _, trusted := range trusteds {
-		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
-			return true
-		}
-	}
-	return false
-}
-
-// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
-// for redirected requests
-func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
-	if len(via) != 0 && via[0] != nil {
-		if trustedLocation(req) && trustedLocation(via[0]) {
-			req.Header = via[0].Header
-			return nil
-		}
-		for k, v := range via[0].Header {
-			if k != "Authorization" {
-				for _, vv := range v {
-					req.Header.Add(k, vv)
-				}
-			}
-		}
-	}
-	return nil
-}
-
 // newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
 // default TLS configuration.
 func newTransport(tlsConfig *tls.Config) *http.Transport {

+ 0 - 226
registry/registry_test.go

@@ -1,142 +1,16 @@
 package registry // import "github.com/docker/docker/registry"
 
 import (
-	"net/http"
-	"net/http/httputil"
 	"os"
-	"strings"
 	"testing"
 
 	"github.com/docker/distribution/reference"
-	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types/registry"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 	"gotest.tools/v3/skip"
 )
 
-func spawnTestRegistrySession(t *testing.T) *session {
-	authConfig := &registry.AuthConfig{}
-	endpoint, err := newV1Endpoint(makeIndex("/v1/"), nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	userAgent := "docker test client"
-	var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log}
-	tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
-	client := httpClient(tr)
-
-	if err := authorizeClient(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.
-	//
-	// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
-	// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
-	//
-	// 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
-}
-
-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)
-		if err != nil {
-			t.Fatal(err)
-		}
-		regInfo, err := ep.ping()
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage)
-	}
-
-	testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
-	testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)")
-	testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
-}
-
-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)
-		if err != nil {
-			t.Fatal(err)
-		}
-		return endpoint
-	}
-
-	assertInsecureIndex := func(index *registry.IndexInfo) {
-		index.Secure = true
-		_, 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)
-		assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index")
-		index.Secure = false
-	}
-
-	index := &registry.IndexInfo{}
-	index.Name = makeURL("/v1/")
-	endpoint := expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
-	assertInsecureIndex(index)
-
-	index.Name = makeURL("")
-	endpoint = expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
-	assertInsecureIndex(index)
-
-	httpURL := makeURL("")
-	index.Name = strings.SplitN(httpURL, "://", 2)[1]
-	endpoint = expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
-	assertInsecureIndex(index)
-
-	index.Name = makeHTTPSURL("/v1/")
-	endpoint = expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
-	assertSecureIndex(index)
-
-	index.Name = makeHTTPSURL("")
-	endpoint = expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
-	assertSecureIndex(index)
-
-	httpsURL := makeHTTPSURL("")
-	index.Name = strings.SplitN(httpsURL, "://", 2)[1]
-	endpoint = expandEndpoint(index)
-	assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
-	assertSecureIndex(index)
-
-	badEndpoints := []string{
-		"http://127.0.0.1/v1/",
-		"https://127.0.0.1/v1/",
-		"http://127.0.0.1",
-		"https://127.0.0.1",
-		"127.0.0.1",
-	}
-	for _, address := range badEndpoints {
-		index.Name = address
-		_, err := newV1Endpoint(index, nil)
-		assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address)
-	}
-}
-
 func TestParseRepositoryInfo(t *testing.T) {
 	type staticRepositoryInfo struct {
 		Index         *registry.IndexInfo
@@ -543,83 +417,6 @@ func TestMirrorEndpointLookup(t *testing.T) {
 	}
 }
 
-func TestSearchRepositories(t *testing.T) {
-	r := spawnTestRegistrySession(t)
-	results, err := r.searchRepositories("fakequery", 25)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if results == nil {
-		t.Fatal("Expected non-nil SearchResults object")
-	}
-	assert.Equal(t, results.NumResults, 1, "Expected 1 search results")
-	assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query")
-	assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
-}
-
-func TestTrustedLocation(t *testing.T) {
-	for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} {
-		req, _ := http.NewRequest(http.MethodGet, url, nil)
-		assert.Check(t, !trustedLocation(req))
-	}
-
-	for _, url := range []string{"https://docker.io", "https://test.docker.com:80"} {
-		req, _ := http.NewRequest(http.MethodGet, url, nil)
-		assert.Check(t, trustedLocation(req))
-	}
-}
-
-func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
-	for _, urls := range [][]string{
-		{"http://docker.io", "https://docker.com"},
-		{"https://foo.docker.io:7777", "http://bar.docker.com"},
-		{"https://foo.docker.io", "https://example.com"},
-	} {
-		reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], nil)
-		reqFrom.Header.Add("Content-Type", "application/json")
-		reqFrom.Header.Add("Authorization", "super_secret")
-		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
-
-		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
-
-		if len(reqTo.Header) != 1 {
-			t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
-		}
-
-		if reqTo.Header.Get("Content-Type") != "application/json" {
-			t.Fatal("'Content-Type' should be 'application/json'")
-		}
-
-		if reqTo.Header.Get("Authorization") != "" {
-			t.Fatal("'Authorization' should be empty")
-		}
-	}
-
-	for _, urls := range [][]string{
-		{"https://docker.io", "https://docker.com"},
-		{"https://foo.docker.io:7777", "https://bar.docker.com"},
-	} {
-		reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], nil)
-		reqFrom.Header.Add("Content-Type", "application/json")
-		reqFrom.Header.Add("Authorization", "super_secret")
-		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
-
-		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
-
-		if len(reqTo.Header) != 2 {
-			t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
-		}
-
-		if reqTo.Header.Get("Content-Type") != "application/json" {
-			t.Fatal("'Content-Type' should be 'application/json'")
-		}
-
-		if reqTo.Header.Get("Authorization") != "super_secret" {
-			t.Fatal("'Authorization' should be 'super_secret'")
-		}
-	}
-}
-
 func TestAllowNondistributableArtifacts(t *testing.T) {
 	tests := []struct {
 		addr       string
@@ -707,26 +504,3 @@ func TestIsSecureIndex(t *testing.T) {
 		}
 	}
 }
-
-type debugTransport struct {
-	http.RoundTripper
-	log func(...interface{})
-}
-
-func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-	dump, err := httputil.DumpRequestOut(req, false)
-	if err != nil {
-		tr.log("could not dump request")
-	}
-	tr.log(string(dump))
-	resp, err := tr.RoundTripper.RoundTrip(req)
-	if err != nil {
-		return nil, err
-	}
-	dump, err = httputil.DumpResponse(resp, false)
-	if err != nil {
-		tr.log("could not dump response")
-	}
-	tr.log(string(dump))
-	return resp, err
-}

+ 26 - 4
registry/search.go

@@ -1,4 +1,4 @@
-package registry // import "github.com/docker/docker/registry"
+package registry
 
 import (
 	"context"
@@ -6,12 +6,11 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/containerd/containerd/log"
+	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
-
-	"github.com/containerd/containerd/log"
-	"github.com/docker/distribution/registry/client/auth"
 	"github.com/pkg/errors"
 )
 
@@ -139,3 +138,26 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int,
 
 	return newSession(client, endpoint).searchRepositories(remoteName, limit)
 }
+
+// splitReposSearchTerm breaks a search term into an index name and remote name
+func splitReposSearchTerm(reposName string) (string, string) {
+	nameParts := strings.SplitN(reposName, "/", 2)
+	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
+		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
+		// This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
+		// use the default Docker Hub registry (docker.io)
+		return IndexName, reposName
+	}
+	return nameParts[0], nameParts[1]
+}
+
+// ParseSearchIndexInfo will use repository name to get back an indexInfo.
+//
+// TODO(thaJeztah) this function is only used by the CLI, and used to get
+// information of the registry (to provide credentials if needed). We should
+// move this function (or equivalent) to the CLI, as it's doing too much just
+// for that.
+func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
+	indexName, _ := splitReposSearchTerm(reposName)
+	return newIndexInfo(emptyServiceConfig, indexName)
+}

+ 45 - 0
registry/endpoint_v1.go → registry/search_endpoint_v1.go

@@ -175,3 +175,48 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
 	log.G(context.TODO()).Debugf("v1PingResult.Standalone: %t", info.Standalone)
 	return info, nil
 }
+
+// httpClient returns an HTTP client structure which uses the given transport
+// and contains the necessary headers for redirected requests
+func httpClient(transport http.RoundTripper) *http.Client {
+	return &http.Client{
+		Transport:     transport,
+		CheckRedirect: addRequiredHeadersToRedirectedRequests,
+	}
+}
+
+func trustedLocation(req *http.Request) bool {
+	var (
+		trusteds = []string{"docker.com", "docker.io"}
+		hostname = strings.SplitN(req.Host, ":", 2)[0]
+	)
+	if req.URL.Scheme != "https" {
+		return false
+	}
+
+	for _, trusted := range trusteds {
+		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
+			return true
+		}
+	}
+	return false
+}
+
+// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
+// for redirected requests
+func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
+	if len(via) != 0 && via[0] != nil {
+		if trustedLocation(req) && trustedLocation(via[0]) {
+			req.Header = via[0].Header
+			return nil
+		}
+		for k, v := range via[0].Header {
+			if k != "Authorization" {
+				for _, vv := range v {
+					req.Header.Add(k, vv)
+				}
+			}
+		}
+	}
+	return nil
+}

+ 251 - 0
registry/search_endpoint_v1_test.go

@@ -0,0 +1,251 @@
+package registry // import "github.com/docker/docker/registry"
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/api/types/registry"
+	"gotest.tools/v3/assert"
+	is "gotest.tools/v3/assert/cmp"
+	"gotest.tools/v3/skip"
+)
+
+func TestV1EndpointPing(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)
+		if err != nil {
+			t.Fatal(err)
+		}
+		regInfo, err := ep.ping()
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage)
+	}
+
+	testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
+	testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)")
+	testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
+}
+
+func TestV1Endpoint(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)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return endpoint
+	}
+
+	assertInsecureIndex := func(index *registry.IndexInfo) {
+		index.Secure = true
+		_, 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)
+		assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index")
+		index.Secure = false
+	}
+
+	index := &registry.IndexInfo{}
+	index.Name = makeURL("/v1/")
+	endpoint := expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
+	assertInsecureIndex(index)
+
+	index.Name = makeURL("")
+	endpoint = expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
+	assertInsecureIndex(index)
+
+	httpURL := makeURL("")
+	index.Name = strings.SplitN(httpURL, "://", 2)[1]
+	endpoint = expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
+	assertInsecureIndex(index)
+
+	index.Name = makeHTTPSURL("/v1/")
+	endpoint = expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
+	assertSecureIndex(index)
+
+	index.Name = makeHTTPSURL("")
+	endpoint = expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
+	assertSecureIndex(index)
+
+	httpsURL := makeHTTPSURL("")
+	index.Name = strings.SplitN(httpsURL, "://", 2)[1]
+	endpoint = expandEndpoint(index)
+	assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
+	assertSecureIndex(index)
+
+	badEndpoints := []string{
+		"http://127.0.0.1/v1/",
+		"https://127.0.0.1/v1/",
+		"http://127.0.0.1",
+		"https://127.0.0.1",
+		"127.0.0.1",
+	}
+	for _, address := range badEndpoints {
+		index.Name = address
+		_, err := newV1Endpoint(index, nil)
+		assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address)
+	}
+}
+
+func TestV1EndpointParse(t *testing.T) {
+	tests := []struct {
+		address     string
+		expected    string
+		expectedErr string
+	}{
+		{
+			address:  IndexServer,
+			expected: IndexServer,
+		},
+		{
+			address:  "https://0.0.0.0:5000/v1/",
+			expected: "https://0.0.0.0:5000/v1/",
+		},
+		{
+			address:  "https://0.0.0.0:5000",
+			expected: "https://0.0.0.0:5000/v1/",
+		},
+		{
+			address:  "0.0.0.0:5000",
+			expected: "https://0.0.0.0:5000/v1/",
+		},
+		{
+			address:  "https://0.0.0.0:5000/nonversion/",
+			expected: "https://0.0.0.0:5000/nonversion/v1/",
+		},
+		{
+			address:  "https://0.0.0.0:5000/v0/",
+			expected: "https://0.0.0.0:5000/v0/v1/",
+		},
+		{
+			address:     "https://0.0.0.0:5000/v2/",
+			expectedErr: "search is not supported on v2 endpoints: https://0.0.0.0:5000/v2/",
+		},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.address, func(t *testing.T) {
+			ep, err := newV1EndpointFromStr(tc.address, nil, nil)
+			if tc.expectedErr != "" {
+				assert.Check(t, is.Error(err, tc.expectedErr))
+				assert.Check(t, is.Nil(ep))
+			} else {
+				assert.NilError(t, err)
+				assert.Check(t, is.Equal(ep.String(), tc.expected))
+			}
+		})
+	}
+}
+
+// Ensure that a registry endpoint that responds with a 401 only is determined
+// to be a valid v1 registry endpoint
+func TestV1EndpointValidate(t *testing.T) {
+	requireBasicAuthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("WWW-Authenticate", `Basic realm="localhost"`)
+		w.WriteHeader(http.StatusUnauthorized)
+	})
+
+	// Make a test server which should validate as a v1 server.
+	testServer := httptest.NewServer(requireBasicAuthHandler)
+	defer testServer.Close()
+
+	testServerURL, err := url.Parse(testServer.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testEndpoint := v1Endpoint{
+		URL:    testServerURL,
+		client: httpClient(newTransport(nil)),
+	}
+
+	if err = validateEndpoint(&testEndpoint); err != nil {
+		t.Fatal(err)
+	}
+
+	if testEndpoint.URL.Scheme != "http" {
+		t.Fatalf("expecting to validate endpoint as http, got url %s", testEndpoint.String())
+	}
+}
+
+func TestTrustedLocation(t *testing.T) {
+	for _, u := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} {
+		req, _ := http.NewRequest(http.MethodGet, u, nil)
+		assert.Check(t, !trustedLocation(req))
+	}
+
+	for _, u := range []string{"https://docker.io", "https://test.docker.com:80"} {
+		req, _ := http.NewRequest(http.MethodGet, u, nil)
+		assert.Check(t, trustedLocation(req))
+	}
+}
+
+func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
+	for _, urls := range [][]string{
+		{"http://docker.io", "https://docker.com"},
+		{"https://foo.docker.io:7777", "http://bar.docker.com"},
+		{"https://foo.docker.io", "https://example.com"},
+	} {
+		reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], nil)
+		reqFrom.Header.Add("Content-Type", "application/json")
+		reqFrom.Header.Add("Authorization", "super_secret")
+		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
+
+		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
+
+		if len(reqTo.Header) != 1 {
+			t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
+		}
+
+		if reqTo.Header.Get("Content-Type") != "application/json" {
+			t.Fatal("'Content-Type' should be 'application/json'")
+		}
+
+		if reqTo.Header.Get("Authorization") != "" {
+			t.Fatal("'Authorization' should be empty")
+		}
+	}
+
+	for _, urls := range [][]string{
+		{"https://docker.io", "https://docker.com"},
+		{"https://foo.docker.io:7777", "https://bar.docker.com"},
+	} {
+		reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], nil)
+		reqFrom.Header.Add("Content-Type", "application/json")
+		reqFrom.Header.Add("Authorization", "super_secret")
+		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
+
+		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
+
+		if len(reqTo.Header) != 2 {
+			t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
+		}
+
+		if reqTo.Header.Get("Content-Type") != "application/json" {
+			t.Fatal("'Content-Type' should be 'application/json'")
+		}
+
+		if reqTo.Header.Get("Authorization") != "super_secret" {
+			t.Fatal("'Authorization' should be 'super_secret'")
+		}
+	}
+}

+ 0 - 0
registry/session.go → registry/search_session.go


+ 70 - 1
registry/search_test.go

@@ -1,18 +1,87 @@
-package registry // import "github.com/docker/docker/registry"
+package registry
 
 import (
 	"context"
 	"encoding/json"
 	"net/http"
 	"net/http/httptest"
+	"net/http/httputil"
 	"testing"
 
+	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
 	"gotest.tools/v3/assert"
 )
 
+func spawnTestRegistrySession(t *testing.T) *session {
+	authConfig := &registry.AuthConfig{}
+	endpoint, err := newV1Endpoint(makeIndex("/v1/"), nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	userAgent := "docker test client"
+	var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log}
+	tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
+	client := httpClient(tr)
+
+	if err := authorizeClient(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.
+	//
+	// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
+	// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
+	//
+	// 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
+}
+
+type debugTransport struct {
+	http.RoundTripper
+	log func(...interface{})
+}
+
+func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	dump, err := httputil.DumpRequestOut(req, false)
+	if err != nil {
+		tr.log("could not dump request")
+	}
+	tr.log(string(dump))
+	resp, err := tr.RoundTripper.RoundTrip(req)
+	if err != nil {
+		return nil, err
+	}
+	dump, err = httputil.DumpResponse(resp, false)
+	if err != nil {
+		tr.log("could not dump response")
+	}
+	tr.log(string(dump))
+	return resp, err
+}
+
+func TestSearchRepositories(t *testing.T) {
+	r := spawnTestRegistrySession(t)
+	results, err := r.searchRepositories("fakequery", 25)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if results == nil {
+		t.Fatal("Expected non-nil SearchResults object")
+	}
+	assert.Equal(t, results.NumResults, 1, "Expected 1 search results")
+	assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query")
+	assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
+}
+
 func TestSearchErrors(t *testing.T) {
 	errorCases := []struct {
 		filtersArgs       filters.Args

+ 0 - 12
registry/service.go

@@ -91,18 +91,6 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use
 	return "", "", err
 }
 
-// splitReposSearchTerm breaks a search term into an index name and remote name
-func splitReposSearchTerm(reposName string) (string, string) {
-	nameParts := strings.SplitN(reposName, "/", 2)
-	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
-		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
-		// This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
-		// use the default Docker Hub registry (docker.io)
-		return IndexName, reposName
-	}
-	return nameParts[0], nameParts[1]
-}
-
 // ResolveRepository splits a repository name into its components
 // and configuration of the associated registry.
 func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {