From ca795c91b91ea38ce26616825c646f59a746edde Mon Sep 17 00:00:00 2001
From: jiangpengfei <jiangpengfei9@huawei.com>
Date: Mon, 30 Sep 2019 14:15:45 -0400
Subject: [PATCH] docker: support private registry

reason:
1. add registries config to support downnload private registry image
2. add LLT testcases for registries config

Change-Id: Icd77363c6c2024e9ece0b79e65aeaee3af928caa
Signed-off-by: jiangpengfei <jiangpengfei9@huawei.com>
---
 components/engine/api/types/registry/registry.go   | 162 ++++++++++++++++++++-
 .../engine/api/types/registry/registry_test.go     |  73 ++++++++++
 components/engine/cmd/dockerd/daemon_test.go       |  24 +++
 components/engine/daemon/config/config.go          |  13 ++
 components/engine/daemon/reload.go                 |  26 ++++
 components/engine/distribution/pull_v2.go          |  26 +++-
 components/engine/distribution/push_v2_test.go     |   4 +
 components/engine/opts/opts.go                     |  34 +++++
 components/engine/registry/config.go               |  24 ++-
 components/engine/registry/service.go              |  12 ++
 components/engine/registry/service_v2.go           |  98 +++++++++----
 components/engine/registry/service_v2_test.go      | 104 +++++++++++++
 12 files changed, 564 insertions(+), 36 deletions(-)
 create mode 100644 components/engine/api/types/registry/registry_test.go
 create mode 100644 components/engine/registry/service_v2_test.go

