diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index d4558fd0dd..6dcee6f100 100644 --- a/api/server/router/image/backend.go +++ b/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) } diff --git a/api/server/router/image/image.go b/api/server/router/image/image.go index 70750d09af..7dd1eabf44 100644 --- a/api/server/router/image/image.go +++ b/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, diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index 5e0cbac315..3140a663ab 100644 --- a/api/server/router/image/image_routes.go +++ b/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 { diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index f5f37c6f1d..85091bd6aa 100644 --- a/cmd/dockerd/daemon.go +++ b/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, diff --git a/daemon/containerd/image_search.go b/daemon/containerd/image_search.go deleted file mode 100644 index 5d72249fbe..0000000000 --- a/daemon/containerd/image_search.go +++ /dev/null @@ -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")) -} diff --git a/daemon/containerd/resolver.go b/daemon/containerd/resolver.go index 82055f6737..086bb2edcd 100644 --- a/daemon/containerd/resolver.go +++ b/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 { diff --git a/daemon/containerd/service.go b/daemon/containerd/service.go index f003c4e2b0..fc691735c7 100644 --- a/daemon/containerd/service.go +++ b/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, diff --git a/daemon/daemon.go b/daemon/daemon.go index 6d3a3aa355..0736944ead 100644 --- a/daemon/daemon.go +++ b/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 { diff --git a/daemon/image_service.go b/daemon/image_service.go index 25b8af82a9..78ac874f4e 100644 --- a/daemon/image_service.go +++ b/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 diff --git a/daemon/images/image_search.go b/daemon/images/image_search.go deleted file mode 100644 index c31f9175ce..0000000000 --- a/daemon/images/image_search.go +++ /dev/null @@ -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 ®istry.SearchResults{ - Query: unfilteredResult.Query, - NumResults: len(filteredResults), - Results: filteredResults, - }, nil -} diff --git a/daemon/images/service.go b/daemon/images/service.go index ab51c396f5..e97964abf4 100644 --- a/daemon/images/service.go +++ b/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 diff --git a/distribution/config.go b/distribution/config.go index 5b6c892d99..e5048f4de1 100644 --- a/distribution/config.go +++ b/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. diff --git a/registry/registry_test.go b/registry/registry_test.go index 889064e0cc..2ad244c0eb 100644 --- a/registry/registry_test.go +++ b/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 { diff --git a/registry/search.go b/registry/search.go new file mode 100644 index 0000000000..d5c156e966 --- /dev/null +++ b/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) +} diff --git a/daemon/images/image_search_test.go b/registry/search_test.go similarity index 83% rename from daemon/images/image_search_test.go rename to registry/search_test.go index 115793d719..69329857c6 100644 --- a/daemon/images/image_search_test.go +++ b/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 ®istry.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) - assert.Equal(t, results.Query, term) - assert.Equal(t, results.NumResults, len(tc.expectedResults)) - assert.DeepEqual(t, results.Results, tc.expectedResults) + results, err := reg.Search(context.Background(), tc.filtersArgs, searchTerm, 0, nil, map[string][]string{}) + assert.NilError(t, err) + assert.DeepEqual(t, results, tc.expectedResults) }) } } diff --git a/registry/service.go b/registry/service.go index f12e10437f..b848065b3c 100644 --- a/registry/service.go +++ b/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) diff --git a/registry/service_v2.go b/registry/service_v2.go index d4352583fa..c8c545d21f 100644 --- a/registry/service_v2.go +++ b/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 { diff --git a/registry/session.go b/registry/session.go index e01f5384bd..86a5cd9edf 100644 --- a/registry/session.go +++ b/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")