diff --git a/_multibuild b/_multibuild
new file mode 100644
index 0000000..ef76501
--- /dev/null
+++ b/_multibuild
@@ -0,0 +1,3 @@
+
+ kubic
+
diff --git a/docker-kubic-service.conf b/docker-kubic-service.conf
new file mode 100644
index 0000000..8b60aa1
--- /dev/null
+++ b/docker-kubic-service.conf
@@ -0,0 +1,4 @@
+[Service]
+# Put docker under the podruntime slice. This the recommended
+# deployment to allow fine resource control on Kubernetes.
+Slice=podruntime.slice
diff --git a/docker-rpmlintrc b/docker-rpmlintrc
index 62838b9..ec2474b 100644
--- a/docker-rpmlintrc
+++ b/docker-rpmlintrc
@@ -1,7 +1,9 @@
-addFilter ("^docker.x86_64: W: statically-linked-binary /usr/lib64/docker/dockerinit")
-addFilter ("^docker-bash-completion.noarch: W: sourced-script-with-shebang /etc/bash_completion.d/docker bash")
-addFilter ("^docker.x86_64: W: statically-linked-binary /usr/lib/docker/dockerinit")
-addFilter ("^docker.x86_64: W: unstripped-binary-or-object /usr/lib/docker/dockerinit")
-addFilter ("^docker.x86_64: W: no-manual-page-for-binary docker")
-addFilter ("^docker.x86_64: W: no-manual-page-for-binary nsinit")
-addFilter ("^docker-test.*")
+# This is intentional, since we use _multibuild for the flavours.
+addFilter ("^docker-kubic.src: W: invalid-spec-name")
+
+# The #! comes from upstream.
+addFilter ("^docker(-kubic)?-bash-completion.noarch: W: sourced-script-with-shebang /etc/bash_completion.d/docker bash")
+addFilter ("^docker(-kubic)?-zsh-completion.noarch: W: sourced-script-with-shebang /etc/zsh_completion.d/docker zsh")
+
+# -test is something that is used internally and isn't actually shipped -- it's a pseduo-source package.
+addFilter ("^docker(-kubic)?-test.*")
diff --git a/docker.changes b/docker.changes
index cda58be..727147e 100644
--- a/docker.changes
+++ b/docker.changes
@@ -1,3 +1,12 @@
+-------------------------------------------------------------------
+Thu Aug 16 02:00:31 UTC 2018 - asarai@suse.com
+
+- Merge -kubic packages back into the main Virtualization:containers packages.
+ This is done using _multibuild to add a "kubic" flavour, which is then used
+ to conditionally compile patches and other kubic-specific features.
+ bsc#1105000
+- Rework docker-rpmlintrc with the new _multibuild setup.
+
-------------------------------------------------------------------
Wed Aug 1 09:40:59 UTC 2018 - asarai@suse.com
@@ -36,11 +45,6 @@ Fri Jun 29 08:35:56 UTC 2018 - asarai@suse.com
* bsc1073877-0001-apparmor-allow-receiving-of-signals-from-docker-kill.patch
+ bsc1073877-0002-apparmor-clobber-docker-default-profile-on-start.patch
--------------------------------------------------------------------
-Wed Jun 13 10:19:23 UTC 2018 - dcassany@suse.com
-
-- Make use of %license macro
-
-------------------------------------------------------------------
Tue Jun 5 11:24:35 UTC 2018 - asarai@suse.com
@@ -48,6 +52,11 @@ Tue Jun 5 11:24:35 UTC 2018 - asarai@suse.com
between in-container processes. bsc#1073877
* bsc1073877-0001-apparmor-allow-receiving-of-signals-from-docker-kill.patch
+-------------------------------------------------------------------
+Tue Jun 5 08:41:07 UTC 2018 - dcassany@suse.com
+
+- Make use of %license macro
+
-------------------------------------------------------------------
Tue Jun 5 06:38:40 UTC 2018 - asarai@suse.com
@@ -63,6 +72,18 @@ Tue May 29 08:10:48 UTC 2018 - asarai@suse.com
* secrets-0001-daemon-allow-directory-creation-in-run-secrets.patch
* secrets-0002-SUSE-implement-SUSE-container-secrets.patch
+-------------------------------------------------------------------
+Wed May 16 10:12:56 UTC 2018 - jmassaguerpla@suse.com
+
+- Review Obsoletes to fix bsc#1080978
+
+-------------------------------------------------------------------
+Thu Apr 12 12:49:25 UTC 2018 - fcastelli@suse.com
+
+- Put docker under the podruntime slice. This the recommended
+ deployment to allow fine resource control on Kubernetes.
+ bsc#1086185
+
-------------------------------------------------------------------
Tue Apr 10 09:25:43 UTC 2018 - mmeister@suse.com
@@ -88,6 +109,13 @@ Tue Mar 27 10:13:41 UTC 2018 - asarai@suse.com
- Add requirement for catatonit, which provides a docker-init implementation.
fate#324652 bsc#1085380
+-------------------------------------------------------------------
+Thu Mar 8 13:14:54 UTC 2018 - vrothberg@suse.com
+
+- Fix private-registry-0001-Add-private-registry-mirror-support.patch to
+ deal corretly with TLS configs of 3rd party registries.
+ fix bsc#1084533
+
-------------------------------------------------------------------
Tue Feb 13 10:45:58 UTC 2018 - asarai@suse.com
@@ -97,9 +125,40 @@ Tue Feb 13 10:45:58 UTC 2018 - asarai@suse.com
patch maintenance is much simpler.
* bsc1021227-0001-pkg-devmapper-dynamically-load-dm_task_deferred_remo.patch
* bsc1055676-0001-daemon-oci-obey-CL_UNPRIVILEGED-for-user-namespaced-.patch
+ * private-registry-0001-Add-private-registry-mirror-support.patch
* secrets-0001-daemon-allow-directory-creation-in-run-secrets.patch
* secrets-0002-SUSE-implement-SUSE-container-secrets.patch
+-------------------------------------------------------------------
+Mon Feb 12 10:52:33 UTC 2018 - rbrown@suse.com
+
+- Add ${version} to equivalent non-kubic package provides
+
+-------------------------------------------------------------------
+Thu Feb 8 12:34:51 UTC 2018 - rbrown@suse.com
+
+- Add Provides for equivalent non-kubic packages
+
+-------------------------------------------------------------------
+Tue Jan 30 12:27:44 UTC 2018 - vrothberg@suse.com
+
+- Disable all tests for docker/client and docker/pkg/discovery. The unit tests
+ of those packages broke reproducibly the builds in IBS.
+
+-------------------------------------------------------------------
+Mon Jan 29 14:39:02 UTC 2018 - vrothberg@suse.com
+
+- Disable flaky tests github.com/docker/docker/pkg/discovery/kv.
+
+-------------------------------------------------------------------
+Fri Jan 26 07:15:53 UTC 2018 - vrothberg@suse.com
+
+- Add patch to support mirroring of private/non-upstream registries. As soon as
+ the upstream PR (https://github.com/moby/moby/pull/34319) is merged, this
+ patch will be replaced by the backported one from upstream.
+ + private-registry-0001-Add-private-registry-mirror-support.patch
+ fix bsc#1074971
+
-------------------------------------------------------------------
Fri Jan 19 14:12:32 UTC 2018 - asarai@suse.com
diff --git a/docker.spec b/docker.spec
index 7f98a88..a007cd9 100644
--- a/docker.spec
+++ b/docker.spec
@@ -26,6 +26,17 @@
%define _fillupdir /var/adm/fillup-templates
%endif
+# Handle _multibuild magic.
+%define flavour @BUILD_FLAVOR@%{nil}
+
+# We split the Name: into "realname" and "name_suffix".
+%define realname docker
+%if "%flavour" == ""
+%define name_suffix %{nil}
+%else
+%define name_suffix -%{flavour}
+%endif
+
# Used when generating the "build" information for Docker version. The value of
# git_commit_epoch is unused here (we use SOURCE_DATE_EPOCH, which rpm
# helpfully injects into our build environment from the changelog). If you want
@@ -40,7 +51,7 @@
%define required_dockerrunc 69663f0bd4b60df09991c08812a60108003fa340
%define required_libnetwork 3ac297bc7fd0afec9051bbb47024c9bc1d75bf5b
-Name: docker
+Name: %{realname}%{name_suffix}
Version: 18.06.0_ce
Release: 0
Summary: The Linux container runtime
@@ -48,8 +59,10 @@ License: Apache-2.0
Group: System/Management
Url: http://www.docker.io
# TODO(VR): check those SOURCE files below
-Source: %{name}-%{version}.tar.xz
+Source: %{realname}-%{version}.tar.xz
Source1: docker.service
+# bsc#1086185 -- but we only apply this on Kubic.
+Source2: docker-kubic-service.conf
Source3: 80-docker.rules
Source4: sysconfig.docker
Source6: docker-rpmlintrc
@@ -68,6 +81,9 @@ Patch400: bsc1073877-0001-apparmor-allow-receiving-of-signals-from-docker-
Patch401: bsc1073877-0002-apparmor-clobber-docker-default-profile-on-start.patch
# SUSE-BACKPORT: Backport of https://github.com/docker/cli/pull/1242. bsc#1100727
Patch402: bsc1100727-0001-build-add-buildmode-pie.patch
+# SUSE-FEATURE: Add support to mirror inofficial/private registries
+# (https://github.com/moby/moby/pull/34319)
+Patch500: private-registry-0001-Add-private-registry-mirror-support.patch
BuildRequires: audit
BuildRequires: bash-completion
BuildRequires: ca-certificates
@@ -86,14 +102,14 @@ Requires: ca-certificates-mozilla
# Required in order for networking to work. fix_bsc_1057743 is a work-around
# for some old packaging issues (where rpm would delete a binary that was
# installed by docker-libnetwork). See bsc#1057743 for more details.
-Requires: docker-libnetwork-git = %{required_libnetwork}
+Requires: docker-libnetwork%{name_suffix}-git = %{required_libnetwork}
Requires: fix_bsc_1057743
# Containerd and runC are required as they are the only currently supported
# execdrivers of Docker. NOTE: The version pinning here matches upstream's
# vendor.conf to ensure that we don't use a slightly incompatible version of
# runC or containerd (which would be bad).
-Requires: containerd-git = %{required_containerd}
-Requires: docker-runc-git = %{required_dockerrunc}
+Requires: containerd%{name_suffix}-git = %{required_containerd}
+Requires: docker-runc%{name_suffix}-git = %{required_dockerrunc}
# Needed for --init support. We don't use "tini", we use our own implementation
# which handles edge-cases better.
Requires: catatonit
@@ -116,11 +132,22 @@ Obsoletes: docker-image-migrator
# different storage-driver than devicemapper
Recommends: lvm2 >= 2.2.89
Conflicts: lxc < 1.0
-BuildRoot: %{_tmppath}/%{name}-%{version}-build
ExcludeArch: s390 ppc
# Make sure we build with go 1.10
BuildRequires: go-go-md2man
BuildRequires: golang(API) = 1.10
+# KUBIC-SPECIFIC: This was required when upgrading from the original kubic
+# packaging, when everything was renamed to -kubic. It also is
+# used to ensure that nothing complains too much when using
+# -kubic packages. Hopfully it can be removed one day.
+%if "%flavour" == "kubic"
+# Obsolete old packege without the -kubic suffix
+Obsoletes: %{realname} = 1.12.6
+Obsoletes: %{realname}_1_12_6
+# Conflict with non-kubic package, and provide equivalent
+Conflicts: %{realname}
+Provides: %{realname} = %{version}
+%endif
%description
Docker complements LXC with a high-level API which operates at the process
@@ -135,8 +162,19 @@ service-oriented architectures, etc.
Summary: Bash Completion for %{name}
Group: System/Management
Requires: %{name} = %{version}
-Supplements: packageand(docker:bash-completion)
+Supplements: packageand(%{name}:bash-completion)
BuildArch: noarch
+# KUBIC-SPECIFIC: This was required when upgrading from the original kubic
+# packaging, when everything was renamed to -kubic. It also is
+# used to ensure that nothing complains too much when using
+# -kubic packages. Hopfully it can be removed one day.
+%if "%flavour" == "kubic"
+# Obsolete old packege without the -kubic suffix
+Obsoletes: %{realname}-bash-completion = 1.12.6
+# Conflict with non-kubic package, and provide equivalent
+Conflicts: %{realname}-bash-completion > 1.12.6
+Provides: %{realname}-bash-completion = %{version}
+%endif
%description bash-completion
Bash command line completion support for %{name}.
@@ -145,8 +183,19 @@ Bash command line completion support for %{name}.
Summary: Zsh Completion for %{name}
Group: System/Management
Requires: %{name} = %{version}
-Supplements: packageand(docker:zsh)
+Supplements: packageand(%{name}:zsh)
BuildArch: noarch
+# KUBIC-SPECIFIC: This was required when upgrading from the original kubic
+# packaging, when everything was renamed to -kubic. It also is
+# used to ensure that nothing complains too much when using
+# -kubic packages. Hopfully it can be removed one day.
+%if "%flavour" == "kubic"
+# Obsolete old packege without the -kubic suffix
+Obsoletes: %{realname}-zsh-completion = 1.12.6
+# Conflict with non-kubic package, and provide equivalent
+Conflicts: %{realname}-zsh-completion > 1.12.6
+Provides: %{realname}-zsh-completion = %{version}
+%endif
%description zsh-completion
Zsh command line completion support for %{name}.
@@ -165,12 +214,23 @@ Requires: libbtrfs-devel >= 3.8
Requires: procps
Requires: sqlite3-devel
Requires: golang(API) = 1.8
+# KUBIC-SPECIFIC: This was required when upgrading from the original kubic
+# packaging, when everything was renamed to -kubic. It also is
+# used to ensure that nothing complains too much when using
+# -kubic packages. Hopfully it can be removed one day.
+%if "%flavour" == "kubic"
+# Obsolete old packege without the -kubic suffix
+Obsoletes: %{realname}-test = 1.12.6
+# Conflict with non-kubic package, and provide equivalent
+Conflicts: %{realname}-test > 1.12.6
+Provides: %{realname}-test = %{version}
+%endif
%description test
Test package for docker. It contains the source code and the tests.
%prep
-%setup -q
+%setup -q -n %{realname}-%{version}
%if 0%{?is_opensuse}
# nothing
%else
@@ -184,6 +244,10 @@ Test package for docker. It contains the source code and the tests.
%patch401 -p1
# bsc#1100727
%patch402 -p1
+%if "%flavour" == "kubic"
+# PATCH-SUSE: Mirror patch.
+%patch500 -p1
+%endif
cp %{SOURCE7} .
cp %{SOURCE9} .
@@ -268,8 +332,8 @@ install -Dd -m 0755 \
%{buildroot}%{_sysconfdir}/init.d \
%{buildroot}%{_sbindir}
-install -D -m0644 components/cli/contrib/completion/bash/docker "%{buildroot}%{_sysconfdir}/bash_completion.d/%{name}"
-install -D -m0644 components/cli/contrib/completion/zsh/_docker "%{buildroot}%{_sysconfdir}/zsh_completion.d/%{name}"
+install -D -m0644 components/cli/contrib/completion/bash/docker "%{buildroot}%{_sysconfdir}/bash_completion.d/%{realname}"
+install -D -m0644 components/cli/contrib/completion/zsh/_docker "%{buildroot}%{_sysconfdir}/zsh_completion.d/%{realname}"
# copy all for the test package
install -d %{buildroot}%{_prefix}/src/docker/
cp -a components/engine/. %{buildroot}%{_prefix}/src/docker/engine
@@ -278,17 +342,20 @@ cp -a components/cli/. %{buildroot}%{_prefix}/src/docker/cli
#
# systemd service
#
-install -D -m 0644 %{SOURCE1} %{buildroot}%{_unitdir}/%{name}.service
+install -D -m0644 %{SOURCE1} %{buildroot}%{_unitdir}/%{realname}.service
+%if "%flavour" == "kubic"
+install -D -m0644 %{SOURCE2} %{buildroot}%{_unitdir}/%{realname}.service.d/90-kubic.conf
+%endif
ln -sf service %{buildroot}%{_sbindir}/rcdocker
#
# udev rules that prevents dolphin to show all docker devices and slows down
# upstream report https://bugs.kde.org/show_bug.cgi?id=329930
#
-install -D -m 0644 %{SOURCE3} %{buildroot}%{_udevrulesdir}/80-%{name}.rules
+install -D -m 0644 %{SOURCE3} %{buildroot}%{_udevrulesdir}/80-%{realname}.rules
# audit rules
-install -D -m 0640 %{SOURCE8} %{buildroot}%{_sysconfdir}/audit/rules.d/%{name}.rules
+install -D -m 0640 %{SOURCE8} %{buildroot}%{_sysconfdir}/audit/rules.d/%{realname}.rules
# sysconfig file
install -D -m 644 %{SOURCE4} %{buildroot}%{_fillupdir}/sysconfig.docker
@@ -305,10 +372,10 @@ install -p -m 644 components/cli/man/man8/*.8 %{buildroot}%{_mandir}/man8
%pre
getent group docker >/dev/null || groupadd -r docker
-%service_add_pre %{name}.service
+%service_add_pre %{realname}.service
%post
-%service_add_post %{name}.service
+%service_add_post %{realname}.service
%{fillup_only -n docker}
# NOTE: This is a pretty hacky way of getting around the fact we've removed
@@ -323,10 +390,10 @@ getent group docker >/dev/null || groupadd -r docker
)
%preun
-%service_del_preun %{name}.service
+%service_del_preun %{realname}.service
%postun
-%service_del_postun %{name}.service
+%service_del_postun %{realname}.service
%files
%defattr(-,root,root)
@@ -335,9 +402,13 @@ getent group docker >/dev/null || groupadd -r docker
%{_bindir}/docker
%{_bindir}/dockerd
%{_sbindir}/rcdocker
-%{_unitdir}/%{name}.service
-%config %{_sysconfdir}/audit/rules.d/%{name}.rules
-%{_udevrulesdir}/80-%{name}.rules
+%{_unitdir}/%{realname}.service
+%if "%flavour" == "kubic"
+%dir %{_unitdir}/%{realname}.service.d/
+%{_unitdir}/%{realname}.service.d/90-kubic.conf
+%endif
+%config %{_sysconfdir}/audit/rules.d/%{realname}.rules
+%{_udevrulesdir}/80-%{realname}.rules
%{_fillupdir}/sysconfig.docker
%dir %{_localstatedir}/lib/docker/
%{_mandir}/man1/docker-*.1%{ext_man}
@@ -347,11 +418,11 @@ getent group docker >/dev/null || groupadd -r docker
%files bash-completion
%defattr(-,root,root)
-%config %{_sysconfdir}/bash_completion.d/%{name}
+%config %{_sysconfdir}/bash_completion.d/%{realname}
%files zsh-completion
%defattr(-,root,root)
-%config %{_sysconfdir}/zsh_completion.d/%{name}
+%config %{_sysconfdir}/zsh_completion.d/%{realname}
%files test
%defattr(-,root,root)
diff --git a/private-registry-0001-Add-private-registry-mirror-support.patch b/private-registry-0001-Add-private-registry-mirror-support.patch
new file mode 100644
index 0000000..5a88ed4
--- /dev/null
+++ b/private-registry-0001-Add-private-registry-mirror-support.patch
@@ -0,0 +1,1163 @@
+From a709df0266457218086de2747c70a1b001fe745f Mon Sep 17 00:00:00 2001
+From: Valentin Rothberg
+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
+Signed-off-by: Valentin Rothberg
+---
+ .../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
+index 6cda223a1181..308eb83f2116 100644
+--- a/components/engine/daemon/config/config.go
++++ b/components/engine/daemon/config/config.go
+@@ -439,6 +439,10 @@ func findConfigurationConflicts(config map[string]interface{}, flags *pflag.Flag
+ // 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
++ }
+ if flag := flags.Lookup(key); flag == nil {
+ unknownKeys[key] = value
+ }
+diff --git a/components/engine/daemon/reload.go b/components/engine/daemon/reload.go
+index 210864ff879d..5e744c5dcf8d 100644
+--- 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{}
+
+@@ -64,6 +70,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)
+ }
+
+@@ -293,6 +302,30 @@ 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/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{
+--
+2.18.0
+