diff --git a/components/engine/api/types/registry/registry.go b/components/engine/api/types/registry/registry.go
index 8789ad3..1ebf18b 100644
--- a/components/engine/api/types/registry/registry.go
+++ b/components/engine/api/types/registry/registry.go
@@ -2,9 +2,25 @@ package registry // import "github.com/docker/docker/api/types/registry"
 
 import (
 	"encoding/json"
+	"fmt"
 	"net"
+	"net/url"
+	"regexp"
+	"strings"
 
-	"github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/docker/distribution/reference"
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+var (
+	// DefaultEndpoint for docker.io
+	DefaultEndpoint = Endpoint{
+		Address: "https://registry-1.docker.io",
+		url: url.URL{
+			Scheme: "https",
+			Host:   "registry-1.docker.io",
+		},
+	}
 )
 
 // ServiceConfig stores daemon registry services configuration.
@@ -14,6 +30,150 @@ type ServiceConfig struct {
 	InsecureRegistryCIDRs                   []*NetIPNet           `json:"InsecureRegistryCIDRs"`
 	IndexConfigs                            map[string]*IndexInfo `json:"IndexConfigs"`
 	Mirrors                                 []string
+	Registries                              Registries
+}
+
+// Registries is a slice of type Registry.
+type Registries []Registry
+
+// Registry includes all data relevant for the lookup of push and pull
+// endpoints.
+type Registry struct {
+	// Pattern is a string contains the registry domain name which pull/push
+	// images directly, don't need to convert to pull from mirror registry
+	Pattern string `json:"pattern"`
+	// Mirrors is a slice contains registry mirror url address
+	Mirrors       []Endpoint `json:"mirrors"`
+	patternRegexp *regexp.Regexp
+}
+
+// Endpoint includes all data associated with a given registry endpoint.
+type Endpoint struct {
+	// Address is the endpoints base URL when assembling a repository in a
+	// registry (e.g., "registry.com:5000/v2").
+	Address string `json:"address"`
+	// url is used during endpoint lookup and avoids to redundantly parse
+	// Address when the Endpoint is used.
+	url url.URL
+	// InsecureSkipVerify: if true, TLS accepts any certificate presented
+	// by the server and any host name in that certificate. In this mode,
+	// TLS is susceptible to man-in-the-middle attacks. This should be used
+	// only for testing
+	InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
+}
+
+// RewriteReference strips the prefix from ref and appends it to registry.
+// If the prefix is empty, ref remains unchanged.  An error is returned if
+// prefix doesn't prefix ref.
+func RewriteReference(ref reference.Named, prefix string, registry *url.URL) (reference.Named, error) {
+	// Sanity check the provided arguments
+	if ref == nil {
+		return nil, fmt.Errorf("provided reference is nil")
+	}
+	if registry == nil {
+		return nil, fmt.Errorf("provided registry is nil")
+	}
+
+	// don't rewrite the default endpoints
+	if *registry == DefaultEndpoint.url {
+		return ref, nil
+	}
+
+	if prefix == "" {
+		return ref, nil
+	}
+
+	baseAddress := strings.TrimPrefix(registry.String(), registry.Scheme+"://")
+
+	refStr := ref.String()
+	if !strings.HasPrefix(refStr, prefix) {
+		return nil, fmt.Errorf("unable to rewrite reference %q with prefix %q", refStr, prefix)
+	}
+	remainder := strings.TrimPrefix(refStr, prefix)
+	remainder = strings.TrimPrefix(remainder, "/")
+	baseAddress = strings.TrimSuffix(baseAddress, "/")
+
+	newRefStr := baseAddress + "/" + remainder
+	newRef, err := reference.ParseNamed(newRefStr)
+	if err != nil {
+		return nil, fmt.Errorf("unable to rewrite reference %q with prefix %q to %q: %v", refStr, prefix, newRefStr, err)
+	}
+	return newRef, nil
+}
+
+// GetURL returns the Endpoint's URL.
+func (r *Endpoint) GetURL() *url.URL {
+	// return the pointer of a copy
+	url := r.url
+	return &url
+}
+
+// MatchWhiteList return reference match the r.whiteListRegexp or not
+func (r *Registry) MatchPattern(reference string) bool {
+	if r.patternRegexp == nil {
+		return false
+	}
+
+	return r.patternRegexp.MatchString(reference)
+}
+
+// FindRegistry returns the Registry mirror url address if reference not in the whitelist
+// or nil if reference in the Registry whitelist.
+func (r Registries) FindRegistry(reference string) *Registry {
+	var reg *Registry = nil
+	for i := range r {
+		match := r[i].MatchPattern(reference)
+		if match {
+			reg = &r[i]
+			break
+		}
+	}
+
+	return reg
+}
+
+// Prepare must be called on each new Registry.  It sets up all specified push
+// and pull endpoints
+func (r *Registry) Prepare() error {
+	var err error
+	r.patternRegexp, err = regexp.Compile(r.Pattern)
+	if err != nil {
+		return fmt.Errorf("invalid pattern: %v", err)
+	}
+
+	prepareEndpoints := func(endpoints []Endpoint) ([]Endpoint, error) {
+		for i := range endpoints {
+			if err := endpoints[i].Prepare(); err != nil {
+				return nil, err
+			}
+		}
+
+		return endpoints, nil
+	}
+
+	if r.Mirrors, err = prepareEndpoints(r.Mirrors); err != nil {
+		return err
+	}
+
+	if len(r.Mirrors) == 0 {
+		return fmt.Errorf("Registry with whitelist %v without mirror endpoints", r.Pattern)
+	}
+
+	return nil
+}
+
+// Prepare sets up the Endpoint.
+func (r *Endpoint) Prepare() error {
+	if !strings.HasPrefix(r.Address, "http://") && !strings.HasPrefix(r.Address, "https://") {
+		return fmt.Errorf("%s: address must start with %q or %q", r.Address, "http://", "https://")
+	}
+
+	u, err := url.Parse(r.Address)
+	if err != nil {
+		return err
+	}
+	r.url = *u
+	return nil
 }
 
 // NetIPNet is the net.IPNet type, which can be marshalled and
diff --git a/components/engine/api/types/registry/registry_test.go b/components/engine/api/types/registry/registry_test.go
new file mode 100644
index 0000000..e532d4d
--- /dev/null
+++ b/components/engine/api/types/registry/registry_test.go
@@ -0,0 +1,73 @@
+package registry
+
+import (
+	"net/url"
+	"testing"
+
+	"github.com/docker/distribution/reference"
+	"gotest.tools/assert"
+)
+
+func TestRewriteReference(t *testing.T) {
+	var ref reference.Named
+	var prefix string
+	var registry *url.URL
+
+	// case 1: ref is nil
+	_, err := RewriteReference(ref, prefix, registry)
+	assert.ErrorContains(t, err, "provided reference is nil")
+
+	ref, err = reference.ParseNormalizedNamed("hello.com/official/busybox")
+	assert.NilError(t, err)
+
+	// case 2: registry is nil
+	_, err = RewriteReference(ref, prefix, registry)
+	assert.ErrorContains(t, err, "provided registry is nil")
+
+	registry = &url.URL{
+		Scheme: "https",
+		Host:   "exapmle.com",
+	}
+
+	// case 3: prefix is empty, expect nil
+	rewriteRef, err := RewriteReference(ref, prefix, registry)
+	assert.NilError(t, err)
+	assert.Equal(t, rewriteRef, ref)
+
+	// case 4: registry equal to DefaultEndpoint.url
+	registry = &url.URL{
+		Scheme: "https",
+		Host:   "registry-1.docker.io",
+	}
+	rewriteRef, err = RewriteReference(ref, prefix, registry)
+	assert.NilError(t, err)
+	assert.Equal(t, rewriteRef, ref)
+
+	// case 5: ref.String() doesn't have prefix
+	registry = &url.URL{
+		Scheme: "https",
+		Host:   "test.io",
+	}
+	prefix = "example.com"
+	rewriteRef, err = RewriteReference(ref, prefix, registry)
+	assert.ErrorContains(t, err, "unable to rewrite reference")
+
+	// case 6: registry host is invalid
+	prefix = "hello.com"
+	registry = &url.URL{
+		Scheme: "https",
+		Host:   "[?f,*fds",
+	}
+	rewriteRef, err = RewriteReference(ref, prefix, registry)
+	assert.ErrorContains(t, err, "unable to rewrite reference")
+
+	// case 7: everything is ok
+	registry = &url.URL{
+		Scheme: "https",
+		Host:   "test.io",
+	}
+	prefix = "hello.com"
+	rewriteRef, err = RewriteReference(ref, prefix, registry)
+	assert.NilError(t, err)
+	assert.Equal(t, rewriteRef.String(), "test.io/official/busybox")
+}
diff --git a/components/engine/cmd/dockerd/daemon_test.go b/components/engine/cmd/dockerd/daemon_test.go
index ad447e3..681bf87 100644
--- a/components/engine/cmd/dockerd/daemon_test.go
+++ b/components/engine/cmd/dockerd/daemon_test.go
@@ -180,3 +180,27 @@ func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) {
 	assert.Check(t, is.Len(loadedConfig.Mirrors, 1))
 	assert.Check(t, is.Len(loadedConfig.InsecureRegistries, 1))
 }
