浏览代码

Merge pull request #43298 from thaJeztah/cleanup_registry

registry: remove v1 leftovers, and refactor to reduce public api/interface
Sebastiaan van Stijn 3 年之前
父节点
当前提交
7cba4ffa30

+ 2 - 4
daemon/daemon.go

@@ -183,10 +183,8 @@ func (daemon *Daemon) RegistryHosts() docker.RegistryHosts {
 	}
 
 	for k, v := range m {
-		if d, err := registry.HostCertsDir(k); err == nil {
-			v.TLSConfigDir = []string{d}
-			m[k] = v
-		}
+		v.TLSConfigDir = []string{registry.HostCertsDir(k)}
+		m[k] = v
 	}
 
 	certsDir := registry.CertsDir()

+ 5 - 6
daemon/images/image_search_test.go

@@ -11,16 +11,15 @@ import (
 	"github.com/docker/docker/registry"
 )
 
-type FakeService struct {
-	registry.DefaultService
-
+type fakeService struct {
+	registry.Service
 	shouldReturnError bool
 
 	term    string
 	results []registrytypes.SearchResult
 }
 
-func (s *FakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
+func (s *fakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
 	if s.shouldReturnError {
 		return nil, errors.New("Search unknown error")
 	}
@@ -76,7 +75,7 @@ func TestSearchRegistryForImagesErrors(t *testing.T) {
 	}
 	for index, e := range errorCases {
 		daemon := &ImageService{
-			registryService: &FakeService{
+			registryService: &fakeService{
 				shouldReturnError: e.shouldReturnError,
 			},
 		}
@@ -322,7 +321,7 @@ func TestSearchRegistryForImages(t *testing.T) {
 	}
 	for index, s := range successCases {
 		daemon := &ImageService{
-			registryService: &FakeService{
+			registryService: &fakeService{
 				term:    term,
 				results: s.registryResults,
 			},

+ 8 - 15
registry/auth.go

@@ -10,15 +10,13 @@ import (
 	"github.com/docker/distribution/registry/client/auth/challenge"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
-const (
-	// AuthClientID is used the ClientID used for the token server
-	AuthClientID = "docker"
-)
+// AuthClientID is used the ClientID used for the token server
+const AuthClientID = "docker"
 
 type loginCredentialStore struct {
 	authConfig *types.AuthConfig
@@ -80,7 +78,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 	var (
 		endpointStr          = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
 		modifiers            = Headers(userAgent, nil)
-		authTransport        = transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
+		authTransport        = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
 		credentialAuthConfig = *authConfig
 		creds                = loginCredentialStore{authConfig: &credentialAuthConfig}
 	)
@@ -109,8 +107,7 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 	}
 
 	// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
-	err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
-	return "", "", err
+	return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
 }
 
 func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
@@ -129,10 +126,9 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi
 	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
 	basicHandler := auth.NewBasicHandler(creds)
 	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
-	tr := transport.NewTransport(authTransport, modifiers...)
 
 	return &http.Client{
-		Transport: tr,
+		Transport: transport.NewTransport(authTransport, modifiers...),
 		Timeout:   15 * time.Second,
 	}, nil
 }
@@ -146,14 +142,11 @@ func ConvertToHostname(url string) string {
 	} else if strings.HasPrefix(url, "https://") {
 		stripped = strings.TrimPrefix(url, "https://")
 	}
-
-	nameParts := strings.SplitN(stripped, "/", 2)
-
-	return nameParts[0]
+	return strings.SplitN(stripped, "/", 2)[0]
 }
 
 // ResolveAuthConfig matches an auth configuration to a server address or a URL
-func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
+func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.IndexInfo) types.AuthConfig {
 	configKey := GetAuthConfigKey(index)
 	// First try the happy case
 	if c, found := authConfigs[configKey]; found || index.Official {

+ 14 - 13
registry/auth_test.go

@@ -4,14 +4,15 @@ import (
 	"testing"
 
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
+	"gotest.tools/v3/assert"
 )
 
 func buildAuthConfigs() map[string]types.AuthConfig {
 	authConfigs := map[string]types.AuthConfig{}
 
-	for _, registry := range []string{"testIndex", IndexServer} {
-		authConfigs[registry] = types.AuthConfig{
+	for _, reg := range []string{"testIndex", IndexServer} {
+		authConfigs[reg] = types.AuthConfig{
 			Username: "docker-user",
 			Password: "docker-pass",
 		}
@@ -24,18 +25,18 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
 	authConfigs := buildAuthConfigs()
 	indexConfig := authConfigs[IndexServer]
 
-	officialIndex := &registrytypes.IndexInfo{
+	officialIndex := &registry.IndexInfo{
 		Official: true,
 	}
-	privateIndex := &registrytypes.IndexInfo{
+	privateIndex := &registry.IndexInfo{
 		Official: false,
 	}
 
 	resolved := ResolveAuthConfig(authConfigs, officialIndex)
-	assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer")
+	assert.Equal(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer")
 
 	resolved = ResolveAuthConfig(authConfigs, privateIndex)
-	assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServer")
+	assert.Check(t, resolved != indexConfig, "Expected ResolveAuthConfig to not return IndexServer")
 }
 
 func TestResolveAuthConfigFullURL(t *testing.T) {
@@ -87,19 +88,19 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
 		if !ok {
 			t.Fail()
 		}
-		index := &registrytypes.IndexInfo{
+		index := &registry.IndexInfo{
 			Name: configKey,
 		}
-		for _, registry := range registries {
-			authConfigs[registry] = configured
+		for _, reg := range registries {
+			authConfigs[reg] = configured
 			resolved := ResolveAuthConfig(authConfigs, index)
 			if resolved.Username != configured.Username || resolved.Password != configured.Password {
-				t.Errorf("%s -> %v != %v\n", registry, resolved, configured)
+				t.Errorf("%s -> %v != %v\n", reg, resolved, configured)
 			}
-			delete(authConfigs, registry)
+			delete(authConfigs, reg)
 			resolved = ResolveAuthConfig(authConfigs, index)
 			if resolved.Username == configured.Username || resolved.Password == configured.Password {
-				t.Errorf("%s -> %v == %v\n", registry, resolved, configured)
+				t.Errorf("%s -> %v == %v\n", reg, resolved, configured)
 			}
 		}
 	}

+ 81 - 94
registry/config.go

@@ -1,7 +1,6 @@
 package registry // import "github.com/docker/docker/registry"
 
 import (
-	"fmt"
 	"net"
 	"net/url"
 	"regexp"
@@ -9,8 +8,7 @@ import (
 	"strings"
 
 	"github.com/docker/distribution/reference"
-	registrytypes "github.com/docker/docker/api/types/registry"
-	"github.com/pkg/errors"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/sirupsen/logrus"
 )
 
@@ -22,9 +20,7 @@ type ServiceOptions struct {
 }
 
 // serviceConfig holds daemon configuration for the registry service.
-type serviceConfig struct {
-	registrytypes.ServiceConfig
-}
+type serviceConfig registry.ServiceConfig
 
 // TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
 // are here for historic reasons and backward-compatibility. These domains
@@ -58,10 +54,6 @@ var (
 		Host:   DefaultRegistryHost,
 	}
 
-	// ErrInvalidRepositoryName is an error returned if the repository name did
-	// not have the correct form
-	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
-
 	emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
 	validHostPortRegex    = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
 
@@ -71,57 +63,65 @@ var (
 
 // newServiceConfig returns a new instance of ServiceConfig
 func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
-	config := &serviceConfig{
-		ServiceConfig: registrytypes.ServiceConfig{
-			InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
-			IndexConfigs:          make(map[string]*registrytypes.IndexInfo),
-			// Hack: Bypass setting the mirrors to IndexConfigs since they are going away
-			// and Mirrors are only for the official registry anyways.
-		},
-	}
-	if err := config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
+	config := &serviceConfig{}
+	if err := config.loadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
 		return nil, err
 	}
-	if err := config.LoadMirrors(options.Mirrors); err != nil {
+	if err := config.loadMirrors(options.Mirrors); err != nil {
 		return nil, err
 	}
-	if err := config.LoadInsecureRegistries(options.InsecureRegistries); err != nil {
+	if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil {
 		return nil, err
 	}
 
 	return config, nil
 }
 
-// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
-func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
-	cidrs := map[string]*registrytypes.NetIPNet{}
+// copy constructs a new ServiceConfig with a copy of the configuration in config.
+func (config *serviceConfig) copy() *registry.ServiceConfig {
+	ic := make(map[string]*registry.IndexInfo)
+	for key, value := range config.IndexConfigs {
+		ic[key] = value
+	}
+	return &registry.ServiceConfig{
+		AllowNondistributableArtifactsCIDRs:     append([]*registry.NetIPNet(nil), config.AllowNondistributableArtifactsCIDRs...),
+		AllowNondistributableArtifactsHostnames: append([]string(nil), config.AllowNondistributableArtifactsHostnames...),
+		InsecureRegistryCIDRs:                   append([]*registry.NetIPNet(nil), config.InsecureRegistryCIDRs...),
+		IndexConfigs:                            ic,
+		Mirrors:                                 append([]string(nil), config.Mirrors...),
+	}
+}
+
+// loadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
+func (config *serviceConfig) loadAllowNondistributableArtifacts(registries []string) error {
+	cidrs := map[string]*registry.NetIPNet{}
 	hostnames := map[string]bool{}
 
 	for _, r := range registries {
 		if _, err := ValidateIndexName(r); err != nil {
 			return err
 		}
-		if validateNoScheme(r) != nil {
-			return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
+		if hasScheme(r) {
+			return invalidParamf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
 		}
 
 		if _, ipnet, err := net.ParseCIDR(r); err == nil {
 			// Valid CIDR.
-			cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
-		} else if err := validateHostPort(r); err == nil {
+			cidrs[ipnet.String()] = (*registry.NetIPNet)(ipnet)
+		} else if err = validateHostPort(r); err == nil {
 			// Must be `host:port` if not CIDR.
 			hostnames[r] = true
 		} else {
-			return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
+			return invalidParamWrapf(err, "allow-nondistributable-artifacts registry %s is not valid", r)
 		}
 	}
 
-	config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
+	config.AllowNondistributableArtifactsCIDRs = make([]*registry.NetIPNet, 0, len(cidrs))
 	for _, c := range cidrs {
 		config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
 	}
 
-	config.AllowNondistributableArtifactsHostnames = make([]string, 0)
+	config.AllowNondistributableArtifactsHostnames = make([]string, 0, len(hostnames))
 	for h := range hostnames {
 		config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
 	}
@@ -129,9 +129,9 @@ func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []str
 	return nil
 }
 
-// LoadMirrors loads mirrors to config, after removing duplicates.
+// loadMirrors loads mirrors to config, after removing duplicates.
 // Returns an error if mirrors contains an invalid mirror.
-func (config *serviceConfig) LoadMirrors(mirrors []string) error {
+func (config *serviceConfig) loadMirrors(mirrors []string) error {
 	mMap := map[string]struct{}{}
 	unique := []string{}
 
@@ -149,40 +149,33 @@ func (config *serviceConfig) LoadMirrors(mirrors []string) error {
 	config.Mirrors = unique
 
 	// Configure public registry since mirrors may have changed.
-	config.IndexConfigs[IndexName] = &registrytypes.IndexInfo{
-		Name:     IndexName,
-		Mirrors:  config.Mirrors,
-		Secure:   true,
-		Official: true,
+	config.IndexConfigs = map[string]*registry.IndexInfo{
+		IndexName: {
+			Name:     IndexName,
+			Mirrors:  unique,
+			Secure:   true,
+			Official: true,
+		},
 	}
 
 	return nil
 }
 
-// LoadInsecureRegistries loads insecure registries to config
-func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
-	// Localhost is by default considered as an insecure registry
-	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
-	//
-	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
-	// daemon flags on boot2docker?
+// loadInsecureRegistries loads insecure registries to config
+func (config *serviceConfig) loadInsecureRegistries(registries []string) error {
+	// Localhost is by default considered as an insecure registry. This is a
+	// stop-gap for people who are running a private registry on localhost.
 	registries = append(registries, "127.0.0.0/8")
 
-	// Store original InsecureRegistryCIDRs and IndexConfigs
-	// Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
-	originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
-	originalIndexInfos := config.ServiceConfig.IndexConfigs
-
-	config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
-	config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo)
+	var (
+		insecureRegistryCIDRs = make([]*registry.NetIPNet, 0)
+		indexConfigs          = make(map[string]*registry.IndexInfo)
+	)
 
 skip:
 	for _, r := range registries {
 		// validate insecure registry
 		if _, err := ValidateIndexName(r); err != nil {
-			// before returning err, roll back to original data
-			config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
-			config.ServiceConfig.IndexConfigs = originalIndexInfos
 			return err
 		}
 		if strings.HasPrefix(strings.ToLower(r), "http://") {
@@ -191,35 +184,27 @@ skip:
 		} else if strings.HasPrefix(strings.ToLower(r), "https://") {
 			logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
 			r = r[8:]
-		} else if validateNoScheme(r) != nil {
-			// Insecure registry should not contain '://'
-			// before returning err, roll back to original data
-			config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
-			config.ServiceConfig.IndexConfigs = originalIndexInfos
-			return fmt.Errorf("insecure registry %s should not contain '://'", r)
+		} else if hasScheme(r) {
+			return invalidParamf("insecure registry %s should not contain '://'", r)
 		}
 		// Check if CIDR was passed to --insecure-registry
 		_, ipnet, err := net.ParseCIDR(r)
 		if err == nil {
 			// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
-			data := (*registrytypes.NetIPNet)(ipnet)
-			for _, value := range config.InsecureRegistryCIDRs {
+			data := (*registry.NetIPNet)(ipnet)
+			for _, value := range insecureRegistryCIDRs {
 				if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
 					continue skip
 				}
 			}
 			// ipnet is not found, add it in config.InsecureRegistryCIDRs
-			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
-
+			insecureRegistryCIDRs = append(insecureRegistryCIDRs, data)
 		} else {
 			if err := validateHostPort(r); err != nil {
-				config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
-				config.ServiceConfig.IndexConfigs = originalIndexInfos
-				return fmt.Errorf("insecure registry %s is not valid: %v", r, err)
-
+				return invalidParamWrapf(err, "insecure registry %s is not valid", r)
 			}
 			// Assume `host:port` if not CIDR.
-			config.IndexConfigs[r] = &registrytypes.IndexInfo{
+			indexConfigs[r] = &registry.IndexInfo{
 				Name:     r,
 				Mirrors:  make([]string, 0),
 				Secure:   false,
@@ -229,12 +214,14 @@ skip:
 	}
 
 	// Configure public registry.
-	config.IndexConfigs[IndexName] = &registrytypes.IndexInfo{
+	indexConfigs[IndexName] = &registry.IndexInfo{
 		Name:     IndexName,
 		Mirrors:  config.Mirrors,
 		Secure:   true,
 		Official: true,
 	}
+	config.InsecureRegistryCIDRs = insecureRegistryCIDRs
+	config.IndexConfigs = indexConfigs
 
 	return nil
 }
@@ -248,7 +235,7 @@ skip:
 // hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
 // or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
 // resolution fails, CIDR matching is not performed.
-func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
+func (config *serviceConfig) allowNondistributableArtifacts(hostname string) bool {
 	for _, h := range config.AllowNondistributableArtifactsHostnames {
 		if h == hostname {
 			return true
@@ -269,7 +256,7 @@ func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool
 // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
 // in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
 // of insecureRegistries.
-func isSecureIndex(config *serviceConfig, indexName string) bool {
+func (config *serviceConfig) isSecureIndex(indexName string) bool {
 	// Check for configured index, first.  This is needed in case isSecureIndex
 	// is called from anything besides newIndexInfo, in order to honor per-index configurations.
 	if index, ok := config.IndexConfigs[indexName]; ok {
@@ -282,7 +269,7 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
 // isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
 // where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
 // resolved to IP addresses for matching. If resolution fails, false is returned.
-func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
+func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool {
 	host, _, err := net.SplitHostPort(URLHost)
 	if err != nil {
 		// Assume URLHost is of the form `host` without the port and go on.
@@ -318,18 +305,18 @@ func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
 func ValidateMirror(val string) (string, error) {
 	uri, err := url.Parse(val)
 	if err != nil {
-		return "", fmt.Errorf("invalid mirror: %q is not a valid URI", val)
+		return "", invalidParamWrapf(err, "invalid mirror: %q is not a valid URI", val)
 	}
 	if uri.Scheme != "http" && uri.Scheme != "https" {
-		return "", fmt.Errorf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
+		return "", invalidParamf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
 	}
 	if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
-		return "", fmt.Errorf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
+		return "", invalidParamf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
 	}
 	if uri.User != nil {
 		// strip password from output
 		uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
-		return "", fmt.Errorf("invalid mirror: username/password not allowed in URI %q", uri)
+		return "", invalidParamf("invalid mirror: username/password not allowed in URI %q", uri)
 	}
 	return strings.TrimSuffix(val, "/") + "/", nil
 }
@@ -341,17 +328,13 @@ func ValidateIndexName(val string) (string, error) {
 		val = "docker.io"
 	}
 	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
-		return "", fmt.Errorf("invalid index name (%s). Cannot begin or end with a hyphen", val)
+		return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val)
 	}
 	return val, nil
 }
 
-func validateNoScheme(reposName string) error {
-	if strings.Contains(reposName, "://") {
-		// It cannot contain a scheme!
-		return ErrInvalidRepositoryName
-	}
-	return nil
+func hasScheme(reposName string) bool {
+	return strings.Contains(reposName, "://")
 }
 
 func validateHostPort(s string) error {
@@ -364,7 +347,7 @@ func validateHostPort(s string) error {
 	// If match against the `host:port` pattern fails,
 	// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
 	if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
-		return fmt.Errorf("invalid host %q", host)
+		return invalidParamf("invalid host %q", host)
 	}
 	if port != "" {
 		v, err := strconv.Atoi(port)
@@ -372,14 +355,14 @@ func validateHostPort(s string) error {
 			return err
 		}
 		if v < 0 || v > 65535 {
-			return fmt.Errorf("invalid port %q", port)
+			return invalidParamf("invalid port %q", port)
 		}
 	}
 	return nil
 }
 
 // newIndexInfo returns IndexInfo configuration from indexName
-func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
+func newIndexInfo(config *serviceConfig, indexName string) (*registry.IndexInfo, error) {
 	var err error
 	indexName, err = ValidateIndexName(indexName)
 	if err != nil {
@@ -392,18 +375,17 @@ func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.Index
 	}
 
 	// Construct a non-configured index info.
-	index := &registrytypes.IndexInfo{
+	return &registry.IndexInfo{
 		Name:     indexName,
 		Mirrors:  make([]string, 0),
+		Secure:   config.isSecureIndex(indexName),
 		Official: false,
-	}
-	index.Secure = isSecureIndex(config, indexName)
-	return index, nil
+	}, nil
 }
 
 // GetAuthConfigKey special-cases using the full index address of the official
 // index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
-func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
+func GetAuthConfigKey(index *registry.IndexInfo) string {
 	if index.Official {
 		return IndexServer
 	}
@@ -432,7 +414,12 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
 }
 
 // ParseSearchIndexInfo will use repository name to get back an indexInfo.
-func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) {
+//
+// 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)
 
 	indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)

+ 7 - 5
registry/config_test.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/docker/docker/errdefs"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 )
@@ -94,7 +95,7 @@ func TestLoadAllowNondistributableArtifacts(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		config := emptyServiceConfig
-		err := config.LoadAllowNondistributableArtifacts(testCase.registries)
+		err := config.loadAllowNondistributableArtifacts(testCase.registries)
 		if testCase.err == "" {
 			if err != nil {
 				t.Fatalf("expect no error, got '%s'", err)
@@ -237,7 +238,7 @@ func TestLoadInsecureRegistries(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		config := emptyServiceConfig
-		err := config.LoadInsecureRegistries(testCase.registries)
+		err := config.loadInsecureRegistries(testCase.registries)
 		if testCase.err == "" {
 			if err != nil {
 				t.Fatalf("expect no error, got '%s'", err)
@@ -255,9 +256,8 @@ func TestLoadInsecureRegistries(t *testing.T) {
 			if err == nil {
 				t.Fatalf("expect error '%s', got no error", testCase.err)
 			}
-			if !strings.Contains(err.Error(), testCase.err) {
-				t.Fatalf("expect error '%s', got '%s'", testCase.err, err)
-			}
+			assert.ErrorContains(t, err, testCase.err)
+			assert.Check(t, errdefs.IsInvalidParameter(err))
 		}
 	}
 }
@@ -313,6 +313,7 @@ func TestNewServiceConfig(t *testing.T) {
 		_, err := newServiceConfig(testCase.opts)
 		if testCase.errStr != "" {
 			assert.Check(t, is.Error(err, testCase.errStr))
+			assert.Check(t, errdefs.IsInvalidParameter(err))
 		} else {
 			assert.Check(t, err)
 		}
@@ -377,5 +378,6 @@ func TestValidateIndexNameWithError(t *testing.T) {
 	for _, testCase := range invalid {
 		_, err := ValidateIndexName(testCase.index)
 		assert.Check(t, is.Error(err, testCase.err))
+		assert.Check(t, errdefs.IsInvalidParameter(err))
 	}
 }

+ 2 - 2
registry/endpoint_test.go

@@ -63,9 +63,9 @@ func TestValidateEndpoint(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	testEndpoint := V1Endpoint{
+	testEndpoint := v1Endpoint{
 		URL:    testServerURL,
-		client: HTTPClient(NewTransport(nil)),
+		client: httpClient(newTransport(nil)),
 	}
 
 	if err = validateEndpoint(&testEndpoint); err != nil {

+ 45 - 43
registry/endpoint_v1.go

@@ -3,27 +3,39 @@ package registry // import "github.com/docker/docker/registry"
 import (
 	"crypto/tls"
 	"encoding/json"
-	"fmt"
 	"io"
 	"net/http"
 	"net/url"
 	"strings"
 
 	"github.com/docker/distribution/registry/client/transport"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/sirupsen/logrus"
 )
 
-// V1Endpoint stores basic information about a V1 registry endpoint.
-type V1Endpoint struct {
+// v1PingResult contains the information returned when pinging a registry. It
+// indicates the registry's version and whether the registry claims to be a
+// standalone registry.
+type v1PingResult struct {
+	// Version is the registry version supplied by the registry in an HTTP
+	// header
+	Version string `json:"version"`
+	// Standalone is set to true if the registry indicates it is a
+	// standalone registry in the X-Docker-Registry-Standalone
+	// header
+	Standalone bool `json:"standalone"`
+}
+
+// v1Endpoint stores basic information about a V1 registry endpoint.
+type v1Endpoint struct {
 	client   *http.Client
 	URL      *url.URL
 	IsSecure bool
 }
 
-// NewV1Endpoint parses the given address to return a registry endpoint.
+// newV1Endpoint parses the given address to return a registry endpoint.
 // TODO: remove. This is only used by search.
-func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
+func newV1Endpoint(index *registry.IndexInfo, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
 	tlsConfig, err := newTLSConfig(index.Name, index.Secure)
 	if err != nil {
 		return nil, err
@@ -42,28 +54,28 @@ func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders
 	return endpoint, nil
 }
 
-func validateEndpoint(endpoint *V1Endpoint) error {
+func validateEndpoint(endpoint *v1Endpoint) error {
 	logrus.Debugf("pinging registry endpoint %s", endpoint)
 
 	// Try HTTPS ping to registry
 	endpoint.URL.Scheme = "https"
-	if _, err := endpoint.Ping(); err != nil {
+	if _, err := endpoint.ping(); err != nil {
 		if endpoint.IsSecure {
 			// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
 			// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
-			return fmt.Errorf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
+			return invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
 		}
 
 		// If registry is insecure and HTTPS failed, fallback to HTTP.
-		logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
+		logrus.WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint)
 		endpoint.URL.Scheme = "http"
 
 		var err2 error
-		if _, err2 = endpoint.Ping(); err2 == nil {
+		if _, err2 = endpoint.ping(); err2 == nil {
 			return nil
 		}
 
-		return fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
+		return invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
 	}
 
 	return nil
@@ -72,28 +84,23 @@ func validateEndpoint(endpoint *V1Endpoint) error {
 // trimV1Address trims the version off the address and returns the
 // trimmed address or an error if there is a non-V1 version.
 func trimV1Address(address string) (string, error) {
-	var (
-		chunks        []string
-		apiVersionStr string
-	)
-
 	address = strings.TrimSuffix(address, "/")
-	chunks = strings.Split(address, "/")
-	apiVersionStr = chunks[len(chunks)-1]
+	chunks := strings.Split(address, "/")
+	apiVersionStr := chunks[len(chunks)-1]
 	if apiVersionStr == "v1" {
 		return strings.Join(chunks[:len(chunks)-1], "/"), nil
 	}
 
 	for k, v := range apiVersions {
 		if k != APIVersion1 && apiVersionStr == v {
-			return "", fmt.Errorf("unsupported V1 version path %s", apiVersionStr)
+			return "", invalidParamf("unsupported V1 version path %s", apiVersionStr)
 		}
 	}
 
 	return address, nil
 }
 
-func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
+func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
 	if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
 		address = "https://" + address
 	}
@@ -105,69 +112,64 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
 
 	uri, err := url.Parse(address)
 	if err != nil {
-		return nil, err
+		return nil, invalidParam(err)
 	}
 
 	// TODO(tiborvass): make sure a ConnectTimeout transport is used
-	tr := NewTransport(tlsConfig)
+	tr := newTransport(tlsConfig)
 
-	return &V1Endpoint{
+	return &v1Endpoint{
 		IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
 		URL:      uri,
-		client:   HTTPClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
+		client:   httpClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
 	}, nil
 }
 
 // Get the formatted URL for the root of this registry Endpoint
-func (e *V1Endpoint) String() string {
+func (e *v1Endpoint) String() string {
 	return e.URL.String() + "/v1/"
 }
 
-// Path returns a formatted string for the URL
-// of this endpoint with the given path appended.
-func (e *V1Endpoint) Path(path string) string {
-	return e.URL.String() + "/v1/" + path
-}
-
-// Ping returns a PingResult which indicates whether the registry is standalone or not.
-func (e *V1Endpoint) Ping() (PingResult, error) {
+// ping returns a v1PingResult which indicates whether the registry is standalone or not.
+func (e *v1Endpoint) ping() (v1PingResult, error) {
 	if e.String() == IndexServer {
 		// Skip the check, we know this one is valid
 		// (and we never want to fallback to http in case of error)
-		return PingResult{}, nil
+		return v1PingResult{}, nil
 	}
 
 	logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
-	req, err := http.NewRequest(http.MethodGet, e.Path("_ping"), nil)
+	pingURL := e.String() + "_ping"
+	req, err := http.NewRequest(http.MethodGet, pingURL, nil)
 	if err != nil {
-		return PingResult{}, err
+		return v1PingResult{}, invalidParam(err)
 	}
 
 	resp, err := e.client.Do(req)
 	if err != nil {
-		return PingResult{}, err
+		return v1PingResult{}, invalidParam(err)
 	}
 
 	defer resp.Body.Close()
 
 	jsonString, err := io.ReadAll(resp.Body)
 	if err != nil {
-		return PingResult{}, fmt.Errorf("error while reading the http response: %s", err)
+		return v1PingResult{}, invalidParamWrapf(err, "error while reading response from %s", pingURL)
 	}
 
 	// If the header is absent, we assume true for compatibility with earlier
 	// versions of the registry. default to true
-	info := PingResult{
+	info := v1PingResult{
 		Standalone: true,
 	}
 	if err := json.Unmarshal(jsonString, &info); err != nil {
-		logrus.Debugf("Error unmarshaling the _ping PingResult: %s", err)
+		logrus.WithError(err).Debug("error unmarshaling _ping response")
 		// don't stop here. Just assume sane defaults
 	}
 	if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
 		info.Version = hdr
 	}
-	logrus.Debugf("PingResult.Version: %q", info.Version)
+	logrus.Debugf("v1PingResult.Version: %q", info.Version)
 
 	standalone := resp.Header.Get("X-Docker-Registry-Standalone")
 
@@ -178,6 +180,6 @@ func (e *V1Endpoint) Ping() (PingResult, error) {
 		// there is a header set, and it is not "true" or "1", so assume fails
 		info.Standalone = false
 	}
-	logrus.Debugf("PingResult.Standalone: %t", info.Standalone)
+	logrus.Debugf("v1PingResult.Standalone: %t", info.Standalone)
 	return info, nil
 }

+ 13 - 0
registry/errors.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/errdefs"
+	"github.com/pkg/errors"
 )
 
 func translateV2AuthError(err error) error {
@@ -21,3 +22,15 @@ func translateV2AuthError(err error) error {
 
 	return err
 }
+
+func invalidParam(err error) error {
+	return errdefs.InvalidParameter(err)
+}
+
+func invalidParamf(format string, args ...interface{}) error {
+	return errdefs.InvalidParameter(errors.Errorf(format, args...))
+}
+
+func invalidParamWrapf(err error, format string, args ...interface{}) error {
+	return errdefs.InvalidParameter(errors.Wrapf(err, format, args...))
+}

+ 14 - 24
registry/registry.go

@@ -3,7 +3,6 @@ package registry // import "github.com/docker/docker/registry"
 
 import (
 	"crypto/tls"
-	"fmt"
 	"net"
 	"net/http"
 	"os"
@@ -16,15 +15,12 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-// HostCertsDir returns the config directory for a specific host
-func HostCertsDir(hostname string) (string, error) {
-	certsDir := CertsDir()
-
-	hostDir := filepath.Join(certsDir, cleanPath(hostname))
-
-	return hostDir, nil
+// HostCertsDir returns the config directory for a specific host.
+func HostCertsDir(hostname string) string {
+	return filepath.Join(CertsDir(), cleanPath(hostname))
 }
 
+// newTLSConfig constructs a client TLS configuration based on server defaults
 func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
 	// PreferredServerCipherSuites should have no effect
 	tlsConfig := tlsconfig.ServerDefault()
@@ -32,11 +28,7 @@ func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
 	tlsConfig.InsecureSkipVerify = !isSecure
 
 	if isSecure && CertsDir() != "" {
-		hostDir, err := HostCertsDir(hostname)
-		if err != nil {
-			return nil, err
-		}
-
+		hostDir := HostCertsDir(hostname)
 		logrus.Debugf("hostDir: %s", hostDir)
 		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
 			return nil, err
@@ -61,7 +53,7 @@ func hasFile(files []os.DirEntry, name string) bool {
 func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
 	fs, err := os.ReadDir(directory)
 	if err != nil && !os.IsNotExist(err) {
-		return err
+		return invalidParam(err)
 	}
 
 	for _, f := range fs {
@@ -69,7 +61,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
 			if tlsConfig.RootCAs == nil {
 				systemPool, err := tlsconfig.SystemCertPool()
 				if err != nil {
-					return fmt.Errorf("unable to get system cert pool: %v", err)
+					return invalidParamWrapf(err, "unable to get system cert pool")
 				}
 				tlsConfig.RootCAs = systemPool
 			}
@@ -85,7 +77,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
 			keyName := certName[:len(certName)-5] + ".key"
 			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
 			if !hasFile(fs, keyName) {
-				return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
+				return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
 			}
 			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
 			if err != nil {
@@ -98,7 +90,7 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
 			certName := keyName[:len(keyName)-4] + ".cert"
 			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
 			if !hasFile(fs, certName) {
-				return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
+				return invalidParamf("missing client certificate %s for key %s", certName, keyName)
 			}
 		}
 	}
@@ -120,9 +112,9 @@ func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModif
 	return modifiers
 }
 
-// HTTPClient returns an HTTP client structure which uses the given transport
+// 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 {
+func httpClient(transport http.RoundTripper) *http.Client {
 	return &http.Client{
 		Transport:     transport,
 		CheckRedirect: addRequiredHeadersToRedirectedRequests,
@@ -165,9 +157,9 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
 	return nil
 }
 
-// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
+// newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
 // default TLS configuration.
-func NewTransport(tlsConfig *tls.Config) *http.Transport {
+func newTransport(tlsConfig *tls.Config) *http.Transport {
 	if tlsConfig == nil {
 		tlsConfig = tlsconfig.ServerDefault()
 	}
@@ -177,7 +169,7 @@ func NewTransport(tlsConfig *tls.Config) *http.Transport {
 		KeepAlive: 30 * time.Second,
 	}
 
-	base := &http.Transport{
+	return &http.Transport{
 		Proxy:               http.ProxyFromEnvironment,
 		DialContext:         direct.DialContext,
 		TLSHandshakeTimeout: 10 * time.Second,
@@ -185,6 +177,4 @@ func NewTransport(tlsConfig *tls.Config) *http.Transport {
 		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
 		DisableKeepAlives: true,
 	}
-
-	return base
 }

+ 19 - 342
registry/registry_mock_test.go

@@ -3,93 +3,21 @@ package registry // import "github.com/docker/docker/registry"
 import (
 	"encoding/json"
 	"errors"
-	"fmt"
 	"io"
 	"net"
 	"net/http"
 	"net/http/httptest"
-	"net/url"
-	"strconv"
-	"strings"
 	"testing"
-	"time"
 
-	"github.com/docker/distribution/reference"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/gorilla/mux"
-
 	"github.com/sirupsen/logrus"
+	"gotest.tools/v3/assert"
 )
 
 var (
 	testHTTPServer  *httptest.Server
 	testHTTPSServer *httptest.Server
-	testLayers      = map[string]map[string]string{
-		"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
-			"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
-				"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
-				"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
-				"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
-				"Tty":false,"OpenStdin":false,"StdinOnce":false,
-				"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
-				"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
-			"checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
-			"checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c",
-			"ancestry":        `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
-			"layer": string([]byte{
-				0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
-				0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
-				0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66,
-				0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78,
-				0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31,
-				0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8,
-				0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1,
-				0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6,
-				0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb,
-				0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce,
-				0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00,
-			}),
-		},
-		"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": {
-			"json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
-				"parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
-				"comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00",
-				"container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
-				"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
-				"Tty":false,"OpenStdin":false,"StdinOnce":false,
-				"Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
-				"VolumesFrom":"","Entrypoint":null},"Size":424242}`,
-			"checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
-			"checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2",
-			"ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
-				"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
-			"layer": string([]byte{
-				0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
-				0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
-				0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56,
-				0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5,
-				0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e,
-				0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93,
-				0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee,
-				0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9,
-				0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55,
-				0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17,
-				0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00,
-			}),
-		},
-	}
-	testRepositories = map[string]map[string]string{
-		"foo42/bar": {
-			"latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
-			"test":   "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
-		},
-	}
-	mockHosts = map[string][]net.IP{
-		"":            {net.ParseIP("0.0.0.0")},
-		"localhost":   {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
-		"example.com": {net.ParseIP("42.42.42.42")},
-		"other.com":   {net.ParseIP("43.43.43.43")},
-	}
 )
 
 func init() {
@@ -97,14 +25,6 @@ func init() {
 
 	// /v1/
 	r.HandleFunc("/v1/_ping", handlerGetPing).Methods(http.MethodGet)
-	r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods(http.MethodGet)
-	r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods(http.MethodPut)
-	r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods(http.MethodGet, http.MethodDelete)
-	r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods(http.MethodGet)
-	r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods(http.MethodPut)
-	r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods(http.MethodGet, http.MethodPost, http.MethodPut)
-	r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods(http.MethodGet, http.MethodPut, http.MethodDelete)
-	r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods(http.MethodPut)
 	r.HandleFunc("/v1/search", handlerSearch).Methods(http.MethodGet)
 
 	// /v2/
@@ -119,6 +39,12 @@ func init() {
 			// I believe in future Go versions this will fail, so let's fix it later
 			return net.LookupIP(host)
 		}
+		mockHosts := map[string][]net.IP{
+			"":            {net.ParseIP("0.0.0.0")},
+			"localhost":   {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
+			"example.com": {net.ParseIP("42.42.42.42")},
+			"other.com":   {net.ParseIP("43.43.43.43")},
+		}
 		for h, addrs := range mockHosts {
 			if host == h {
 				return addrs, nil
@@ -135,7 +61,7 @@ func init() {
 
 func handlerAccessLog(handler http.Handler) http.Handler {
 	logHandler := func(w http.ResponseWriter, r *http.Request) {
-		logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
+		logrus.Debugf(`%s "%s %s"`, r.RemoteAddr, r.Method, r.URL)
 		handler.ServeHTTP(w, r)
 	}
 	return http.HandlerFunc(logHandler)
@@ -149,22 +75,22 @@ func makeHTTPSURL(req string) string {
 	return testHTTPSServer.URL + req
 }
 
-func makeIndex(req string) *registrytypes.IndexInfo {
-	index := &registrytypes.IndexInfo{
+func makeIndex(req string) *registry.IndexInfo {
+	index := &registry.IndexInfo{
 		Name: makeURL(req),
 	}
 	return index
 }
 
-func makeHTTPSIndex(req string) *registrytypes.IndexInfo {
-	index := &registrytypes.IndexInfo{
+func makeHTTPSIndex(req string) *registry.IndexInfo {
+	index := &registry.IndexInfo{
 		Name: makeHTTPSURL(req),
 	}
 	return index
 }
 
-func makePublicIndex() *registrytypes.IndexInfo {
-	index := &registrytypes.IndexInfo{
+func makePublicIndex() *registry.IndexInfo {
+	index := &registry.IndexInfo{
 		Name:     IndexServer,
 		Secure:   true,
 		Official: true,
@@ -203,252 +129,15 @@ func writeResponse(w http.ResponseWriter, message interface{}, code int) {
 	w.Write(body)
 }
 
-func readJSON(r *http.Request, dest interface{}) error {
-	body, err := io.ReadAll(r.Body)
-	if err != nil {
-		return err
-	}
-	return json.Unmarshal(body, dest)
-}
-
-func apiError(w http.ResponseWriter, message string, code int) {
-	body := map[string]string{
-		"error": message,
-	}
-	writeResponse(w, body, code)
-}
-
-func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
-	if a == b {
-		return
-	}
-	if len(message) == 0 {
-		message = fmt.Sprintf("%v != %v", a, b)
-	}
-	t.Fatal(message)
-}
-
-func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
-	if a != b {
-		return
-	}
-	if len(message) == 0 {
-		message = fmt.Sprintf("%v == %v", a, b)
-	}
-	t.Fatal(message)
-}
-
-// Similar to assertEqual, but does not stop test
-func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
-	if a == b {
-		return
-	}
-	message := fmt.Sprintf("%v != %v", a, b)
-	if len(messagePrefix) != 0 {
-		message = messagePrefix + ": " + message
-	}
-	t.Error(message)
-}
-
-// Similar to assertNotEqual, but does not stop test
-func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
-	if a != b {
-		return
-	}
-	message := fmt.Sprintf("%v == %v", a, b)
-	if len(messagePrefix) != 0 {
-		message = messagePrefix + ": " + message
-	}
-	t.Error(message)
-}
-
-func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
-	writeCookie := func() {
-		value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
-		cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600}
-		http.SetCookie(w, cookie)
-		// FIXME(sam): this should be sent only on Index routes
-		value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano())
-		w.Header().Add("X-Docker-Token", value)
-	}
-	if len(r.Cookies()) > 0 {
-		writeCookie()
-		return true
-	}
-	if len(r.Header.Get("Authorization")) > 0 {
-		writeCookie()
-		return true
-	}
-	w.Header().Add("WWW-Authenticate", "token")
-	apiError(w, "Wrong auth", http.StatusUnauthorized)
-	return false
-}
-
 func handlerGetPing(w http.ResponseWriter, r *http.Request) {
 	writeResponse(w, true, http.StatusOK)
 }
 
-func handlerGetImage(w http.ResponseWriter, r *http.Request) {
-	if !requiresAuth(w, r) {
-		return
-	}
-	vars := mux.Vars(r)
-	layer, exists := testLayers[vars["image_id"]]
-	if !exists {
-		http.NotFound(w, r)
-		return
-	}
-	writeHeaders(w)
-	layerSize := len(layer["layer"])
-	w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize))
-	io.WriteString(w, layer[vars["action"]])
-}
-
-func handlerPutImage(w http.ResponseWriter, r *http.Request) {
-	if !requiresAuth(w, r) {
-		return
-	}
-	vars := mux.Vars(r)
-	imageID := vars["image_id"]
-	action := vars["action"]
-	layer, exists := testLayers[imageID]
-	if !exists {
-		if action != "json" {
-			http.NotFound(w, r)
-			return
-		}
-		layer = make(map[string]string)
-		testLayers[imageID] = layer
-	}
-	if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" {
-		if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] {
-			apiError(w, "Wrong checksum", http.StatusBadRequest)
-			return
-		}
-	}
-	body, err := io.ReadAll(r.Body)
-	if err != nil {
-		apiError(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError)
-		return
-	}
-	layer[action] = string(body)
-	writeResponse(w, true, http.StatusOK)
-}
-
-func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
-	if !requiresAuth(w, r) {
-		return
-	}
-	repositoryName, err := reference.WithName(mux.Vars(r)["repository"])
-	if err != nil {
-		apiError(w, "Could not parse repository", http.StatusBadRequest)
-		return
-	}
-	tags, exists := testRepositories[repositoryName.String()]
-	if !exists {
-		apiError(w, "Repository not found", http.StatusNotFound)
-		return
-	}
-	if r.Method == http.MethodDelete {
-		delete(testRepositories, repositoryName.String())
-		writeResponse(w, true, http.StatusOK)
-		return
-	}
-	writeResponse(w, tags, http.StatusOK)
-}
-
-func handlerGetTag(w http.ResponseWriter, r *http.Request) {
-	if !requiresAuth(w, r) {
-		return
-	}
-	vars := mux.Vars(r)
-	repositoryName, err := reference.WithName(vars["repository"])
-	if err != nil {
-		apiError(w, "Could not parse repository", http.StatusBadRequest)
-		return
-	}
-	tagName := vars["tag"]
-	tags, exists := testRepositories[repositoryName.String()]
-	if !exists {
-		apiError(w, "Repository not found", http.StatusNotFound)
-		return
-	}
-	tag, exists := tags[tagName]
-	if !exists {
-		apiError(w, "Tag not found", http.StatusNotFound)
-		return
-	}
-	writeResponse(w, tag, http.StatusOK)
-}
-
-func handlerPutTag(w http.ResponseWriter, r *http.Request) {
-	if !requiresAuth(w, r) {
-		return
-	}
-	vars := mux.Vars(r)
-	repositoryName, err := reference.WithName(vars["repository"])
-	if err != nil {
-		apiError(w, "Could not parse repository", http.StatusBadRequest)
-		return
-	}
-	tagName := vars["tag"]
-	tags, exists := testRepositories[repositoryName.String()]
-	if !exists {
-		tags = make(map[string]string)
-		testRepositories[repositoryName.String()] = tags
-	}
-	tagValue := ""
-	readJSON(r, tagValue)
-	tags[tagName] = tagValue
-	writeResponse(w, true, http.StatusOK)
-}
-
-func handlerUsers(w http.ResponseWriter, r *http.Request) {
-	code := http.StatusOK
-	if r.Method == http.MethodPost {
-		code = http.StatusCreated
-	} else if r.Method == http.MethodPut {
-		code = http.StatusNoContent
-	}
-	writeResponse(w, "", code)
-}
-
-func handlerImages(w http.ResponseWriter, r *http.Request) {
-	u, _ := url.Parse(testHTTPServer.URL)
-	w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s 	,  %s ", u.Host, "test.example.com"))
-	w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()))
-	if r.Method == http.MethodPut {
-		if strings.HasSuffix(r.URL.Path, "images") {
-			writeResponse(w, "", http.StatusNoContent)
-			return
-		}
-		writeResponse(w, "", http.StatusOK)
-		return
-	}
-	if r.Method == http.MethodDelete {
-		writeResponse(w, "", http.StatusNoContent)
-		return
-	}
-	var images []map[string]string
-	for imageID, layer := range testLayers {
-		image := make(map[string]string)
-		image["id"] = imageID
-		image["checksum"] = layer["checksum_tarsum"]
-		image["Tag"] = "latest"
-		images = append(images, image)
-	}
-	writeResponse(w, images, http.StatusOK)
-}
-
-func handlerAuth(w http.ResponseWriter, r *http.Request) {
-	writeResponse(w, "OK", http.StatusOK)
-}
-
 func handlerSearch(w http.ResponseWriter, r *http.Request) {
-	result := &registrytypes.SearchResults{
+	result := &registry.SearchResults{
 		Query:      "fakequery",
 		NumResults: 1,
-		Results:    []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}},
+		Results:    []registry.SearchResult{{Name: "fakeimage", StarCount: 42}},
 	}
 	writeResponse(w, result, http.StatusOK)
 }
@@ -458,18 +147,6 @@ func TestPing(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	assertEqual(t, res.StatusCode, http.StatusOK, "")
-	assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock",
-		"This is not a Mocked Registry")
+	assert.Equal(t, res.StatusCode, http.StatusOK, "")
+	assert.Equal(t, res.Header.Get("X-Docker-Registry-Config"), "mock", "This is not a Mocked Registry")
 }
-
-/* Uncomment this to test Mocked Registry locally with curl
- * WARNING: Don't push on the repos uncommented, it'll block the tests
- *
-func TestWait(t *testing.T) {
-	logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL)
-	c := make(chan int)
-	<-c
-}
-
-//*/

+ 75 - 74
registry/registry_test.go

@@ -10,25 +10,28 @@ import (
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"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 {
+func spawnTestRegistrySession(t *testing.T) *session {
 	authConfig := &types.AuthConfig{}
-	endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil)
+	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}
+	var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log}
 	tr = transport.NewTransport(AuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
-	client := HTTPClient(tr)
-	r, err := NewSession(client, authConfig, endpoint)
-	if err != 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.
@@ -45,17 +48,17 @@ 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 *registrytypes.IndexInfo, expectedStandalone bool, assertMessage string) {
-		ep, err := NewV1Endpoint(index, "", nil)
+	testPing := func(index *registry.IndexInfo, expectedStandalone bool, assertMessage string) {
+		ep, err := newV1Endpoint(index, "", nil)
 		if err != nil {
 			t.Fatal(err)
 		}
-		regInfo, err := ep.Ping()
+		regInfo, err := ep.ping()
 		if err != nil {
 			t.Fatal(err)
 		}
 
-		assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
+		assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage)
 	}
 
 	testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
@@ -66,61 +69,59 @@ func TestPingRegistryEndpoint(t *testing.T) {
 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 *registrytypes.IndexInfo) *V1Endpoint {
-		endpoint, err := NewV1Endpoint(index, "", nil)
+	expandEndpoint := func(index *registry.IndexInfo) *v1Endpoint {
+		endpoint, err := newV1Endpoint(index, "", nil)
 		if err != nil {
 			t.Fatal(err)
 		}
 		return endpoint
 	}
 
-	assertInsecureIndex := func(index *registrytypes.IndexInfo) {
+	assertInsecureIndex := func(index *registry.IndexInfo) {
 		index.Secure = true
-		_, err := NewV1Endpoint(index, "", nil)
-		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
-		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
+		_, 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 *registrytypes.IndexInfo) {
+	assertSecureIndex := func(index *registry.IndexInfo) {
 		index.Secure = true
-		_, err := NewV1Endpoint(index, "", nil)
-		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
-		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
+		_, 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 := &registrytypes.IndexInfo{}
+	index := &registry.IndexInfo{}
 	index.Name = makeURL("/v1/")
 	endpoint := expandEndpoint(index)
-	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
+	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
 	assertInsecureIndex(index)
 
 	index.Name = makeURL("")
 	endpoint = expandEndpoint(index)
-	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
+	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)
-	assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
+	assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
 	assertInsecureIndex(index)
 
 	index.Name = makeHTTPSURL("/v1/")
 	endpoint = expandEndpoint(index)
-	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
+	assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
 	assertSecureIndex(index)
 
 	index.Name = makeHTTPSURL("")
 	endpoint = expandEndpoint(index)
-	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
+	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)
-	assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
+	assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
 	assertSecureIndex(index)
 
 	badEndpoints := []string{
@@ -132,14 +133,14 @@ func TestEndpoint(t *testing.T) {
 	}
 	for _, address := range badEndpoints {
 		index.Name = address
-		_, err := NewV1Endpoint(index, "", nil)
-		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
+		_, 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         *registrytypes.IndexInfo
+		Index         *registry.IndexInfo
 		RemoteName    string
 		CanonicalName string
 		LocalName     string
@@ -148,7 +149,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 
 	expectedRepoInfos := map[string]staticRepositoryInfo{
 		"fooo/bar": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -158,7 +159,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"library/ubuntu": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -168,7 +169,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      true,
 		},
 		"nonlibrary/ubuntu": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -178,7 +179,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"ubuntu": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -188,7 +189,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      true,
 		},
 		"other/library": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -198,7 +199,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"127.0.0.1:8000/private/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "127.0.0.1:8000",
 				Official: false,
 			},
@@ -208,7 +209,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"127.0.0.1:8000/privatebase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "127.0.0.1:8000",
 				Official: false,
 			},
@@ -218,7 +219,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"localhost:8000/private/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "localhost:8000",
 				Official: false,
 			},
@@ -228,7 +229,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"localhost:8000/privatebase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "localhost:8000",
 				Official: false,
 			},
@@ -238,7 +239,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"example.com/private/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "example.com",
 				Official: false,
 			},
@@ -248,7 +249,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"example.com/privatebase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "example.com",
 				Official: false,
 			},
@@ -258,7 +259,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"example.com:8000/private/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "example.com:8000",
 				Official: false,
 			},
@@ -268,7 +269,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"example.com:8000/privatebase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "example.com:8000",
 				Official: false,
 			},
@@ -278,7 +279,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"localhost/private/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "localhost",
 				Official: false,
 			},
@@ -288,7 +289,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"localhost/privatebase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     "localhost",
 				Official: false,
 			},
@@ -298,7 +299,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		IndexName + "/public/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -308,7 +309,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"index." + IndexName + "/public/moonbase": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -318,7 +319,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      false,
 		},
 		"ubuntu-12.04-base": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -328,7 +329,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      true,
 		},
 		IndexName + "/ubuntu-12.04-base": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -338,7 +339,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 			Official:      true,
 		},
 		"index." + IndexName + "/ubuntu-12.04-base": {
-			Index: &registrytypes.IndexInfo{
+			Index: &registry.IndexInfo{
 				Name:     IndexName,
 				Official: true,
 			},
@@ -359,34 +360,34 @@ func TestParseRepositoryInfo(t *testing.T) {
 		if err != nil {
 			t.Error(err)
 		} else {
-			checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
-			checkEqual(t, reference.Path(repoInfo.Name), expectedRepoInfo.RemoteName, reposName)
-			checkEqual(t, reference.FamiliarName(repoInfo.Name), expectedRepoInfo.LocalName, reposName)
-			checkEqual(t, repoInfo.Name.Name(), expectedRepoInfo.CanonicalName, reposName)
-			checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
-			checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
+			assert.Check(t, is.Equal(repoInfo.Index.Name, expectedRepoInfo.Index.Name), reposName)
+			assert.Check(t, is.Equal(reference.Path(repoInfo.Name), expectedRepoInfo.RemoteName), reposName)
+			assert.Check(t, is.Equal(reference.FamiliarName(repoInfo.Name), expectedRepoInfo.LocalName), reposName)
+			assert.Check(t, is.Equal(repoInfo.Name.Name(), expectedRepoInfo.CanonicalName), reposName)
+			assert.Check(t, is.Equal(repoInfo.Index.Official, expectedRepoInfo.Index.Official), reposName)
+			assert.Check(t, is.Equal(repoInfo.Official, expectedRepoInfo.Official), reposName)
 		}
 	}
 }
 
 func TestNewIndexInfo(t *testing.T) {
-	testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registrytypes.IndexInfo) {
+	testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registry.IndexInfo) {
 		for indexName, expectedIndexInfo := range expectedIndexInfos {
 			index, err := newIndexInfo(config, indexName)
 			if err != nil {
 				t.Fatal(err)
 			} else {
-				checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
-				checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
-				checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
-				checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
+				assert.Check(t, is.Equal(index.Name, expectedIndexInfo.Name), indexName+" name")
+				assert.Check(t, is.Equal(index.Official, expectedIndexInfo.Official), indexName+" is official")
+				assert.Check(t, is.Equal(index.Secure, expectedIndexInfo.Secure), indexName+" is secure")
+				assert.Check(t, is.Equal(len(index.Mirrors), len(expectedIndexInfo.Mirrors)), indexName+" mirrors")
 			}
 		}
 	}
 
 	config := emptyServiceConfig
 	var noMirrors []string
-	expectedIndexInfos := map[string]*registrytypes.IndexInfo{
+	expectedIndexInfos := map[string]*registry.IndexInfo{
 		IndexName: {
 			Name:     IndexName,
 			Official: true,
@@ -421,7 +422,7 @@ func TestNewIndexInfo(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	expectedIndexInfos = map[string]*registrytypes.IndexInfo{
+	expectedIndexInfos = map[string]*registry.IndexInfo{
 		IndexName: {
 			Name:     IndexName,
 			Official: true,
@@ -471,7 +472,7 @@ func TestNewIndexInfo(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	expectedIndexInfos = map[string]*registrytypes.IndexInfo{
+	expectedIndexInfos = map[string]*registry.IndexInfo{
 		"example.com": {
 			Name:     "example.com",
 			Official: false,
@@ -520,7 +521,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	s := DefaultService{config: cfg}
+	s := defaultService{config: cfg}
 
 	imageName, err := reference.WithName(IndexName + "/test/image")
 	if err != nil {
@@ -545,16 +546,16 @@ func TestMirrorEndpointLookup(t *testing.T) {
 
 func TestSearchRepositories(t *testing.T) {
 	r := spawnTestRegistrySession(t)
-	results, err := r.SearchRepositories("fakequery", 25)
+	results, err := r.searchRepositories("fakequery", 25)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if results == nil {
 		t.Fatal("Expected non-nil SearchResults object")
 	}
-	assertEqual(t, results.NumResults, 1, "Expected 1 search results")
-	assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query")
-	assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
+	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) {
@@ -580,7 +581,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
 		reqFrom.Header.Add("Authorization", "super_secret")
 		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
 
-		addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
+		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
 
 		if len(reqTo.Header) != 1 {
 			t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
@@ -604,7 +605,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
 		reqFrom.Header.Add("Authorization", "super_secret")
 		reqTo, _ := http.NewRequest(http.MethodGet, urls[1], nil)
 
-		addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
+		_ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
 
 		if len(reqTo.Header) != 2 {
 			t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
@@ -659,7 +660,7 @@ func TestAllowNondistributableArtifacts(t *testing.T) {
 		if err != nil {
 			t.Error(err)
 		}
-		if v := allowNondistributableArtifacts(config, tt.addr); v != tt.expected {
+		if v := config.allowNondistributableArtifacts(tt.addr); v != tt.expected {
 			t.Errorf("allowNondistributableArtifacts failed for %q %v, expected %v got %v", tt.addr, tt.registries, tt.expected, v)
 		}
 	}
@@ -702,7 +703,7 @@ func TestIsSecureIndex(t *testing.T) {
 		if err != nil {
 			t.Error(err)
 		}
-		if sec := isSecureIndex(config, tt.addr); sec != tt.expected {
+		if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
 			t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
 		}
 	}

+ 49 - 78
registry/service.go

@@ -11,9 +11,8 @@ import (
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
@@ -28,85 +27,64 @@ type Service interface {
 	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 *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
-	ServiceConfig() *registrytypes.ServiceConfig
+	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
+	ServiceConfig() *registry.ServiceConfig
 	TLSConfig(hostname string) (*tls.Config, error)
 	LoadAllowNondistributableArtifacts([]string) error
 	LoadMirrors([]string) error
 	LoadInsecureRegistries([]string) error
 }
 
-// DefaultService is a registry service. It tracks configuration data such as a list
+// defaultService is a registry service. It tracks configuration data such as a list
 // of mirrors.
-type DefaultService struct {
+type defaultService struct {
 	config *serviceConfig
-	mu     sync.Mutex
+	mu     sync.RWMutex
 }
 
-// NewService returns a new instance of DefaultService ready to be
+// NewService returns a new instance of defaultService ready to be
 // installed into an engine.
-func NewService(options ServiceOptions) (*DefaultService, error) {
+func NewService(options ServiceOptions) (Service, error) {
 	config, err := newServiceConfig(options)
 
-	return &DefaultService{config: config}, err
+	return &defaultService{config: config}, err
 }
 
-// ServiceConfig returns the public registry service configuration.
-func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	servConfig := registrytypes.ServiceConfig{
-		AllowNondistributableArtifactsCIDRs:     make([]*(registrytypes.NetIPNet), 0),
-		AllowNondistributableArtifactsHostnames: make([]string, 0),
-		InsecureRegistryCIDRs:                   make([]*(registrytypes.NetIPNet), 0),
-		IndexConfigs:                            make(map[string]*(registrytypes.IndexInfo)),
-		Mirrors:                                 make([]string, 0),
-	}
-
-	// construct a new ServiceConfig which will not retrieve s.Config directly,
-	// and look up items in s.config with mu locked
-	servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
-	servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
-	servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
-
-	for key, value := range s.config.ServiceConfig.IndexConfigs {
-		servConfig.IndexConfigs[key] = value
-	}
-
-	servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
-
-	return &servConfig
+// ServiceConfig returns a copy of the public registry service's configuration.
+func (s *defaultService) 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 *defaultService) LoadAllowNondistributableArtifacts(registries []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	return s.config.LoadAllowNondistributableArtifacts(registries)
+	return s.config.loadAllowNondistributableArtifacts(registries)
 }
 
 // LoadMirrors loads registry mirrors for Service
-func (s *DefaultService) LoadMirrors(mirrors []string) error {
+func (s *defaultService) LoadMirrors(mirrors []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	return s.config.LoadMirrors(mirrors)
+	return s.config.loadMirrors(mirrors)
 }
 
 // LoadInsecureRegistries loads insecure registries for Service
-func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
+func (s *defaultService) LoadInsecureRegistries(registries []string) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	return s.config.LoadInsecureRegistries(registries)
+	return s.config.loadInsecureRegistries(registries)
 }
 
 // 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 *types.AuthConfig, userAgent string) (status, token string, err error) {
+func (s *defaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
 	// TODO Use ctx when searching for repositories
 	var registryHostName = IndexHostname
 
@@ -117,7 +95,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
 		}
 		u, err := url.Parse(serverAddress)
 		if err != nil {
-			return "", "", errdefs.InvalidParameter(errors.Errorf("unable to parse server address: %v", err))
+			return "", "", invalidParamWrapf(err, "unable to parse server address")
 		}
 		registryHostName = u.Host
 	}
@@ -127,7 +105,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
 	// to a mirror.
 	endpoints, err := s.LookupPushEndpoints(registryHostName)
 	if err != nil {
-		return "", "", errdefs.InvalidParameter(err)
+		return "", "", invalidParam(err)
 	}
 
 	for _, endpoint := range endpoints {
@@ -159,25 +137,28 @@ func splitReposSearchTerm(reposName string) (string, string) {
 
 // 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 *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
+func (s *defaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
 	// TODO Use ctx when searching for repositories
-	if err := validateNoScheme(term); err != nil {
-		return nil, err
+	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.Lock()
+	s.mu.RLock()
 	index, err := newIndexInfo(s.config, indexName)
-	s.mu.Unlock()
+	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/")
+	}
 
-	// *TODO: Search multiple indexes.
-	endpoint, err := NewV1Endpoint(index, userAgent, headers)
+	endpoint, err := newV1Endpoint(index, userAgent, headers)
 	if err != nil {
 		return nil, err
 	}
@@ -196,7 +177,7 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
 		v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
 		if err != nil {
 			if fErr, ok := err.(fallbackError); ok {
-				logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
+				logrus.WithError(fErr.err).Error("cannot use identity token for search, v2 auth not supported")
 			} else {
 				return nil, err
 			}
@@ -218,20 +199,14 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
 		}
 	}
 
-	r := newSession(client, authConfig, endpoint)
-
-	if index.Official {
-		// If pull "library/foo", it's stored locally under "foo"
-		remoteName = strings.TrimPrefix(remoteName, "library/")
-	}
-	return r.SearchRepositories(remoteName, limit)
+	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) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
 	return newRepositoryInfo(s.config, name)
 }
 
@@ -247,32 +222,28 @@ type APIEndpoint struct {
 }
 
 // TLSConfig constructs a client TLS configuration based on server defaults
-func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	return s.tlsConfig(hostname)
-}
+func (s *defaultService) TLSConfig(hostname string) (*tls.Config, error) {
+	s.mu.RLock()
+	secure := s.config.isSecureIndex(hostname)
+	s.mu.RUnlock()
 
-// tlsConfig constructs a client TLS configuration based on server defaults
-func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
-	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
+	return newTLSConfig(hostname, secure)
 }
 
 // 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) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
 
 	return s.lookupV2Endpoints(hostname)
 }
 
 // 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) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
 
 	allEndpoints, err := s.lookupV2Endpoints(hostname)
 	if err == nil {

+ 6 - 8
registry/service_v2.go

@@ -7,8 +7,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 )
 
-func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
-	tlsConfig := tlsconfig.ServerDefault()
+func (s *defaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	if hostname == DefaultNamespace || hostname == IndexHostname {
 		for _, mirror := range s.config.Mirrors {
 			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
@@ -16,9 +15,9 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 			}
 			mirrorURL, err := url.Parse(mirror)
 			if err != nil {
-				return nil, err
+				return nil, invalidParam(err)
 			}
-			mirrorTLSConfig, err := s.tlsConfig(mirrorURL.Host)
+			mirrorTLSConfig, err := newTLSConfig(mirrorURL.Host, s.config.isSecureIndex(mirrorURL.Host))
 			if err != nil {
 				return nil, err
 			}
@@ -35,19 +34,18 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 			Version:      APIVersion2,
 			Official:     true,
 			TrimHostname: true,
-			TLSConfig:    tlsConfig,
+			TLSConfig:    tlsconfig.ServerDefault(),
 		})
 
 		return endpoints, nil
 	}
 
-	ana := allowNondistributableArtifacts(s.config, hostname)
-
-	tlsConfig, err = s.tlsConfig(hostname)
+	tlsConfig, err := newTLSConfig(hostname, s.config.isSecureIndex(hostname))
 	if err != nil {
 		return nil, err
 	}
 
+	ana := s.config.allowNondistributableArtifacts(hostname)
 	endpoints = []APIEndpoint{
 		{
 			URL: &url.URL{

+ 15 - 28
registry/session.go

@@ -12,7 +12,7 @@ import (
 	"sync"
 
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/jsonmessage"
@@ -21,13 +21,11 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-// A Session is used to communicate with a V1 registry
-type Session struct {
-	indexEndpoint *V1Endpoint
+// A session is used to communicate with a V1 registry
+type session struct {
+	indexEndpoint *v1Endpoint
 	client        *http.Client
-	// TODO(tiborvass): remove authConfig
-	authConfig *types.AuthConfig
-	id         string
+	id            string
 }
 
 type authTransport struct {
@@ -149,13 +147,13 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
 	}
 }
 
-func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) error {
+func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *v1Endpoint) error {
 	var alwaysSetBasicAuth bool
 
 	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
 	// alongside all our requests.
 	if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
-		info, err := endpoint.Ping()
+		info, err := endpoint.ping()
 		if err != nil {
 			return err
 		}
@@ -171,43 +169,32 @@ func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint
 
 	jar, err := cookiejar.New(nil)
 	if err != nil {
-		return errors.New("cookiejar.New is not supposed to return an error")
+		return errdefs.System(errors.New("cookiejar.New is not supposed to return an error"))
 	}
 	client.Jar = jar
 
 	return nil
 }
 
-func newSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) *Session {
-	return &Session{
-		authConfig:    authConfig,
+func newSession(client *http.Client, endpoint *v1Endpoint) *session {
+	return &session{
 		client:        client,
 		indexEndpoint: endpoint,
 		id:            stringid.GenerateRandomID(),
 	}
 }
 
-// NewSession creates a new session
-// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
-func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) (*Session, error) {
-	if err := authorizeClient(client, authConfig, endpoint); err != nil {
-		return nil, err
-	}
-
-	return newSession(client, authConfig, endpoint), nil
-}
-
-// SearchRepositories performs a search against the remote repository
-func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
+// searchRepositories performs a search against the remote repository
+func (r *session) searchRepositories(term string, limit int) (*registry.SearchResults, error) {
 	if limit < 1 || limit > 100 {
-		return nil, errdefs.InvalidParameter(errors.Errorf("Limit %d is outside the range of [1, 100]", limit))
+		return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit)
 	}
 	logrus.Debugf("Index server: %s", r.indexEndpoint)
 	u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
 
 	req, err := http.NewRequest(http.MethodGet, u, nil)
 	if err != nil {
-		return nil, errors.Wrap(errdefs.InvalidParameter(err), "Error building request")
+		return nil, invalidParamWrapf(err, "error building request")
 	}
 	// Have the AuthTransport send authentication, when logged in.
 	req.Header.Set("X-Docker-Token", "true")
@@ -222,6 +209,6 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea
 			Code:    res.StatusCode,
 		}
 	}
-	result := new(registrytypes.SearchResults)
+	result := new(registry.SearchResults)
 	return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
 }

+ 2 - 32
registry/types.go

@@ -2,39 +2,9 @@ package registry // import "github.com/docker/docker/registry"
 
 import (
 	"github.com/docker/distribution/reference"
-	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/api/types/registry"
 )
 
-// RepositoryData tracks the image list, list of endpoints for a repository
-type RepositoryData struct {
-	// ImgList is a list of images in the repository
-	ImgList map[string]*ImgData
-	// Endpoints is a list of endpoints returned in X-Docker-Endpoints
-	Endpoints []string
-}
-
-// ImgData is used to transfer image checksums to and from the registry
-type ImgData struct {
-	// ID is an opaque string that identifies the image
-	ID              string `json:"id"`
-	Checksum        string `json:"checksum,omitempty"`
-	ChecksumPayload string `json:"-"`
-	Tag             string `json:",omitempty"`
-}
-
-// PingResult contains the information returned when pinging a registry. It
-// indicates the registry's version and whether the registry claims to be a
-// standalone registry.
-type PingResult struct {
-	// Version is the registry version supplied by the registry in an HTTP
-	// header
-	Version string `json:"version"`
-	// Standalone is set to true if the registry indicates it is a
-	// standalone registry in the X-Docker-Registry-Standalone
-	// header
-	Standalone bool `json:"standalone"`
-}
-
 // APIVersion is an integral representation of an API version (presently
 // either 1 or 2)
 type APIVersion int
@@ -58,7 +28,7 @@ var apiVersions = map[APIVersion]string{
 type RepositoryInfo struct {
 	Name reference.Named
 	// Index points to registry information
-	Index *registrytypes.IndexInfo
+	Index *registry.IndexInfo
 	// Official indicates whether the repository is considered official.
 	// If the registry is official, and the normalized name does not
 	// contain a '/' (e.g. "foo"), then it is considered an official repo.