From cc1fefc9bf4646a2432e8a492b44066b6529d6c67997787cb6787ba30f5471ca Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 20 Aug 2018 08:55:46 +0000 Subject: [PATCH] - 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 OBS-URL: https://build.opensuse.org/package/show/Virtualization:containers/docker?expand=0&rev=259 --- _multibuild | 3 + docker-kubic-service.conf | 4 + docker-rpmlintrc | 16 +- docker.changes | 69 +- docker.spec | 117 +- ...-Add-private-registry-mirror-support.patch | 1163 +++++++++++++++++ 6 files changed, 1337 insertions(+), 35 deletions(-) create mode 100644 _multibuild create mode 100644 docker-kubic-service.conf create mode 100644 private-registry-0001-Add-private-registry-mirror-support.patch 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 +