2019-05-03 18:22:55 +02:00
|
|
|
From 2a00f998e1e081a9f72f0ba81403dceea252c6a1 Mon Sep 17 00:00:00 2001
|
2018-08-20 10:55:46 +02:00
|
|
|
From: Valentin Rothberg <vrothberg@suse.com>
|
|
|
|
Date: Mon, 2 Jul 2018 13:37:34 +0200
|
|
|
|
Subject: [PATCH] Add private-registry mirror support
|
|
|
|
|
|
|
|
NOTE: This is a backport/downstream patch of the upstream pull-request
|
|
|
|
for Moby, which is still subject to changes. Please visit
|
|
|
|
https://github.com/moby/moby/pull/34319 for the current status.
|
|
|
|
|
|
|
|
Add support for mirroring private registries. The daemon.json config
|
|
|
|
can now be configured as exemplified below:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"registries": [
|
|
|
|
{
|
|
|
|
"Prefix": "docker.io/library/alpine",
|
|
|
|
"Mirrors": [
|
|
|
|
{
|
|
|
|
"URL": "http://local-alpine-mirror.lan"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Prefix": "registry.suse.com",
|
|
|
|
"Mirrors": [
|
|
|
|
{
|
|
|
|
"URL": "https://remote.suse.mirror.com"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Prefix": "http://insecure.registry.org:5000"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"registry-mirrors": ["https://deprecated-mirror.com"]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
With the new semantics, a mirror will be selected as an endpoint if the
|
|
|
|
specified prefix matches the prefix of the requested resource (e.g., an
|
|
|
|
image reference). In the upper example, "local-alpine-mirror" will only
|
|
|
|
serve as a mirror for docker.io if the requested resource matches the
|
|
|
|
"alpine" prefix, such as "alpine:latest" or "alpine-foo/bar".
|
|
|
|
|
|
|
|
Furthermore, private registries can now be mirrored as well. In the
|
|
|
|
example above, "remote.suse.mirror.com" will serve as a mirror for all
|
|
|
|
requests to "registry.suse.com". Notice that if no http{s,} scheme is
|
|
|
|
specified, the URI will always default to https without fallback to
|
|
|
|
http. An insecure registry can now be specified by adding the "http://"
|
|
|
|
scheme to the corresponding prefix.
|
|
|
|
|
|
|
|
Note that the configuration is sanity checked, so that a given mirror
|
|
|
|
can serve multiple prefixes if they all point to the same registry,
|
|
|
|
while a registry cannot simultaneously serve as a mirror. The daemon
|
|
|
|
will warn in case the URI schemes of a registry and one of its mirrors
|
|
|
|
do not correspond.
|
|
|
|
|
|
|
|
This change deprecates the "insecure-regestries" and "registry-mirrors"
|
|
|
|
options, while the "insecure-registries" cannot be used simultaneously
|
|
|
|
with the new "registries", which doesn't allow a fallback from https to
|
|
|
|
http for security reasons.
|
|
|
|
|
|
|
|
Signed-off-by: Flavio Castelli <fcastelli@suse.com>
|
|
|
|
Signed-off-by: Valentin Rothberg <vrothberg@suse.com>
|
2018-11-29 16:15:40 +01:00
|
|
|
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
2018-08-20 10:55:46 +02:00
|
|
|
---
|
|
|
|
.../engine/api/types/registry/registry.go | 144 ++++++++++++++++++
|
|
|
|
components/engine/daemon/config/config.go | 4 +
|
|
|
|
components/engine/daemon/reload.go | 33 ++++
|
|
|
|
components/engine/daemon/reload_test.go | 95 ++++++++++++
|
|
|
|
components/engine/distribution/pull.go | 2 +-
|
|
|
|
components/engine/distribution/pull_v2.go | 2 +-
|
|
|
|
components/engine/distribution/push.go | 2 +-
|
|
|
|
components/engine/registry/config.go | 120 ++++++++++++++-
|
|
|
|
components/engine/registry/config_test.go | 136 +++++++++++++++++
|
|
|
|
components/engine/registry/registry_test.go | 91 ++++++++++-
|
|
|
|
components/engine/registry/service.go | 56 ++++---
|
|
|
|
components/engine/registry/service_v2.go | 66 +++++---
|
|
|
|
12 files changed, 705 insertions(+), 46 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/components/engine/api/types/registry/registry.go b/components/engine/api/types/registry/registry.go
|
|
|
|
index 8789ad3b3210..c663fec7d881 100644
|
|
|
|
--- a/components/engine/api/types/registry/registry.go
|
|
|
|
+++ b/components/engine/api/types/registry/registry.go
|
|
|
|
@@ -2,7 +2,10 @@ package registry // import "github.com/docker/docker/api/types/registry"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
+ "fmt"
|
|
|
|
"net"
|
|
|
|
+ "net/url"
|
|
|
|
+ "strings"
|
|
|
|
|
|
|
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
)
|
|
|
|
@@ -14,6 +17,147 @@ type ServiceConfig struct {
|
|
|
|
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
|
|
|
|
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
|
|
|
|
Mirrors []string
|
|
|
|
+ Registries map[string]Registry
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Registry holds information for a registry and its mirrors.
|
|
|
|
+type Registry struct {
|
|
|
|
+ // Prefix is used for the lookup of endpoints, where the given registry
|
|
|
|
+ // is selected when its Prefix is a prefix of the passed reference, for
|
|
|
|
+ // instance, Prefix:"docker.io/opensuse" will match a `docker pull
|
|
|
|
+ // opensuse:tumleweed`.
|
|
|
|
+ URL RegURL `json:"Prefix"`
|
|
|
|
+ // The mirrors will be selected prior to the registry during lookup of
|
|
|
|
+ // endpoints.
|
|
|
|
+ Mirrors []Mirror `json:"Mirrors,omitempty"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NewRegistry returns a Registry and interprets input as a URL.
|
|
|
|
+func NewRegistry(input string) (Registry, error) {
|
|
|
|
+ reg := Registry{}
|
|
|
|
+ err := reg.URL.Parse(input)
|
|
|
|
+ return reg, err
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// AddMirror interprets input as a URL and adds it as a new mirror.
|
|
|
|
+func (r *Registry) AddMirror(input string) error {
|
|
|
|
+ mir, err := NewMirror(input)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ r.Mirrors = append(r.Mirrors, mir)
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ContainsMirror returns true if the URL of any mirror equals input.
|
|
|
|
+func (r *Registry) ContainsMirror(input string) bool {
|
|
|
|
+ for _, m := range r.Mirrors {
|
|
|
|
+ if m.URL.String() == input {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Mirror holds information for a given registry mirror.
|
|
|
|
+type Mirror struct {
|
|
|
|
+ // The URL of the mirror.
|
|
|
|
+ URL RegURL `json:"URL,omitempty"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NewMirror returns a Registry and interprets input as a URL.
|
|
|
|
+func NewMirror(input string) (Mirror, error) {
|
|
|
|
+ mir := Mirror{}
|
|
|
|
+ err := mir.URL.Parse(input)
|
|
|
|
+ return mir, err
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// RegURL is a wrapper for url.URL to unmarshal it from the JSON config and to
|
|
|
|
+// make it an embedded type for its users.
|
|
|
|
+type RegURL struct {
|
|
|
|
+ // rURL is a simple url.URL. Notice it is no pointer to avoid potential
|
|
|
|
+ // null pointer dereferences.
|
|
|
|
+ rURL url.URL
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// UnmarshalJSON unmarshals the byte array into the RegURL pointer.
|
|
|
|
+func (r *RegURL) UnmarshalJSON(b []byte) error {
|
|
|
|
+ var input string
|
|
|
|
+ if err := json.Unmarshal(b, &input); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ return r.Parse(input)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MarshalJSON marshals the RegURL.
|
|
|
|
+func (r *RegURL) MarshalJSON() ([]byte, error) {
|
|
|
|
+ return json.Marshal(r.String())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Parse parses input as a URL.
|
|
|
|
+func (r *RegURL) Parse(input string) error {
|
|
|
|
+ input = strings.ToLower(input)
|
|
|
|
+ uri, err := url.Parse(input)
|
|
|
|
+ if err == nil {
|
|
|
|
+ r.rURL = *uri
|
|
|
|
+ } else {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ // default to https if no URI scheme is specified
|
|
|
|
+ if uri.Scheme == "" {
|
|
|
|
+ // we have to parse again to update all associated data
|
|
|
|
+ return r.Parse("https://" + input)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // sanity checks
|
|
|
|
+ if uri.Scheme != "http" && uri.Scheme != "https" {
|
|
|
|
+ return fmt.Errorf("invalid url: unsupported scheme %q in %q", uri.Scheme, uri)
|
|
|
|
+ }
|
|
|
|
+ if uri.Host == "" {
|
|
|
|
+ return fmt.Errorf("invalid url: unspecified hostname in %s", uri)
|
|
|
|
+ }
|
|
|
|
+ if uri.User != nil {
|
|
|
|
+ // strip password from output
|
|
|
|
+ uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
|
|
|
|
+ return fmt.Errorf("invalid url: username/password not allowed in URI %q", uri)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Host returns the host:port of the URL.
|
|
|
|
+func (r *RegURL) Host() string {
|
|
|
|
+ return r.rURL.Host
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Prefix returns the host:port/path of the URL.
|
|
|
|
+func (r *RegURL) Prefix() string {
|
|
|
|
+ return r.rURL.Host + r.rURL.Path
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// IsOfficial returns true if the URL points to an official "docker.io" host.
|
|
|
|
+func (r *RegURL) IsOfficial() bool {
|
|
|
|
+ return r.rURL.Hostname() == "docker.io"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// IsSecure returns true if the URI scheme of the URL is "https".
|
|
|
|
+func (r *RegURL) IsSecure() bool {
|
|
|
|
+ return r.Scheme() == "https"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Scheme returns the URI scheme.
|
|
|
|
+func (r *RegURL) Scheme() string {
|
|
|
|
+ return r.rURL.Scheme
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// URL return URL of the RegURL.
|
|
|
|
+func (r *RegURL) URL() url.URL {
|
|
|
|
+ return r.rURL
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// String return URL as a string.
|
|
|
|
+func (r *RegURL) String() string {
|
|
|
|
+ return r.rURL.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NetIPNet is the net.IPNet type, which can be marshalled and
|
|
|
|
diff --git a/components/engine/daemon/config/config.go b/components/engine/daemon/config/config.go
|
2018-11-29 16:15:40 +01:00
|
|
|
index 8b2c844a579f..e61940661c70 100644
|
2018-08-20 10:55:46 +02:00
|
|
|
--- a/components/engine/daemon/config/config.go
|
|
|
|
+++ b/components/engine/daemon/config/config.go
|
2018-11-29 16:15:40 +01:00
|
|
|
@@ -470,6 +470,10 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
|
2018-08-20 10:55:46 +02:00
|
|
|
// 1. Search keys from the file that we don't recognize as flags.
|
|
|
|
unknownKeys := make(map[string]interface{})
|
|
|
|
for key, value := range config {
|
|
|
|
+ // skip complex config-only options (daemon.json)
|
|
|
|
+ if key == "registries" {
|
|
|
|
+ continue
|
|
|
|
+ }
|
2018-11-29 16:15:40 +01:00
|
|
|
if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] {
|
2018-08-20 10:55:46 +02:00
|
|
|
unknownKeys[key] = value
|
|
|
|
}
|
|
|
|
diff --git a/components/engine/daemon/reload.go b/components/engine/daemon/reload.go
|
2018-11-29 16:15:40 +01:00
|
|
|
index 026d7dd517f7..924c3982cd2a 100644
|
2018-08-20 10:55:46 +02:00
|
|
|
--- a/components/engine/daemon/reload.go
|
|
|
|
+++ b/components/engine/daemon/reload.go
|
|
|
|
@@ -21,8 +21,14 @@ import (
|
|
|
|
// - Daemon labels
|
|
|
|
// - Insecure registries
|
|
|
|
// - Registry mirrors
|
|
|
|
+// - Registries
|
|
|
|
// - Daemon live restore
|
|
|
|
func (daemon *Daemon) Reload(conf *config.Config) (err error) {
|
|
|
|
+ // check for incompatible options
|
|
|
|
+ if err := conf.ServiceOptions.CompatCheck(); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
daemon.configStore.Lock()
|
|
|
|
attributes := map[string]string{}
|
|
|
|
|
2018-11-29 16:15:40 +01:00
|
|
|
@@ -65,6 +71,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
|
2018-08-20 10:55:46 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:15:40 +01:00
|
|
|
@@ -294,6 +303,30 @@ func (daemon *Daemon) reloadRegistryMirrors(conf *config.Config, attributes map[
|
2018-08-20 10:55:46 +02:00
|
|
|
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/daemon/reload_test.go b/components/engine/daemon/reload_test.go
|
|
|
|
index ffad297f71b7..21733c3f1e33 100644
|
|
|
|
--- a/components/engine/daemon/reload_test.go
|
|
|
|
+++ b/components/engine/daemon/reload_test.go
|
|
|
|
@@ -7,6 +7,7 @@ import (
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
+ registrytypes "github.com/docker/docker/api/types/registry"
|
|
|
|
"github.com/docker/docker/daemon/config"
|
|
|
|
"github.com/docker/docker/daemon/images"
|
|
|
|
"github.com/docker/docker/pkg/discovery"
|
|
|
|
@@ -201,6 +202,100 @@ func TestDaemonReloadMirrors(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+func TestDaemonReloadRegistries(t *testing.T) {
|
|
|
|
+ daemon := &Daemon{
|
|
|
|
+ imageService: images.NewImageService(images.ImageServiceConfig{}),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // create registries: note that this is done implicitly when loading
|
|
|
|
+ // daemon.json file.
|
|
|
|
+ var (
|
|
|
|
+ err error
|
|
|
|
+ regA registrytypes.Registry // no change
|
|
|
|
+ regB registrytypes.Registry // will be changed
|
|
|
|
+ regC registrytypes.Registry // will be added
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ regA, err = registrytypes.NewRegistry("https://registry-a.com")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := regA.AddMirror("https://mirror-a.com"); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // we'll add a 2nd mirror before reloading
|
|
|
|
+ regB, err = registrytypes.NewRegistry("https://registry-b.com")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := regB.AddMirror("https://mirror1-b.com"); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // insecure regC will be added before reloading
|
|
|
|
+ regC, err = registrytypes.NewRegistry("http://registry-c.com")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ daemon.RegistryService, err = registry.NewService(registry.ServiceOptions{
|
|
|
|
+ Registries: []registrytypes.Registry{regA, regB},
|
|
|
|
+ })
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ daemon.configStore = &config.Config{}
|
|
|
|
+
|
|
|
|
+ if err := regB.AddMirror("https://mirror2-b.com"); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ registries := []registrytypes.Registry{regA, regB, regC}
|
|
|
|
+
|
|
|
|
+ valuesSets := make(map[string]interface{})
|
|
|
|
+ valuesSets["registries"] = registries
|
|
|
|
+
|
|
|
|
+ newConfig := &config.Config{
|
|
|
|
+ CommonConfig: config.CommonConfig{
|
|
|
|
+ ServiceOptions: registry.ServiceOptions{
|
|
|
|
+ Registries: registries,
|
|
|
|
+ },
|
|
|
|
+ ValuesSet: valuesSets,
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := daemon.Reload(newConfig); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ registryService := daemon.RegistryService.ServiceConfig()
|
|
|
|
+
|
|
|
|
+ if reg, exists := registryService.Registries["registry-a.com"]; !exists {
|
|
|
|
+ t.Fatal("registry should exist but doesn't")
|
|
|
|
+ } else {
|
|
|
|
+ if !reg.ContainsMirror("https://mirror-a.com") {
|
|
|
|
+ t.Fatal("registry should contain mirror but doesn't")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if reg, exists := registryService.Registries["registry-b.com"]; !exists {
|
|
|
|
+ t.Fatal("registry should exist but doesn't")
|
|
|
|
+ } else {
|
|
|
|
+ if !reg.ContainsMirror("https://mirror1-b.com") {
|
|
|
|
+ t.Fatal("registry should contain mirror but doesn't")
|
|
|
|
+ }
|
|
|
|
+ if !reg.ContainsMirror("https://mirror2-b.com") {
|
|
|
|
+ t.Fatal("registry should contain mirror but doesn't")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if _, exists := registryService.Registries["registry-c.com"]; !exists {
|
|
|
|
+ t.Fatal("registry should exist but doesn't")
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
func TestDaemonReloadInsecureRegistries(t *testing.T) {
|
|
|
|
daemon := &Daemon{
|
|
|
|
imageService: images.NewImageService(images.ImageServiceConfig{}),
|
|
|
|
diff --git a/components/engine/distribution/pull.go b/components/engine/distribution/pull.go
|
|
|
|
index 5de73ae99ac3..8e78c49273dd 100644
|
|
|
|
--- a/components/engine/distribution/pull.go
|
|
|
|
+++ b/components/engine/distribution/pull.go
|
|
|
|
@@ -63,7 +63,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
- endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
|
|
|
|
+ endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(ref.Name())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go
|
|
|
|
index 8f05cfa0b289..a562477ea6cd 100644
|
|
|
|
--- a/components/engine/distribution/pull_v2.go
|
|
|
|
+++ b/components/engine/distribution/pull_v2.go
|
|
|
|
@@ -379,7 +379,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.Infof("Pulling ref %s from V2 registry %s", reference.FamiliarString(ref), p.endpoint.URL)
|
|
|
|
progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named()))
|
|
|
|
|
|
|
|
var (
|
|
|
|
diff --git a/components/engine/distribution/push.go b/components/engine/distribution/push.go
|
|
|
|
index eb3bc5597462..a4624dee9482 100644
|
|
|
|
--- a/components/engine/distribution/push.go
|
|
|
|
+++ b/components/engine/distribution/push.go
|
|
|
|
@@ -64,7 +64,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
- endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
|
|
|
|
+ endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(ref.Name())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
diff --git a/components/engine/registry/config.go b/components/engine/registry/config.go
|
|
|
|
index de5a526b694d..cf90abb8be04 100644
|
|
|
|
--- a/components/engine/registry/config.go
|
|
|
|
+++ b/components/engine/registry/config.go
|
|
|
|
@@ -14,7 +14,7 @@ import (
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
-// ServiceOptions holds command line options.
|
|
|
|
+// ServiceOptions holds the user-specified configuration options.
|
|
|
|
type ServiceOptions struct {
|
|
|
|
AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
|
|
|
|
Mirrors []string `json:"registry-mirrors,omitempty"`
|
|
|
|
@@ -23,6 +23,9 @@ type ServiceOptions struct {
|
|
|
|
// 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"`
|
|
|
|
+
|
|
|
|
+ // Registries holds information associated with the specified registries.
|
|
|
|
+ Registries []registrytypes.Registry `json:"registries,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// serviceConfig holds daemon configuration for the registry service.
|
|
|
|
@@ -67,8 +70,21 @@ var (
|
|
|
|
// for mocking in unit tests
|
|
|
|
var lookupIP = net.LookupIP
|
|
|
|
|
|
|
|
+// CompatCheck performs some compatibility checks among the config options and
|
|
|
|
+// returns an error in case of conflicts.
|
|
|
|
+func (options *ServiceOptions) CompatCheck() error {
|
|
|
|
+ if len(options.InsecureRegistries) > 0 && len(options.Registries) > 0 {
|
|
|
|
+ return fmt.Errorf("usage of \"registries\" with deprecated option \"insecure-registries\" is not supported")
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
// newServiceConfig returns a new instance of ServiceConfig
|
|
|
|
func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
|
|
|
|
+ if err := options.CompatCheck(); err != nil {
|
|
|
|
+ panic(fmt.Sprintf("error loading config: %v", err))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
config := &serviceConfig{
|
|
|
|
ServiceConfig: registrytypes.ServiceConfig{
|
|
|
|
InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
|
|
|
|
@@ -87,10 +103,104 @@ 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, fmt.Errorf("error loading registries: %v", err)
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
+// checkRegistries makes sure that no mirror serves more than one registry and
|
|
|
|
+// that no host is used as a registry and as a mirror simultaneously. Notice
|
|
|
|
+// that different registry prefixes can share a mirror as long as they point to
|
|
|
|
+// the same registry. It also warns if the URI schemes of a given registry and
|
|
|
|
+// one of its mirrors differ.
|
|
|
|
+func (config *serviceConfig) checkRegistries() error {
|
|
|
|
+ inUse := make(map[string]string) // key: host, value: user
|
|
|
|
+
|
|
|
|
+ // make sure that each mirror serves only one registry
|
|
|
|
+ for _, reg := range config.Registries {
|
|
|
|
+ for _, mirror := range reg.Mirrors {
|
|
|
|
+ if used, conflict := inUse[mirror.URL.Host()]; conflict {
|
|
|
|
+ if used != reg.URL.Host() {
|
|
|
|
+ return fmt.Errorf("mirror '%s' can only serve one registry host", mirror.URL.Host())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // docker.io etc. is reserved
|
|
|
|
+ if mirror.URL.IsOfficial() {
|
|
|
|
+ return fmt.Errorf("mirror '%s' cannot be used (reserved host)", mirror.URL.Host())
|
|
|
|
+ }
|
|
|
|
+ inUse[mirror.URL.Host()] = reg.URL.Host()
|
|
|
|
+ // also warnf if seucurity levels differ
|
|
|
|
+ if reg.URL.IsSecure() != mirror.URL.IsSecure() {
|
|
|
|
+ logrus.Warnf("registry '%s' and mirror '%s' have different security levels", reg.URL.URL(), mirror.URL.URL())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if reg.URL.IsSecure() && len(reg.Mirrors) == 0 {
|
|
|
|
+ logrus.Warnf("specifying secure registry '%s' without mirrors has no effect", reg.URL.Prefix())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // make sure that no registry host is used as a mirror
|
|
|
|
+ for _, reg := range config.Registries {
|
|
|
|
+ if _, conflict := inUse[reg.URL.Host()]; conflict {
|
|
|
|
+ return fmt.Errorf("registry '%s' cannot simultaneously serve as a mirror for '%s'", reg.URL.Host(), inUse[reg.URL.Host()])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// FindRegistry returns a Registry pointer based on the passed reference. If
|
|
|
|
+// more than one index-prefix match the reference, the longest index is
|
|
|
|
+// returned. In case of no match, nil is returned.
|
|
|
|
+func (config *serviceConfig) FindRegistry(reference string) *registrytypes.Registry {
|
|
|
|
+ prefixStr := ""
|
|
|
|
+ prefixLen := 0
|
|
|
|
+ for _, reg := range config.Registries {
|
|
|
|
+ if strings.HasPrefix(reference, reg.URL.Prefix()) {
|
|
|
|
+ length := len(reg.URL.Prefix())
|
|
|
|
+ if length > prefixLen {
|
|
|
|
+ prefixStr = reg.URL.Prefix()
|
|
|
|
+ prefixLen = length
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if prefixLen > 0 {
|
|
|
|
+ reg := config.Registries[prefixStr]
|
|
|
|
+ return ®
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// LoadRegistries loads the user-specified configuration options for registries.
|
|
|
|
+func (config *serviceConfig) LoadRegistries(registries []registrytypes.Registry) error {
|
|
|
|
+ config.Registries = make(map[string]registrytypes.Registry)
|
|
|
|
+
|
|
|
|
+ for _, reg := range registries {
|
|
|
|
+ config.Registries[reg.URL.Prefix()] = reg
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // backwards compatability to the "registry-mirrors" config
|
|
|
|
+ if len(config.Mirrors) > 0 {
|
|
|
|
+ reg := registrytypes.Registry{}
|
|
|
|
+ if officialReg, exists := config.Registries[IndexName]; exists {
|
|
|
|
+ reg = officialReg
|
|
|
|
+ } else {
|
|
|
|
+ var err error
|
|
|
|
+ reg, err = registrytypes.NewRegistry(IndexName)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for _, mirrorStr := range config.Mirrors {
|
|
|
|
+ reg.AddMirror(mirrorStr)
|
|
|
|
+ }
|
|
|
|
+ config.Registries[IndexName] = reg
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return config.checkRegistries()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
|
|
|
|
func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
|
|
|
|
cidrs := map[string]*registrytypes.NetIPNet{}
|
|
|
|
@@ -131,6 +241,10 @@ func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []str
|
|
|
|
// LoadMirrors loads mirrors to config, after removing duplicates.
|
|
|
|
// Returns an error if mirrors contains an invalid mirror.
|
|
|
|
func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
|
|
|
+ if len(mirrors) > 0 {
|
|
|
|
+ logrus.Infof("usage of deprecated 'registry-mirrors' option: please use 'registries' instead")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
mMap := map[string]struct{}{}
|
|
|
|
unique := []string{}
|
|
|
|
|
|
|
|
@@ -160,6 +274,10 @@ func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
|
|
|
|
|
|
|
// LoadInsecureRegistries loads insecure registries to config
|
|
|
|
func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
|
|
|
|
+ if len(registries) > 0 {
|
|
|
|
+ logrus.Info("usage of deprecated 'insecure-registries' option: please use 'registries' instead")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
// 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).
|
|
|
|
//
|
|
|
|
diff --git a/components/engine/registry/config_test.go b/components/engine/registry/config_test.go
|
|
|
|
index 30a257e32556..78a4fadd733f 100644
|
|
|
|
--- a/components/engine/registry/config_test.go
|
|
|
|
+++ b/components/engine/registry/config_test.go
|
|
|
|
@@ -6,10 +6,146 @@ import (
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
+ registrytypes "github.com/docker/docker/api/types/registry"
|
|
|
|
"gotest.tools/assert"
|
|
|
|
is "gotest.tools/assert/cmp"
|
|
|
|
)
|
|
|
|
|
|
|
|
+func TestLoadValidRegistries(t *testing.T) {
|
|
|
|
+ var (
|
|
|
|
+ secReg registrytypes.Registry
|
|
|
|
+ insecReg registrytypes.Registry
|
|
|
|
+ config *serviceConfig
|
|
|
|
+ err error
|
|
|
|
+ )
|
|
|
|
+ // secure with mirrors
|
|
|
|
+ secReg, err = registrytypes.NewRegistry("https://secure.registry.com")
|
|
|
|
+ secMirrors := []string{"https://secure.mirror1.com", "https://secure.mirror2.com"}
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := secReg.AddMirror(secMirrors[0]); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := secReg.AddMirror(secMirrors[1]); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // insecure without mirrors
|
|
|
|
+ insecReg, err = registrytypes.NewRegistry("http://insecure.registry.com")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // docker.io mirrors to test backwards compatibility
|
|
|
|
+ officialMirrors := []string{"https://official.mirror1.com", "https://official.mirror2.com"}
|
|
|
|
+
|
|
|
|
+ // create serciveConfig
|
|
|
|
+ config = newServiceConfig(
|
|
|
|
+ ServiceOptions{
|
|
|
|
+ Mirrors: officialMirrors,
|
|
|
|
+ Registries: []registrytypes.Registry{secReg, insecReg},
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // now test if the config looks as expected
|
|
|
|
+ getMirrors := func(reg registrytypes.Registry) []string {
|
|
|
|
+ mirrors := []string{}
|
|
|
|
+ for _, mir := range reg.Mirrors {
|
|
|
|
+ mirrors = append(mirrors, mir.URL.String())
|
|
|
|
+ }
|
|
|
|
+ return mirrors
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if reg, loaded := config.Registries["secure.registry.com"]; !loaded {
|
|
|
|
+ t.Fatalf("registry not loaded")
|
|
|
|
+ } else {
|
|
|
|
+ assert.Equal(t, true, reg.URL.IsSecure())
|
|
|
|
+ assert.Equal(t, false, reg.URL.IsOfficial())
|
|
|
|
+ mirrors := getMirrors(reg)
|
|
|
|
+ assert.Equal(t, len(secMirrors), len(mirrors))
|
|
|
|
+ sort.Strings(mirrors)
|
|
|
|
+ sort.Strings(secMirrors)
|
|
|
|
+ assert.Equal(t, secMirrors[0], mirrors[0])
|
|
|
|
+ assert.Equal(t, secMirrors[1], mirrors[1])
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if reg, loaded := config.Registries["insecure.registry.com"]; !loaded {
|
|
|
|
+ t.Fatalf("registry not loaded")
|
|
|
|
+ } else {
|
|
|
|
+ assert.Equal(t, false, reg.URL.IsSecure())
|
|
|
|
+ assert.Equal(t, false, reg.URL.IsOfficial())
|
|
|
|
+ mirrors := getMirrors(reg)
|
|
|
|
+ assert.Equal(t, 0, len(mirrors))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // backwards compatibility: "docker.io" will be loaded due to the config.Mirrors
|
|
|
|
+ if reg, loaded := config.Registries["docker.io"]; !loaded {
|
|
|
|
+ t.Fatalf("registry not loaded")
|
|
|
|
+ } else {
|
|
|
|
+ assert.Equal(t, true, reg.URL.IsSecure())
|
|
|
|
+ assert.Equal(t, true, reg.URL.IsOfficial())
|
|
|
|
+ mirrors := getMirrors(reg)
|
|
|
|
+ assert.Equal(t, len(officialMirrors), len(mirrors))
|
|
|
|
+ sort.Strings(mirrors)
|
|
|
|
+ sort.Strings(officialMirrors)
|
|
|
|
+ // append '/' (see ValidateMirror())
|
|
|
|
+ assert.Equal(t, officialMirrors[0]+"/", mirrors[0])
|
|
|
|
+ assert.Equal(t, officialMirrors[1]+"/", mirrors[1])
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//func TestLoadInvalidRegistries(t *testing.T) {
|
|
|
|
+// XXX: this has to be tested manually as the v17.09.X doesn't have a proper
|
|
|
|
+// error handling for service configs (errors are silently ignored), so
|
|
|
|
+// the backported patch panics() instead.
|
|
|
|
+//}
|
|
|
|
+
|
|
|
|
+func TestFindRegistry(t *testing.T) {
|
|
|
|
+ var (
|
|
|
|
+ regA registrytypes.Registry
|
|
|
|
+ regB registrytypes.Registry
|
|
|
|
+ config *serviceConfig
|
|
|
|
+ err error
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ regA, err = registrytypes.NewRegistry("https://registry-a.com/my-prefix")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ regB, err = registrytypes.NewRegistry("http://registry-b.com")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // create serciveConfig
|
|
|
|
+ config = newServiceConfig(
|
|
|
|
+ ServiceOptions{
|
|
|
|
+ Registries: []registrytypes.Registry{regA, regB},
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // no match -> nil
|
|
|
|
+ reg := config.FindRegistry("foo")
|
|
|
|
+ assert.Nil(t, reg)
|
|
|
|
+
|
|
|
|
+ // prefix match -> registry
|
|
|
|
+ reg = config.FindRegistry("registry-a.com/my-prefix/image:latest")
|
|
|
|
+ assert.NotNil(t, reg)
|
|
|
|
+ assert.Equal(t, "registry-a.com", reg.URL.Host())
|
|
|
|
+ // no prefix match -> nil
|
|
|
|
+ reg = config.FindRegistry("registry-a.com/not-my-prefix/image:42")
|
|
|
|
+ assert.Nil(t, reg)
|
|
|
|
+
|
|
|
|
+ // prefix match -> registry
|
|
|
|
+ reg = config.FindRegistry("registry-b.com/image:latest")
|
|
|
|
+ assert.NotNil(t, reg)
|
|
|
|
+ assert.Equal(t, "registry-b.com", reg.URL.Host())
|
|
|
|
+ // prefix match -> registry
|
|
|
|
+ reg = config.FindRegistry("registry-b.com/also-in-namespaces/image:latest")
|
|
|
|
+ assert.NotNil(t, reg)
|
|
|
|
+ assert.Equal(t, "registry-b.com", reg.URL.Host())
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
func TestLoadAllowNondistributableArtifacts(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
registries []string
|
|
|
|
diff --git a/components/engine/registry/registry_test.go b/components/engine/registry/registry_test.go
|
|
|
|
index b7459471b3f6..1e0d53e7dc21 100644
|
|
|
|
--- a/components/engine/registry/registry_test.go
|
|
|
|
+++ b/components/engine/registry/registry_test.go
|
|
|
|
@@ -665,7 +665,32 @@ func TestNewIndexInfo(t *testing.T) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMirrorEndpointLookup(t *testing.T) {
|
|
|
|
+ var (
|
|
|
|
+ secReg registrytypes.Registry
|
|
|
|
+ config *serviceConfig
|
|
|
|
+ pushAPIEndpoints []APIEndpoint
|
|
|
|
+ pullAPIEndpoints []APIEndpoint
|
|
|
|
+ err error
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
|
|
|
|
+
|
|
|
|
+ // secure with mirrors
|
|
|
|
+ secReg, err = registrytypes.NewRegistry("https://secure.registry.com/test-prefix/")
|
|
|
|
+ secMirrors := []string{"https://secure.mirror1.com/", "https://secure.mirror2.com/"}
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := secReg.AddMirror(secMirrors[0]); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if err := secReg.AddMirror(secMirrors[1]); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // docker.io mirrors to test backwards compatibility
|
|
|
|
+ officialMirrors := []string{"https://official.mirror1.com/", "https://official.mirror2.com/"}
|
|
|
|
+
|
|
|
|
containsMirror := func(endpoints []APIEndpoint) bool {
|
|
|
|
for _, pe := range endpoints {
|
|
|
|
if pe.URL.Host == "my.mirror" {
|
|
|
|
@@ -674,31 +699,83 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
- cfg, err := makeServiceConfig([]string{"https://my.mirror"}, nil)
|
|
|
|
+ cfg, err := makeServiceConfig(officialMirrors, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
s := DefaultService{config: cfg}
|
|
|
|
|
|
|
|
- imageName, err := reference.WithName(IndexName + "/test/image")
|
|
|
|
+ // lookups for "docker.io"
|
|
|
|
+ officialRef := "docker.io/test/image:latest"
|
|
|
|
+ pushAPIEndpoints, err = s.LookupPushEndpoints(officialRef)
|
|
|
|
if err != nil {
|
|
|
|
- t.Error(err)
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(officialMirrors[0], pushAPIEndpoints) {
|
|
|
|
+ t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
}
|
|
|
|
- pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName))
|
|
|
|
+ if containsMirror(officialMirrors[1], pushAPIEndpoints) {
|
|
|
|
+ t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pullAPIEndpoints, err = s.LookupPullEndpoints(officialRef)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
- if containsMirror(pushAPIEndpoints) {
|
|
|
|
+ if !containsMirror(officialMirrors[0], pullAPIEndpoints) {
|
|
|
|
+ t.Fatal("Pull endpoint should contain mirror")
|
|
|
|
+ }
|
|
|
|
+ if !containsMirror(officialMirrors[1], pullAPIEndpoints) {
|
|
|
|
+ t.Fatal("Pull endpoint should contain mirror")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // prefix lookups
|
|
|
|
+ prefixRef := "secure.registry.com/test-prefix/foo:latest"
|
|
|
|
+ pushAPIEndpoints, err = s.LookupPushEndpoints(prefixRef)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[0], pushAPIEndpoints) {
|
|
|
|
+ t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[1], pushAPIEndpoints) {
|
|
|
|
t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
}
|
|
|
|
|
|
|
|
- pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName))
|
|
|
|
+ pullAPIEndpoints, err = s.LookupPullEndpoints(prefixRef)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
- if !containsMirror(pullAPIEndpoints) {
|
|
|
|
+ if !containsMirror(secMirrors[0], pullAPIEndpoints) {
|
|
|
|
t.Fatal("Pull endpoint should contain mirror")
|
|
|
|
}
|
|
|
|
+ if !containsMirror(secMirrors[1], pullAPIEndpoints) {
|
|
|
|
+ t.Fatal("Pull endpoint should contain mirror")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // lookups without matching prefix -> no mirrors
|
|
|
|
+ noPrefixRef := "secure.registry.com/no-matching-prefix/foo:latest"
|
|
|
|
+ pushAPIEndpoints, err = s.LookupPushEndpoints(noPrefixRef)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[0], pushAPIEndpoints) {
|
|
|
|
+ t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[1], pushAPIEndpoints) {
|
|
|
|
+ t.Fatal("Push endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pullAPIEndpoints, err = s.LookupPullEndpoints(noPrefixRef)
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[0], pullAPIEndpoints) {
|
|
|
|
+ t.Fatal("Pull endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
+ if containsMirror(secMirrors[1], pullAPIEndpoints) {
|
|
|
|
+ t.Fatal("Pull endpoint should not contain mirror")
|
|
|
|
+ }
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPushRegistryTag(t *testing.T) {
|
|
|
|
diff --git a/components/engine/registry/service.go b/components/engine/registry/service.go
|
|
|
|
index b441970ff170..b3c1ee21f383 100644
|
|
|
|
--- a/components/engine/registry/service.go
|
|
|
|
+++ b/components/engine/registry/service.go
|
|
|
|
@@ -8,7 +8,7 @@ import (
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
- "github.com/docker/distribution/reference"
|
|
|
|
+ dref "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"
|
|
|
|
@@ -25,14 +25,15 @@ const (
|
|
|
|
// Service is the interface defining what a registry service should implement.
|
|
|
|
type Service interface {
|
|
|
|
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
|
|
|
|
- LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
|
|
|
- LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
|
|
|
- ResolveRepository(name reference.Named) (*RepositoryInfo, error)
|
|
|
|
+ LookupPullEndpoints(reference string) (endpoints []APIEndpoint, err error)
|
|
|
|
+ LookupPushEndpoints(reference string) (endpoints []APIEndpoint, err error)
|
|
|
|
+ ResolveRepository(name dref.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
|
|
|
|
TLSConfig(hostname string) (*tls.Config, error)
|
|
|
|
LoadAllowNondistributableArtifacts([]string) error
|
|
|
|
LoadMirrors([]string) error
|
|
|
|
+ LoadRegistries([]registrytypes.Registry) error
|
|
|
|
LoadInsecureRegistries([]string) error
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -61,6 +62,7 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
|
|
|
AllowNondistributableArtifactsHostnames: make([]string, 0),
|
|
|
|
InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
|
|
|
IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
|
|
|
|
+ Registries: make(map[string]registrytypes.Registry),
|
|
|
|
Mirrors: make([]string, 0),
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -76,6 +78,10 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
|
|
|
|
|
|
|
servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
|
|
|
|
|
|
|
|
+ for key, value := range s.config.ServiceConfig.Registries {
|
|
|
|
+ servConfig.Registries[key] = value
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
return &servConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -103,6 +109,14 @@ func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
|
|
|
|
return s.config.LoadInsecureRegistries(registries)
|
|
|
|
}
|
|
|
|
|
|
|
|
+// LoadRegistries loads registries for Service
|
|
|
|
+func (s *DefaultService) LoadRegistries(registries []registrytypes.Registry) 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.
|
|
|
|
@@ -241,7 +255,7 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
|
|
|
|
|
|
|
|
// ResolveRepository splits a repository name into its components
|
|
|
|
// and configuration of the associated registry.
|
|
|
|
-func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
|
|
|
+func (s *DefaultService) ResolveRepository(name dref.Named) (*RepositoryInfo, error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
return newRepositoryInfo(s.config, name)
|
|
|
|
@@ -280,24 +294,25 @@ func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, er
|
|
|
|
return s.tlsConfig(mirrorURL.Host)
|
|
|
|
}
|
|
|
|
|
|
|
|
-// LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
|
|
|
|
-// It gives preference to v2 endpoints over v1, mirrors over the actual
|
|
|
|
-// registry, and HTTPS over plain HTTP.
|
|
|
|
-func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
|
|
+// LookupPullEndpoints creates a list of endpoints based on the provided
|
|
|
|
+// reference to try to pull from, in order of preference. It gives preference
|
|
|
|
+// to v2 endpoints over v1, mirrors over the actual registry, and HTTPS over
|
|
|
|
+// plain HTTP.
|
|
|
|
+func (s *DefaultService) LookupPullEndpoints(reference string) (endpoints []APIEndpoint, err error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
- return s.lookupEndpoints(hostname)
|
|
|
|
+ return s.lookupEndpoints(reference)
|
|
|
|
}
|
|
|
|
|
|
|
|
-// LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
|
|
|
|
-// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
|
|
|
|
-// Mirrors are not included.
|
|
|
|
-func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
|
|
+// LookupPushEndpoints creates a list of endpoints based on the provided
|
|
|
|
+// reference to try to push to, in order of preference. It gives preference to
|
|
|
|
+// v2 endpoints over v1, and HTTPS over plain HTTP. Mirrors are not included.
|
|
|
|
+func (s *DefaultService) LookupPushEndpoints(reference string) (endpoints []APIEndpoint, err error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
- allEndpoints, err := s.lookupEndpoints(hostname)
|
|
|
|
+ allEndpoints, err := s.lookupEndpoints(reference)
|
|
|
|
if err == nil {
|
|
|
|
for _, endpoint := range allEndpoints {
|
|
|
|
if !endpoint.Mirror {
|
|
|
|
@@ -308,8 +323,8 @@ func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEn
|
|
|
|
return endpoints, err
|
|
|
|
}
|
|
|
|
|
|
|
|
-func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
|
|
- endpoints, err = s.lookupV2Endpoints(hostname)
|
|
|
|
+func (s *DefaultService) lookupEndpoints(reference string) (endpoints []APIEndpoint, err error) {
|
|
|
|
+ endpoints, err = s.lookupV2Endpoints(reference)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
@@ -318,6 +333,13 @@ func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoi
|
|
|
|
return endpoints, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
+ // When falling back to V1 endpoints, switch to the hostname
|
|
|
|
+ ref, err := dref.ParseNamed(reference)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ hostname := dref.Domain(ref)
|
|
|
|
+
|
|
|
|
legacyEndpoints, err := s.lookupV1Endpoints(hostname)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
diff --git a/components/engine/registry/service_v2.go b/components/engine/registry/service_v2.go
|
|
|
|
index 3a56dc91145a..9de221cf2aa0 100644
|
|
|
|
--- a/components/engine/registry/service_v2.go
|
|
|
|
+++ b/components/engine/registry/service_v2.go
|
|
|
|
@@ -1,30 +1,51 @@
|
|
|
|
package registry // import "github.com/docker/docker/registry"
|
|
|
|
|
|
|
|
import (
|
|
|
|
+ "fmt"
|
|
|
|
"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) {
|
|
|
|
+func (s *DefaultService) lookupV2Endpoints(reference 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
|
|
|
|
- }
|
|
|
|
- mirrorTLSConfig, err := s.tlsConfigForMirror(mirrorURL)
|
|
|
|
+
|
|
|
|
+ // extraxt the hostname from the reference
|
|
|
|
+ refURL := reference
|
|
|
|
+ if !strings.HasPrefix(refURL, "http://") && !strings.HasPrefix(refURL, "https://") {
|
|
|
|
+ refURL = "https://" + refURL
|
|
|
|
+ }
|
|
|
|
+ u, err := url.Parse(refURL)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("SUSE PATCH [lookupV2Endpoints]: error parsing reference %s: %s", reference, err)
|
|
|
|
+ }
|
|
|
|
+ hostname := u.Host // hostname + port (if present)
|
|
|
|
+ if hostname == "" {
|
|
|
|
+ return nil, fmt.Errorf("SUSE PATCH [lookupV2Endpoints]: cannot determine hostname of reference %s", reference)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // create endpoints for official and configured registries
|
|
|
|
+ official := false
|
|
|
|
+ if hostname == "docker.io" {
|
|
|
|
+ official = true
|
|
|
|
+ }
|
|
|
|
+ reg := s.config.FindRegistry(reference)
|
|
|
|
+
|
|
|
|
+ if reg != nil || official {
|
|
|
|
+ if reg == nil {
|
|
|
|
+ reg = ®istrytypes.Registry{}
|
|
|
|
+ }
|
|
|
|
+ // if present, add mirrors prior to the registry
|
|
|
|
+ for _, mirror := range reg.Mirrors {
|
|
|
|
+ mURL := mirror.URL.URL()
|
|
|
|
+ mirrorTLSConfig, err := s.tlsConfigForMirror(&mURL)
|
|
|
|
if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
+ return nil, fmt.Errorf("SUSE PATCH [lookupV2Endpoints]: %s", err)
|
|
|
|
}
|
|
|
|
endpoints = append(endpoints, APIEndpoint{
|
|
|
|
- URL: mirrorURL,
|
|
|
|
+ URL: &mURL,
|
|
|
|
// guess mirrors are v2
|
|
|
|
Version: APIVersion2,
|
|
|
|
Mirror: true,
|
|
|
|
@@ -32,11 +53,20 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
|
|
|
TLSConfig: mirrorTLSConfig,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
- // v2 registry
|
|
|
|
+ // add the registry
|
|
|
|
+ var endpointURL *url.URL
|
|
|
|
+ if official {
|
|
|
|
+ endpointURL = DefaultV2Registry
|
|
|
|
+ } else {
|
|
|
|
+ endpointURL = &url.URL{
|
|
|
|
+ Scheme: reg.URL.Scheme(),
|
|
|
|
+ Host: reg.URL.Host(),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
endpoints = append(endpoints, APIEndpoint{
|
|
|
|
- URL: DefaultV2Registry,
|
|
|
|
+ URL: endpointURL,
|
|
|
|
Version: APIVersion2,
|
|
|
|
- Official: true,
|
|
|
|
+ Official: official,
|
|
|
|
TrimHostname: true,
|
|
|
|
TLSConfig: tlsConfig,
|
|
|
|
})
|
|
|
|
@@ -48,7 +78,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
|
|
|
|
|
|
|
tlsConfig, err = s.tlsConfig(hostname)
|
|
|
|
if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
+ return nil, fmt.Errorf("SUSE PATCH [lookupV2Enpoints]: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoints = []APIEndpoint{
|
|
|
|
--
|
2019-05-03 18:22:55 +02:00
|
|
|
2.21.0
|
2018-08-20 10:55:46 +02:00
|
|
|
|