Kaynağa Gözat

Merge pull request #45086 from corhere/search-in-registry-service

Move filtered registry search out of the image service
Bjorn Neergaard 2 yıl önce
ebeveyn
işleme
1c84f63a40

+ 4 - 1
api/server/router/image/backend.go

@@ -39,5 +39,8 @@ type importExportBackend interface {
 type registryBackend interface {
 	PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
 	PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
-	SearchRegistryForImages(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
+}
+
+type Searcher interface {
+	Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, metaHeaders map[string][]string) ([]registry.SearchResult, error)
 }

+ 3 - 1
api/server/router/image/image.go

@@ -10,6 +10,7 @@ import (
 // imageRouter is a router to talk with the image controller
 type imageRouter struct {
 	backend          Backend
+	searcher         Searcher
 	referenceBackend reference.Store
 	imageStore       image.Store
 	layerStore       layer.Store
@@ -17,9 +18,10 @@ type imageRouter struct {
 }
 
 // NewRouter initializes a new image router
-func NewRouter(backend Backend, referenceBackend reference.Store, imageStore image.Store, layerStore layer.Store) router.Router {
+func NewRouter(backend Backend, searcher Searcher, referenceBackend reference.Store, imageStore image.Store, layerStore layer.Store) router.Router {
 	ir := &imageRouter{
 		backend:          backend,
+		searcher:         searcher,
 		referenceBackend: referenceBackend,
 		imageStore:       imageStore,
 		layerStore:       layerStore,

+ 2 - 2
api/server/router/image/image_routes.go

@@ -415,11 +415,11 @@ 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))
-	query, err := ir.backend.SearchRegistryForImages(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
+	res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
 	if err != nil {
 		return err
 	}
-	return httputils.WriteJSON(w, http.StatusOK, query.Results)
+	return httputils.WriteJSON(w, http.StatusOK, res)
 }
 
 func (ir *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

+ 1 - 0
cmd/dockerd/daemon.go

@@ -511,6 +511,7 @@ func initRouter(opts routerOptions) {
 		container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified),
 		image.NewRouter(
 			opts.daemon.ImageService(),
+			opts.daemon.RegistryService(),
 			opts.daemon.ReferenceStore,
 			opts.daemon.ImageService().DistributionServices().ImageStore,
 			opts.daemon.ImageService().DistributionServices().LayerStore,

+ 0 - 19
daemon/containerd/image_search.go

@@ -1,19 +0,0 @@
-package containerd
-
-import (
-	"context"
-	"errors"
-
-	"github.com/docker/docker/api/types/filters"
-	"github.com/docker/docker/api/types/registry"
-	"github.com/docker/docker/errdefs"
-)
-
-// SearchRegistryForImages queries the registry for images matching
-// term. authConfig is used to login.
-//
-// TODO: this could be implemented in a registry service instead of the image
-// service.
-func (i *ImageService) SearchRegistryForImages(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error) {
-	return nil, errdefs.NotImplemented(errors.New("not implemented"))
-}

+ 1 - 1
daemon/containerd/resolver.go

@@ -23,7 +23,7 @@ func (i *ImageService) newResolverFromAuthConfig(authConfig *registrytypes.AuthC
 	}), tracker
 }
 
-func hostsWrapper(hostsFn docker.RegistryHosts, authConfig *registrytypes.AuthConfig, regService registry.Service) docker.RegistryHosts {
+func hostsWrapper(hostsFn docker.RegistryHosts, authConfig *registrytypes.AuthConfig, regService RegistryConfigProvider) docker.RegistryHosts {
 	return func(n string) ([]docker.RegistryHost, error) {
 		hosts, err := hostsFn(n)
 		if err != nil {

+ 6 - 3
daemon/containerd/service.go

@@ -14,7 +14,6 @@ import (
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
-	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/image-spec/identity"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -27,15 +26,19 @@ type ImageService struct {
 	containers      container.Store
 	snapshotter     string
 	registryHosts   RegistryHostsProvider
-	registryService registry.Service
+	registryService RegistryConfigProvider
 }
 
 type RegistryHostsProvider interface {
 	RegistryHosts() docker.RegistryHosts
 }
 
+type RegistryConfigProvider interface {
+	IsInsecureRegistry(host string) bool
+}
+
 // NewService creates a new ImageService.
-func NewService(c *containerd.Client, containers container.Store, snapshotter string, hostsProvider RegistryHostsProvider, registry registry.Service) *ImageService {
+func NewService(c *containerd.Client, containers container.Store, snapshotter string, hostsProvider RegistryHostsProvider, registry RegistryConfigProvider) *ImageService {
 	return &ImageService{
 		client:          c,
 		containers:      containers,

+ 6 - 1
daemon/daemon.go

@@ -82,7 +82,7 @@ type Daemon struct {
 	configStore           *config.Config
 	statsCollector        *stats.Collector
 	defaultLogConfig      containertypes.LogConfig
-	registryService       registry.Service
+	registryService       *registry.Service
 	EventsService         *events.Events
 	netController         *libnetwork.Controller
 	volumes               *volumesservice.VolumesService
@@ -1459,6 +1459,11 @@ func (daemon *Daemon) ImageService() ImageService {
 	return daemon.imageService
 }
 
+// RegistryService returns the Daemon's RegistryService
+func (daemon *Daemon) RegistryService() *registry.Service {
+	return daemon.registryService
+}
+
 // BuilderBackend returns the backend used by builder
 func (daemon *Daemon) BuilderBackend() builder.Backend {
 	return struct {

+ 0 - 1
daemon/image_service.go

@@ -73,7 +73,6 @@ type ImageService interface {
 	// Other
 
 	GetRepository(ctx context.Context, ref reference.Named, authConfig *registry.AuthConfig) (distribution.Repository, error)
-	SearchRegistryForImages(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) (*registry.SearchResults, error)
 	DistributionServices() images.DistributionServices
 	Children(id image.ID) []image.ID
 	Cleanup() error

+ 0 - 85
daemon/images/image_search.go

@@ -1,85 +0,0 @@
-package images // import "github.com/docker/docker/daemon/images"
-
-import (
-	"context"
-	"strconv"
-
-	"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/pkg/errors"
-)
-
-var acceptedSearchFilterTags = map[string]bool{
-	"is-automated": true,
-	"is-official":  true,
-	"stars":        true,
-}
-
-// SearchRegistryForImages queries the registry for images matching
-// term. authConfig is used to login.
-//
-// TODO: this could be implemented in a registry service instead of the image
-// service.
-func (i *ImageService) SearchRegistryForImages(ctx context.Context, searchFilters filters.Args, term string, limit int,
-	authConfig *registry.AuthConfig,
-	headers map[string][]string) (*registry.SearchResults, error) {
-	if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
-		return nil, err
-	}
-
-	isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
-	if err != nil {
-		return nil, err
-	}
-	isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
-	if err != nil {
-		return nil, err
-	}
-
-	hasStarFilter := 0
-	if searchFilters.Contains("stars") {
-		hasStars := searchFilters.Get("stars")
-		for _, hasStar := range hasStars {
-			iHasStar, err := strconv.Atoi(hasStar)
-			if err != nil {
-				return nil, errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar))
-			}
-			if iHasStar > hasStarFilter {
-				hasStarFilter = iHasStar
-			}
-		}
-	}
-
-	unfilteredResult, err := i.registryService.Search(ctx, term, limit, authConfig, dockerversion.DockerUserAgent(ctx), headers)
-	if err != nil {
-		return nil, err
-	}
-
-	filteredResults := []registry.SearchResult{}
-	for _, result := range unfilteredResult.Results {
-		if searchFilters.Contains("is-automated") {
-			if isAutomated != result.IsAutomated {
-				continue
-			}
-		}
-		if searchFilters.Contains("is-official") {
-			if isOfficial != result.IsOfficial {
-				continue
-			}
-		}
-		if searchFilters.Contains("stars") {
-			if result.StarCount < hasStarFilter {
-				continue
-			}
-		}
-		filteredResults = append(filteredResults, result)
-	}
-
-	return &registry.SearchResults{
-		Query:      unfilteredResult.Query,
-		NumResults: len(filteredResults),
-		Results:    filteredResults,
-	}, nil
-}

+ 3 - 3
daemon/images/service.go

@@ -8,12 +8,12 @@ import (
 	"github.com/containerd/containerd/leases"
 	"github.com/docker/docker/container"
 	daemonevents "github.com/docker/docker/daemon/events"
+	"github.com/docker/docker/distribution"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	dockerreference "github.com/docker/docker/reference"
-	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/pkg/errors"
 )
@@ -39,7 +39,7 @@ type ImageServiceConfig struct {
 	MaxConcurrentUploads      int
 	MaxDownloadAttempts       int
 	ReferenceStore            dockerreference.Store
-	RegistryService           registry.Service
+	RegistryService           distribution.RegistryResolver
 	ContentStore              content.Store
 	Leases                    leases.Manager
 	ContentNamespace          string
@@ -73,7 +73,7 @@ type ImageService struct {
 	layerStore                layer.Store
 	pruneRunning              int32
 	referenceStore            dockerreference.Store
-	registryService           registry.Service
+	registryService           distribution.RegistryResolver
 	uploadManager             *xfer.LayerUploadManager
 	leases                    leases.Manager
 	content                   content.Store

+ 9 - 1
distribution/config.go

@@ -8,6 +8,7 @@ import (
 
 	"github.com/docker/distribution"
 	"github.com/docker/distribution/manifest/schema2"
+	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
@@ -35,7 +36,7 @@ type Config struct {
 	ProgressOutput progress.Output
 	// RegistryService is the registry service to use for TLS configuration
 	// and endpoint lookup.
-	RegistryService registrypkg.Service
+	RegistryService RegistryResolver
 	// ImageEventLogger notifies events for a given image
 	ImageEventLogger func(id, name, action string)
 	// MetadataStore is the storage backend for distribution-specific
@@ -75,6 +76,13 @@ type ImagePushConfig struct {
 	UploadManager *xfer.LayerUploadManager
 }
 
+// RegistryResolver is used for TLS configuration and endpoint lookup.
+type RegistryResolver interface {
+	LookupPushEndpoints(hostname string) (endpoints []registrypkg.APIEndpoint, err error)
+	LookupPullEndpoints(hostname string) (endpoints []registrypkg.APIEndpoint, err error)
+	ResolveRepository(name reference.Named) (*registrypkg.RepositoryInfo, error)
+}
+
 // ImageConfigStore handles storing and getting image configurations
 // by digest. Allows getting an image configurations rootfs from the
 // configuration.

+ 1 - 1
registry/registry_test.go

@@ -520,7 +520,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	s := defaultService{config: cfg}
+	s := Service{config: cfg}
 
 	imageName, err := reference.WithName(IndexName + "/test/image")
 	if err != nil {

+ 139 - 0
registry/search.go

@@ -0,0 +1,139 @@
+package registry // import "github.com/docker/docker/registry"
+
+import (
+	"context"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"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"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+)
+
+var acceptedSearchFilterTags = map[string]bool{
+	"is-automated": true,
+	"is-official":  true,
+	"stars":        true,
+}
+
+// Search queries the public registry for repositories matching the specified
+// search term and filters.
+func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) {
+	if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
+		return nil, err
+	}
+
+	isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
+	if err != nil {
+		return nil, err
+	}
+	isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
+	if err != nil {
+		return nil, err
+	}
+
+	hasStarFilter := 0
+	if searchFilters.Contains("stars") {
+		hasStars := searchFilters.Get("stars")
+		for _, hasStar := range hasStars {
+			iHasStar, err := strconv.Atoi(hasStar)
+			if err != nil {
+				return nil, errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar))
+			}
+			if iHasStar > hasStarFilter {
+				hasStarFilter = iHasStar
+			}
+		}
+	}
+
+	unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, dockerversion.DockerUserAgent(ctx), headers)
+	if err != nil {
+		return nil, err
+	}
+
+	filteredResults := []registry.SearchResult{}
+	for _, result := range unfilteredResult.Results {
+		if searchFilters.Contains("is-automated") {
+			if isAutomated != result.IsAutomated {
+				continue
+			}
+		}
+		if searchFilters.Contains("is-official") {
+			if isOfficial != result.IsOfficial {
+				continue
+			}
+		}
+		if searchFilters.Contains("stars") {
+			if result.StarCount < hasStarFilter {
+				continue
+			}
+		}
+		filteredResults = append(filteredResults, result)
+	}
+
+	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) {
+	// 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)
+	}
+
+	indexName, remoteName := splitReposSearchTerm(term)
+
+	// Search is a long-running operation, just lock s.config to avoid block others.
+	s.mu.RLock()
+	index, err := newIndexInfo(s.config, indexName)
+	s.mu.RUnlock()
+
+	if err != nil {
+		return nil, err
+	}
+	if index.Official {
+		// If pull "library/foo", it's stored locally under "foo"
+		remoteName = strings.TrimPrefix(remoteName, "library/")
+	}
+
+	endpoint, err := newV1Endpoint(index, userAgent, headers)
+	if err != nil {
+		return nil, err
+	}
+
+	var client *http.Client
+	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
+		creds := NewStaticCredentialStore(authConfig)
+		scopes := []auth.Scope{
+			auth.RegistryScope{
+				Name:    "catalog",
+				Actions: []string{"search"},
+			},
+		}
+
+		modifiers := Headers(userAgent, nil)
+		v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
+		if err != nil {
+			return nil, err
+		}
+		// Copy non transport http client features
+		v2Client.Timeout = endpoint.client.Timeout
+		v2Client.CheckRedirect = endpoint.client.CheckRedirect
+		v2Client.Jar = endpoint.client.Jar
+
+		logrus.Debugf("using v2 client for search to %s", endpoint.URL)
+		client = v2Client
+	} else {
+		client = endpoint.client
+		if err := authorizeClient(client, authConfig, endpoint); err != nil {
+			return nil, err
+		}
+	}
+
+	return newSession(client, endpoint).searchRepositories(remoteName, limit)
+}

+ 39 - 42
daemon/images/image_search_test.go → registry/search_test.go

@@ -1,44 +1,26 @@
-package images // import "github.com/docker/docker/daemon/images"
+package registry // import "github.com/docker/docker/registry"
 
 import (
 	"context"
-	"errors"
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
 	"testing"
 
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
-	registrypkg "github.com/docker/docker/registry"
 	"gotest.tools/v3/assert"
 )
 
-type fakeService struct {
-	registrypkg.Service
-	shouldReturnError bool
-
-	term    string
-	results []registry.SearchResult
-}
-
-func (s *fakeService) Search(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
-	if s.shouldReturnError {
-		return nil, errdefs.Unknown(errors.New("search unknown error"))
-	}
-	return &registry.SearchResults{
-		Query:      s.term,
-		NumResults: len(s.results),
-		Results:    s.results,
-	}, nil
-}
-
-func TestSearchRegistryForImagesErrors(t *testing.T) {
+func TestSearchErrors(t *testing.T) {
 	errorCases := []struct {
 		filtersArgs       filters.Args
 		shouldReturnError bool
 		expectedError     string
 	}{
 		{
-			expectedError:     "search unknown error",
+			expectedError:     "Unexpected status code 500",
 			shouldReturnError: true,
 		},
 		{
@@ -82,12 +64,20 @@ func TestSearchRegistryForImagesErrors(t *testing.T) {
 	for _, tc := range errorCases {
 		tc := tc
 		t.Run(tc.expectedError, func(t *testing.T) {
-			daemon := &ImageService{
-				registryService: &fakeService{
-					shouldReturnError: tc.shouldReturnError,
-				},
-			}
-			_, err := daemon.SearchRegistryForImages(context.Background(), tc.filtersArgs, "term", 0, nil, map[string][]string{})
+			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				if !tc.shouldReturnError {
+					t.Errorf("unexpected HTTP request")
+				}
+				http.Error(w, "no search for you", http.StatusInternalServerError)
+			}))
+			defer srv.Close()
+
+			// Construct the search term by cutting the 'http://' prefix off srv.URL.
+			term := srv.URL[7:] + "/term"
+
+			reg, err := NewService(ServiceOptions{})
+			assert.NilError(t, err)
+			_, err = reg.Search(context.Background(), tc.filtersArgs, term, 0, nil, map[string][]string{})
 			assert.ErrorContains(t, err, tc.expectedError)
 			if tc.shouldReturnError {
 				assert.Check(t, errdefs.IsUnknown(err), "got: %T: %v", err, err)
@@ -98,8 +88,8 @@ func TestSearchRegistryForImagesErrors(t *testing.T) {
 	}
 }
 
-func TestSearchRegistryForImages(t *testing.T) {
-	term := "term"
+func TestSearch(t *testing.T) {
+	const term = "term"
 	successCases := []struct {
 		name            string
 		filtersArgs     filters.Args
@@ -348,17 +338,24 @@ func TestSearchRegistryForImages(t *testing.T) {
 	for _, tc := range successCases {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
-			daemon := &ImageService{
-				registryService: &fakeService{
-					term:    term,
-					results: tc.registryResults,
-				},
-			}
-			results, err := daemon.SearchRegistryForImages(context.Background(), tc.filtersArgs, term, 0, nil, map[string][]string{})
+			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Header().Set("Content-type", "application/json")
+				json.NewEncoder(w).Encode(registry.SearchResults{
+					Query:      term,
+					NumResults: len(tc.registryResults),
+					Results:    tc.registryResults,
+				})
+			}))
+			defer srv.Close()
+
+			// Construct the search term by cutting the 'http://' prefix off srv.URL.
+			searchTerm := srv.URL[7:] + "/" + term
+
+			reg, err := NewService(ServiceOptions{})
+			assert.NilError(t, err)
+			results, err := reg.Search(context.Background(), tc.filtersArgs, searchTerm, 0, nil, map[string][]string{})
 			assert.NilError(t, err)
-			assert.Equal(t, results.Query, term)
-			assert.Equal(t, results.NumResults, len(tc.expectedResults))
-			assert.DeepEqual(t, results.Results, tc.expectedResults)
+			assert.DeepEqual(t, results, tc.expectedResults)
 		})
 	}
 }

+ 13 - 89
registry/service.go

@@ -3,56 +3,40 @@ package registry // import "github.com/docker/docker/registry"
 import (
 	"context"
 	"crypto/tls"
-	"net/http"
 	"net/url"
 	"strings"
 	"sync"
 
 	"github.com/docker/distribution/reference"
-	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
 	"github.com/sirupsen/logrus"
 )
 
-// Service is the interface defining what a registry service should implement.
-type Service interface {
-	Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error)
-	LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
-	LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
-	ResolveRepository(name reference.Named) (*RepositoryInfo, error)
-	Search(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
-	ServiceConfig() *registry.ServiceConfig
-	LoadAllowNondistributableArtifacts([]string) error
-	LoadMirrors([]string) error
-	LoadInsecureRegistries([]string) error
-	IsInsecureRegistry(string) bool
-}
-
-// defaultService is a registry service. It tracks configuration data such as a list
+// Service is a registry service. It tracks configuration data such as a list
 // of mirrors.
-type defaultService struct {
+type Service struct {
 	config *serviceConfig
 	mu     sync.RWMutex
 }
 
 // NewService returns a new instance of defaultService ready to be
 // installed into an engine.
-func NewService(options ServiceOptions) (Service, error) {
+func NewService(options ServiceOptions) (*Service, error) {
 	config, err := newServiceConfig(options)
 
-	return &defaultService{config: config}, err
+	return &Service{config: config}, err
 }
 
 // ServiceConfig returns a copy of the public registry service's configuration.
-func (s *defaultService) ServiceConfig() *registry.ServiceConfig {
+func (s *Service) ServiceConfig() *registry.ServiceConfig {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	return s.config.copy()
 }
 
 // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
-func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string) error {
+func (s *Service) LoadAllowNondistributableArtifacts(registries []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
@@ -60,7 +44,7 @@ func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string)
 }
 
 // LoadMirrors loads registry mirrors for Service
-func (s *defaultService) LoadMirrors(mirrors []string) error {
+func (s *Service) LoadMirrors(mirrors []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
@@ -68,7 +52,7 @@ func (s *defaultService) LoadMirrors(mirrors []string) error {
 }
 
 // LoadInsecureRegistries loads insecure registries for Service
-func (s *defaultService) LoadInsecureRegistries(registries []string) error {
+func (s *Service) LoadInsecureRegistries(registries []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
@@ -78,7 +62,7 @@ func (s *defaultService) LoadInsecureRegistries(registries []string) error {
 // Auth contacts the public registry with the provided credentials,
 // and returns OK if authentication was successful.
 // It can be used to verify the validity of a client's credentials.
-func (s *defaultService) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) {
+func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) {
 	// TODO Use ctx when searching for repositories
 	var registryHostName = IndexHostname
 
@@ -129,69 +113,9 @@ func splitReposSearchTerm(reposName string) (string, string) {
 	return nameParts[0], nameParts[1]
 }
 
-// Search queries the public registry for images matching the specified
-// search terms, and returns the results.
-func (s *defaultService) Search(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*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)
-	}
-
-	indexName, remoteName := splitReposSearchTerm(term)
-
-	// Search is a long-running operation, just lock s.config to avoid block others.
-	s.mu.RLock()
-	index, err := newIndexInfo(s.config, indexName)
-	s.mu.RUnlock()
-
-	if err != nil {
-		return nil, err
-	}
-	if index.Official {
-		// If pull "library/foo", it's stored locally under "foo"
-		remoteName = strings.TrimPrefix(remoteName, "library/")
-	}
-
-	endpoint, err := newV1Endpoint(index, userAgent, headers)
-	if err != nil {
-		return nil, err
-	}
-
-	var client *http.Client
-	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
-		creds := NewStaticCredentialStore(authConfig)
-		scopes := []auth.Scope{
-			auth.RegistryScope{
-				Name:    "catalog",
-				Actions: []string{"search"},
-			},
-		}
-
-		modifiers := Headers(userAgent, nil)
-		v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
-		if err != nil {
-			return nil, err
-		}
-		// Copy non transport http client features
-		v2Client.Timeout = endpoint.client.Timeout
-		v2Client.CheckRedirect = endpoint.client.CheckRedirect
-		v2Client.Jar = endpoint.client.Jar
-
-		logrus.Debugf("using v2 client for search to %s", endpoint.URL)
-		client = v2Client
-	} else {
-		client = endpoint.client
-		if err := authorizeClient(client, authConfig, endpoint); err != nil {
-			return nil, err
-		}
-	}
-
-	return newSession(client, endpoint).searchRepositories(remoteName, limit)
-}
-
 // ResolveRepository splits a repository name into its components
 // and configuration of the associated registry.
-func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
+func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	return newRepositoryInfo(s.config, name)
@@ -210,7 +134,7 @@ type APIEndpoint struct {
 
 // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
 // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
-func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *Service) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
@@ -219,7 +143,7 @@ func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEn
 
 // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
 // It gives preference to HTTPS over plain HTTP. Mirrors are not included.
-func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
@@ -236,7 +160,7 @@ func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEn
 
 // IsInsecureRegistry returns true if the registry at given host is configured as
 // insecure registry.
-func (s *defaultService) IsInsecureRegistry(host string) bool {
+func (s *Service) IsInsecureRegistry(host string) bool {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	return !s.config.isSecureIndex(host)

+ 1 - 1
registry/service_v2.go

@@ -7,7 +7,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 )
 
-func (s *defaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	ana := s.config.allowNondistributableArtifacts(hostname)
 
 	if hostname == DefaultNamespace || hostname == IndexHostname {

+ 2 - 2
registry/session.go

@@ -206,10 +206,10 @@ func (r *session) searchRepositories(term string, limit int) (*registry.SearchRe
 	}
 	defer res.Body.Close()
 	if res.StatusCode != http.StatusOK {
-		return nil, &jsonmessage.JSONError{
+		return nil, errdefs.Unknown(&jsonmessage.JSONError{
 			Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode),
 			Code:    res.StatusCode,
-		}
+		})
 	}
 	result := new(registry.SearchResults)
 	return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")