+
+func TestLoadDaemonConfigWithRegistriesOptions(t *testing.T) {
+	content := `{
+		"registries": [
+			{
+				"pattern": "xxx.com",
+				"mirrors": [
+					{
+						"address": "http://hello.mirror.com"
+					}
+				]
+			}
+		]
+	}`
+	tempFile := fs.NewFile(t, "config", fs.WithContent(content))
+	defer tempFile.Remove()
+
+	opts := defaultOptions(tempFile.Path())
+	loadedConfig, err := loadDaemonCliConfig(opts)
+	assert.NilError(t, err)
+	assert.Assert(t, loadedConfig != nil)
+
+	assert.Check(t, is.Len(loadedConfig.Registries, 1))
+}
diff --git a/components/engine/daemon/config/config.go b/components/engine/daemon/config/config.go
index 2141ce8..07d4c89 100644
--- a/components/engine/daemon/config/config.go
+++ b/components/engine/daemon/config/config.go
@@ -435,6 +435,10 @@ func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Con
 		return nil, err
 	}
 
+	if len(config.Mirrors) > 0 && len(config.Registries) > 0 {
+		return nil, fmt.Errorf("registry-mirror config conflict with registries config")
+	}
+
 	if config.RootDeprecated != "" {
 		logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`)
 
@@ -472,6 +476,10 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
 	unknownKeys := make(map[string]interface{})
 	for key, value := range config {
 		if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] {
+			// skip config-only flags
+			if key == "registries" {
+				continue
+			}
 			unknownKeys[key] = value
 		}
 	}
@@ -579,6 +587,11 @@ func Validate(config *Config) error {
 		}
 	}
 
+	// validate registries mirror settings
+	if err := opts.ValidateRegistries(config.Registries); err != nil {
+		return err
+	}
+
 	// validate platform-specific settings
 	return config.ValidatePlatformConfig()
 }
diff --git a/components/engine/daemon/reload.go b/components/engine/daemon/reload.go
index 026d7dd..b8132cc 100644
--- a/components/engine/daemon/reload.go
+++ b/components/engine/daemon/reload.go
@@ -65,6 +65,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
 	if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
 		return err
 	}
+	if err := daemon.reloadRegistries(conf, attributes);err != nil {
+		return err
+	}
 	return daemon.reloadNetworkDiagnosticPort(conf, attributes)
 }
 
@@ -294,6 +297,29 @@ func (daemon *Daemon) reloadRegistryMirrors(conf *config.Config, attributes map[
 	return nil
 }
 
+// reloadRegistries updates the registries configuration and the passed attributes
+func (daemon *Daemon) reloadRegistries(conf *config.Config, attributes map[string]string) error {
+	// update corresponding configuration
+	if conf.IsValueSet("registries") {
+		daemon.configStore.Registries = conf.Registries
+		if err := daemon.RegistryService.LoadRegistries(conf.Registries); err != nil {
+			return err
+		}
+	}
+
+	// prepare reload event attributes with updatable configurations
+	if daemon.configStore.Registries != nil {
+		registries, err := json.Marshal(daemon.configStore.Registries)
+		if err != nil {
+			return err
+		}
+		attributes["registries"] = string(registries)
+	} else {
+		attributes["registries"] = "[]"
+	}
+	return nil
+}
+
 // reloadLiveRestore updates configuration with live retore option
 // and updates the passed attributes
 func (daemon *Daemon) reloadLiveRestore(conf *config.Config, attributes map[string]string) error {
diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go
index 99cee79..4150241 100644
--- a/components/engine/distribution/pull_v2.go
+++ b/components/engine/distribution/pull_v2.go
@@ -20,10 +20,11 @@ import (
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/transport"
+	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/image"
-	"github.com/docker/docker/image/v1"
+	v1 "github.com/docker/docker/image/v1"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/progress"
@@ -66,6 +67,10 @@ type v2Puller struct {
 
 func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) {
 	// TODO(tiborvass): was ReceiveTimeout
+	if p.endpoint.Prefix != "" {
+		p.config.MetaHeaders["Docker-Prefix"] = []string{p.endpoint.Prefix}
+	}
+
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
 	if err != nil {
 		logrus.Warnf("Error getting v2 registry: %v", err)
@@ -334,6 +339,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
 		return false, err
 	}
 
+	var pullRef reference.Named = ref
+	if len(p.endpoint.Prefix) != 0 {
+		// Note that pullRef is only used for pulling while ref is used as
+		// the reference for storing the image
+		pullRef, err = registrytypes.RewriteReference(ref, p.endpoint.Prefix, p.endpoint.URL)
+		if err != nil {
+			return false, err
+		}
+		logrus.Infof("rewriting %q to %q", ref.String(), pullRef.String())
+	}
+
 	var (
 		manifest    distribution.Manifest
 		tagOrDigest string // Used for logging/progress only
@@ -379,7 +395,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
 	// the other side speaks the v2 protocol.
 	p.confirmedV2 = true
 
-	logrus.Debugf("Pulling ref from V2 registry: %s", reference.FamiliarString(ref))
+	logrus.Debugf("Pulling ref %q from V2 registry: %s", ref, p.endpoint.URL)
 	progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named()))
 
 	var (
@@ -392,18 +408,18 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
 		if p.config.RequireSchema2 {
 			return false, fmt.Errorf("invalid manifest: not schema2")
 		}
-		id, manifestDigest, err = p.pullSchema1(ctx, ref, v, platform)
+		id, manifestDigest, err = p.pullSchema1(ctx, pullRef, v, platform)
 		if err != nil {
 			return false, err
 		}
 	case *schema2.DeserializedManifest:
-		id, manifestDigest, err = p.pullSchema2(ctx, ref, v, platform)
+		id, manifestDigest, err = p.pullSchema2(ctx, pullRef, v, platform)
 		if err != nil {
 			logrus.Errorf("try to pull schema2 failed. manifest: %+v", manifest.References())
 			return false, err
 		}
 	case *manifestlist.DeserializedManifestList:
-		id, manifestDigest, err = p.pullManifestList(ctx, ref, v, platform)
+		id, manifestDigest, err = p.pullManifestList(ctx, pullRef, v, platform)
 		if err != nil {
 			logrus.Errorf("try to get manifest data from storage failed. manifest: %+v", manifest.References())
 			return false, err
diff --git a/components/engine/distribution/push_v2_test.go b/components/engine/distribution/push_v2_test.go
index 436b4a1..8d39403 100644
--- a/components/engine/distribution/push_v2_test.go
+++ b/components/engine/distribution/push_v2_test.go
@@ -488,6 +488,10 @@ func (s *mockReferenceStore) Get(ref reference.Named) (digest.Digest, error) {
 	return "", nil
 }
 
+func (s *mockReferenceStore) List() []digest.Digest {
+	return []digest.Digest{}
+}
+
 func TestWhenEmptyAuthConfig(t *testing.T) {
 	for _, authInfo := range []struct {
 		username      string
diff --git a/components/engine/opts/opts.go b/components/engine/opts/opts.go
index de8aacb..db63aa6 100644
--- a/components/engine/opts/opts.go
+++ b/components/engine/opts/opts.go
@@ -7,6 +7,7 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/go-units"
 )
 
@@ -15,6 +16,11 @@ var (
 	domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
 )
 
+const (
+	maxRegistryNum  = 100
+	maxMirrorNumber = 100
+)
+
 // ListOpts holds a list of values and a validation function.
 type ListOpts struct {
 	values    *[]string
@@ -273,6 +279,34 @@ func ValidateSingleGenericResource(val string) (string, error) {
 	return val, nil
 }
 
+func ValidateRegistries(registries registry.Registries) error {
+	if len(registries) == 0 {
+		return nil
+	}
+
+	if len(registries) > maxRegistryNum {
+		return fmt.Errorf("registries config registry number should not larger than %d", maxRegistryNum)
+	}
+
+	for _, reg := range registries {
+		if len(reg.Pattern) == 0 || len(reg.Mirrors) == 0 {
+			return fmt.Errorf("registry pattern and mirrors is required, should not be empty")
+		}
+
+		if len(reg.Mirrors) > maxMirrorNumber {
+			return fmt.Errorf("registry mirrors number should not larger than %d", maxMirrorNumber)
+		}
+
+		for _, mirror := range reg.Mirrors {
+			if len(mirror.Address) == 0 {
+				return fmt.Errorf("mirror address is required, should not be empty")
+			}
+		}
+	}
+
+	return nil
+}
+
 // ParseLink parses and validates the specified string as a link format (name:alias)
 func ParseLink(val string) (string, string, error) {
 	if val == "" {
diff --git a/components/engine/registry/config.go b/components/engine/registry/config.go
index ea491b9..9c2b762 100644
--- a/components/engine/registry/config.go
+++ b/components/engine/registry/config.go
@@ -20,6 +20,10 @@ type ServiceOptions struct {
 	Mirrors                        []string `json:"registry-mirrors,omitempty"`
 	InsecureRegistries             []string `json:"insecure-registries,omitempty"`
 
+	// Registries holds information associated with registries and their
+	// push and pull mirrors.
+	Registries registrytypes.Registries `json:"registries,omitempty"`
+
 	// V2Only controls access to legacy registries.  If it is set to true via the
 	// command line flag the daemon will not attempt to contact v1 legacy registries
 	V2Only bool `json:"disable-legacy-registry,omitempty"`
@@ -97,6 +101,9 @@ func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
 	if err := config.LoadInsecureRegistries(options.InsecureRegistries); err != nil {
 		return nil, err
 	}
+	if err := config.LoadRegistries(options.Registries); err != nil {
+		return nil, err
+	}
 
 	return config, nil
 }
@@ -248,7 +255,22 @@ skip:
 	return nil
 }
 
-// allowNondistributableArtifacts returns true if the provided hostname is part of the list of registries
+// LoadRegistries loads the user-specified configuration options for registries
+func (config *serviceConfig) LoadRegistries(registries registrytypes.Registries) error {
+	for _, registry := range registries {
+		if err := registry.Prepare(); err != nil {
+			return err
+		}
+		config.Registries = append(config.Registries, registry)
+	}
+
+	for i, r := range config.Registries {
+		logrus.Infof("REGISTRY %d: %v", i, r)
+	}
+	return nil
+}
+
+// allowNondistributableArtifacts returns true if the provided hostname is part of the list of regsitries
 // that allow push of nondistributable artifacts.
 //
 // The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
diff --git a/components/engine/registry/service.go b/components/engine/registry/service.go
index d38f44b..8530f97 100644
--- a/components/engine/registry/service.go
+++ b/components/engine/registry/service.go
@@ -34,6 +34,7 @@ type Service interface {
 	LoadAllowNondistributableArtifacts([]string) error
 	LoadMirrors([]string) error
 	LoadInsecureRegistries([]string) error
+	LoadRegistries(registrytypes.Registries) error
 }
 
 // DefaultService is a registry service. It tracks configuration data such as a list
@@ -64,6 +65,7 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 		InsecureRegistryCIDRs:                   make([]*(registrytypes.NetIPNet), 0),
 		IndexConfigs:                            make(map[string]*(registrytypes.IndexInfo)),
 		Mirrors:                                 make([]string, 0),
+		Registries:                              make([]registrytypes.Registry, 0),
 	}
 
 	// construct a new ServiceConfig which will not retrieve s.Config directly,
@@ -77,6 +79,7 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 	}
 
 	servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
+	servConfig.Registries = append(servConfig.Registries, s.config.ServiceConfig.Registries...)
 
 	return &servConfig
 }
@@ -105,6 +108,14 @@ func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
 	return s.config.LoadInsecureRegistries(registries)
 }
 
+// LoadRegistries loads registries for Service
+func (s *DefaultService) LoadRegistries(registries registrytypes.Registries) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	return s.config.LoadRegistries(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.
@@ -258,6 +269,7 @@ type APIEndpoint struct {
 	Official                       bool
 	TrimHostname                   bool
 	TLSConfig                      *tls.Config
+	Prefix                         string
 }
 
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
diff --git a/components/engine/registry/service_v2.go b/components/engine/registry/service_v2.go
index 3a56dc9..adeb10c 100644
--- a/components/engine/registry/service_v2.go
+++ b/components/engine/registry/service_v2.go
@@ -1,47 +1,87 @@
 package registry // import "github.com/docker/docker/registry"
 
 import (
+	"crypto/tls"
 	"net/url"
 	"strings"
 
+	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/go-connections/tlsconfig"
 )
 
 func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
-	tlsConfig := tlsconfig.ServerDefault()
-	if hostname == DefaultNamespace || hostname == IndexHostname {
-		// v2 mirrors
-		for _, mirror := range s.config.Mirrors {
-			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
-				mirror = "https://" + mirror
-			}
-			mirrorURL, err := url.Parse(mirror)
-			if err != nil {
-				return nil, err
+	var tlsConfig *tls.Config
+
+	// if s.config.Registries is set, lookup regsitry mirror addr from s.config.Registries
+	if len(s.config.Registries) > 0 {
+		reg := s.config.Registries.FindRegistry(hostname)
+
+		if reg != nil {
+			var regEndpoints []registrytypes.Endpoint = reg.Mirrors
+
+			lastIndex := len(regEndpoints) - 1
+			for i, regEP := range regEndpoints {
+				official := regEP.Address == registrytypes.DefaultEndpoint.Address
+				regURL := regEP.GetURL()
+
+				if official {
+					tlsConfig = tlsconfig.ServerDefault()
+				} else {
+					tlsConfig, err = s.tlsConfigForMirror(regURL)
+					if err != nil {
+						return nil, err
+					}
+				}
+				tlsConfig.InsecureSkipVerify = regEP.InsecureSkipVerify
+				endpoints = append(endpoints, APIEndpoint{
+					URL:          regURL,
+					Version:      APIVersion2,
+					Official:     official,
+					TrimHostname: true,
+					TLSConfig:    tlsConfig,
+					Prefix:       hostname,
+					// the last endpoint is not considered a mirror
+					Mirror: i != lastIndex,
+				})
 			}
-			mirrorTLSConfig, err := s.tlsConfigForMirror(mirrorURL)
-			if err != nil {
-				return nil, err
+			return endpoints, nil
+		}
+	} else {
+		tlsConfig = tlsconfig.ServerDefault()
+		if hostname == DefaultNamespace || hostname == IndexHostname {
+			// v2 mirrors
+			for _, mirror := range s.config.Mirrors {
+				if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
+					mirror = "https://" + mirror
+				}
+				mirrorURL, err := url.Parse(mirror)
+				if err != nil {
+					return nil, err
+				}
+				mirrorTLSConfig, err := s.tlsConfigForMirror(mirrorURL)
+				if err != nil {
+					return nil, err
+				}
+				endpoints = append(endpoints, APIEndpoint{
+					URL: mirrorURL,
+					// guess mirrors are v2
+					Version:      APIVersion2,
+					Mirror:       true,
+					TrimHostname: true,
+					TLSConfig:    mirrorTLSConfig,
+				})
 			}
+			// v2 registry
 			endpoints = append(endpoints, APIEndpoint{
-				URL: mirrorURL,
-				// guess mirrors are v2
+				URL:          DefaultV2Registry,
 				Version:      APIVersion2,
-				Mirror:       true,
+				Official:     true,
 				TrimHostname: true,
-				TLSConfig:    mirrorTLSConfig,
+				TLSConfig:    tlsConfig,
 			})
-		}
-		// v2 registry
-		endpoints = append(endpoints, APIEndpoint{
-			URL:          DefaultV2Registry,
-			Version:      APIVersion2,
-			Official:     true,
-			TrimHostname: true,
-			TLSConfig:    tlsConfig,
-		})
 
-		return endpoints, nil
+			return endpoints, nil
+		}
 	}
 
 	ana := allowNondistributableArtifacts(s.config, hostname)
@@ -57,7 +97,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 				Scheme: "https",
 				Host:   hostname,
 			},
-			Version: APIVersion2,
+			Version:                        APIVersion2,
 			AllowNondistributableArtifacts: ana,
 			TrimHostname:                   true,
 			TLSConfig:                      tlsConfig,
@@ -70,7 +110,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
 				Scheme: "http",
 				Host:   hostname,
 			},
-			Version: APIVersion2,
+			Version:                        APIVersion2,
 			AllowNondistributableArtifacts: ana,
 			TrimHostname:                   true,
 			// used to check if supposed to be secure via InsecureSkipVerify
diff --git a/components/engine/registry/service_v2_test.go b/components/engine/registry/service_v2_test.go
new file mode 100644
index 0000000..02c954b
--- /dev/null
+++ b/components/engine/registry/service_v2_test.go
@@ -0,0 +1,104 @@
+package registry
+
+import (
+	"testing"
+	"gotest.tools/assert"
+
+	registrytypes "github.com/docker/docker/api/types/registry"
+)
+
+func TestLookupV2Endpoints(t *testing.T) {
+	// case 1: doesn't call r.Prepare(), expect use default
+	r := registrytypes.Registry{
+		Pattern: "hello.com",
+		Mirrors: []registrytypes.Endpoint{
+			{
+				Address:            "http://docker.com",
+				InsecureSkipVerify: false,
+			},
+		},
+	}
+
+	s, err := NewService(ServiceOptions{
+		Registries: registrytypes.Registries{
+			r,
+		},
+	})
+
+	_, err = s.lookupV2Endpoints("hello.com")
+	assert.NilError(t, err)
+
+	// case 2: everything is ok
+	err = r.Prepare()
+	assert.NilError(t, err)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = s.lookupV2Endpoints("hello.com")
+	assert.NilError(t, err)
+
+	// case 3: Mirror Address is invalid, without http:// or https:// prefix
+	r = registrytypes.Registry{
+		Pattern: "hello.com",
+		Mirrors: []registrytypes.Endpoint{
+			{
+				Address:            "docker.com",
+				InsecureSkipVerify: false,
+			},
+		},
+	}
+
+	err = r.Prepare()
+	assert.ErrorContains(t, err, "address must start with")
+
+	// case 4: invalid pattern
+	r = registrytypes.Registry{
+		Pattern: "`[@1xxfdsaf",
+		Mirrors: []registrytypes.Endpoint{
+			{
+				Address:            "https://docker.com",
+				InsecureSkipVerify: false,
+			},
+		},
+	}
+
+	err = r.Prepare()
+	assert.ErrorContains(t, err, "invalid pattern")
+
+	// case 5: r.Mirrors is empty, expect error
+	r = registrytypes.Registry{
+		Pattern: "hello.com",
+		Mirrors: []registrytypes.Endpoint{},
+	}
+
+	err = r.Prepare()
+	assert.ErrorContains(t, err, "without mirror endpoints")
+
+	// case 6: lookupV2Endpoints doesn't match to registry pattern, expect no error, return default endpoints
+	r = registrytypes.Registry{
+		Pattern: "hello.com",
+		Mirrors: []registrytypes.Endpoint{
+			{
+				Address:            "http://docker.com",
+				InsecureSkipVerify: false,
+			},
+		},
+	}
+
+	err = r.Prepare()
+	assert.NilError(t, err)
+
+	s, err = NewService(ServiceOptions{
+		Registries: registrytypes.Registries{
+			r,
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = s.lookupV2Endpoints("example.com")
+	assert.NilError(t, err)
+}
-- 
1.8.3.1

