docker/patch/0158-docker-support-private-registry.patch
Grooooot e7de2c79b3 docker: add patches
Signed-off-by: Grooooot <isula@huawei.com>
2020-03-05 15:13:09 +08:00

905 lines
29 KiB
Diff

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