Skip to content

Commit

Permalink
support private registry mirror (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
justadogistaken authored Aug 4, 2021
1 parent a904087 commit 4919324
Show file tree
Hide file tree
Showing 11 changed files with 629 additions and 4 deletions.
106 changes: 106 additions & 0 deletions api/types/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,104 @@ 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"
)

type regMirrorJSONHelper struct {
Domain string `json:"domain,omitempty"` // domain is domainName:port(if port is specified)
Mirrors []string `json:"mirrors,omitempty"`
}

type RegMirror struct {
Domain url.URL `json:"domain,omitempty"` // domain is domainName:port(if port is specified)
Mirrors []url.URL `json:"mirrors,omitempty"`
}

func (rm *RegMirror) UnmarshalJSON(data []byte) error {
var (
helper = regMirrorJSONHelper{}
domainURL url.URL
mirrorURLs []url.URL
)

err := json.Unmarshal(data, &helper)
if err != nil {
return err
}

u, err := parseURL(helper.Domain)
if err != nil {
return err
}
domainURL = *u

for _, m := range helper.Mirrors {
u, err = parseURL(m)
if err != nil {
return err
}
mirrorURLs = append(mirrorURLs, *u)
}

rm.Domain, rm.Mirrors = domainURL, mirrorURLs
return nil
}

func (rm *RegMirror) MarshalJSON() ([]byte, error) {
var (
helper = regMirrorJSONHelper{}
domainURL string
mirrorURLs []string
)

domainURL = rm.Domain.String()
for _, mirror := range rm.Mirrors {
mirrorURLs = append(mirrorURLs, mirror.String())
}
helper.Domain, helper.Mirrors = domainURL, mirrorURLs
return json.Marshal(helper)
}

func (rm *RegMirror) ContainerMirror(str string) bool {
for _, mirror := range rm.Mirrors {
if mirror.String() == str {
return true
}
}
return false
}

func NewRegistryMirror(domain string, mirrors []string) (RegMirror, error) {
reg := RegMirror{}
domainU, err := parseURL(domain)
if err != nil {
return RegMirror{}, err
}

reg.Domain = *domainU
for _, str := range mirrors {
mirrorU, err := parseURL(str)
if err != nil {
return RegMirror{}, err
}
reg.Mirrors = append(reg.Mirrors, *mirrorU)
}
return reg, nil
}

// ServiceConfig stores daemon registry services configuration.
type ServiceConfig struct {
AllowNondistributableArtifactsCIDRs []*NetIPNet
AllowNondistributableArtifactsHostnames []string
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
Mirrors []string
RegMirrors map[string]RegMirror
}

// NetIPNet is the net.IPNet type, which can be marshalled and
Expand Down Expand Up @@ -117,3 +203,23 @@ type DistributionInspect struct {
// obtained by parsing the manifest
Platforms []v1.Platform
}

func parseURL(str string) (*url.URL, error) {
str = strings.ToLower(str)
newURL, err := url.Parse(str)
if err != nil {
return nil, err
}
if newURL.Scheme == "" {
newURL.Scheme = "https"
return parseURL("https://" + str)
}
if newURL.Host == "" {
return nil, fmt.Errorf("failed to parse %s to url, err: host is empty", str)
}
if newURL.Scheme != "http" && newURL.Scheme != "https" {
return nil, fmt.Errorf("failed to parse %s to url, err: unsupported scheme %s", str, newURL.Scheme)
}

return newURL, nil
}
4 changes: 4 additions & 0 deletions daemon/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,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 {
// support mirror-registries
if key == "mirror-registries" {
continue
}
if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] {
unknownKeys[key] = value
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/info_unix.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !windows
// +build !windows darwin

package daemon // import "github.com/docker/docker/daemon"

Expand Down
29 changes: 29 additions & 0 deletions daemon/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
// - Cluster discovery (reconfigure and restart)
// - Daemon labels
// - Insecure registries
// - Mirror Registries
// - Registry mirrors
// - Daemon live restore
func (daemon *Daemon) Reload(conf *config.Config) (err error) {
Expand Down Expand Up @@ -62,6 +63,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
if err := daemon.reloadRegistryMirrors(conf, attributes); err != nil {
return err
}
if err := daemon.reloadMirrorRegistries(conf, attributes); err != nil {
return err
}
if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
return err
}
Expand Down Expand Up @@ -295,6 +299,31 @@ func (daemon *Daemon) reloadRegistryMirrors(conf *config.Config, attributes map[
return nil
}

// reloadMirrorRegistries updates configuration with multiple registry mirror options
// and updates the passed attributes
func (daemon *Daemon) reloadMirrorRegistries(conf *config.Config, attributes map[string]string) error {
// update corresponding configuration
if conf.IsValueSet("mirror-registries") {
daemon.configStore.MirrorRegistries = conf.MirrorRegistries
if err := daemon.RegistryService.LoadMirrorRegistries(conf.MirrorRegistries); err != nil {
return err
}
}

// prepare reload event attributes with updatable configurations
if daemon.configStore.MirrorRegistries != nil {
mirrors, err := json.Marshal(daemon.configStore.MirrorRegistries)
if err != nil {
return err
}
attributes["mirror-registries"] = string(mirrors)
} else {
attributes["mirror-registries"] = "[]"
}

return nil
}

// reloadLiveRestore updates configuration with live restore option
// and updates the passed attributes
func (daemon *Daemon) reloadLiveRestore(conf *config.Config, attributes map[string]string) error {
Expand Down
114 changes: 114 additions & 0 deletions daemon/reload_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package daemon // import "github.com/docker/docker/daemon"

import (
registrytypes "github.com/docker/docker/api/types/registry"
"os"
"reflect"
"sort"
Expand Down Expand Up @@ -201,6 +202,119 @@ func TestDaemonReloadMirrors(t *testing.T) {
}
}

func TestDaemonReloadMirrorRegistries(t *testing.T) {
daemon := &Daemon{
imageService: images.NewImageService(images.ImageServiceConfig{}),
}
muteLogs()

var (
err error
mirrorRegistryA, mirrorRegistryB, mirrorRegistryC registrytypes.RegMirror
)

mirrorRegistryA, err = registrytypes.NewRegistryMirror("https://registry.test1.com", []string{"https://mirror.test1.com"})
if err != nil {
t.Fatal(err)
}
mirrorRegistryB, err = registrytypes.NewRegistryMirror("https://registry.test2.com:5000", []string{"https://mirror.test2.com"})
if err != nil {
t.Fatal(err)
}
mirrorRegistryC, err = registrytypes.NewRegistryMirror("http://registry.test3.com", []string{"http://mirror.test3.com"})
if err != nil {
t.Fatal(err)
}
daemon.RegistryService, err = registry.NewService(registry.ServiceOptions{
MirrorRegistries: []registrytypes.RegMirror{
mirrorRegistryA, mirrorRegistryB, mirrorRegistryC,
},
})
if err != nil {
t.Fatal(err)
}
daemon.configStore = &config.Config{}

type pair struct {
domain string
mirrors []string
mirrorReg registrytypes.RegMirror
}
testMirrors := []pair{
{
domain: "registry.test1.com",
mirrors: []string{"https://mirror.test1.com"},
},
{
domain: "registry.test2.com:5000",
mirrors: []string{"https://mirror.test2.com"},
},
{
domain: "registry.test3.com",
mirrors: []string{"http://mirror.test3.com"},
},
}
serviceConfig := daemon.RegistryService.ServiceConfig()
for _, tm := range testMirrors {
reg, ok := serviceConfig.RegMirrors[tm.domain]
if !ok {
t.Fatalf("%s shoule be present in service config", tm.domain)
}
for _, mir := range tm.mirrors {
if !reg.ContainerMirror(mir) {
t.Fatalf("%s should be one of mirrors for %s, but it does not", mir, tm.domain)
}
}
}

testMirrors = []pair{
{
domain: "registry.test1.com",
mirrors: []string{"https://mirror.test1.com"},
mirrorReg: mirrorRegistryA,
},
{
domain: "registry.test2.com:5000",
mirrors: []string{"https://mirror.test2.com"},
mirrorReg: mirrorRegistryB,
},
{
domain: "registry.test3.com",
mirrors: []string{"http://mirror.test3.com"},
mirrorReg: mirrorRegistryC,
},
}

for _, tm := range testMirrors {
valuesSets := make(map[string]interface{})
valuesSets["mirror-registries"] = []registrytypes.RegMirror{tm.mirrorReg}
newConfig := &config.Config{
CommonConfig: config.CommonConfig{
ServiceOptions: registry.ServiceOptions{
MirrorRegistries: []registrytypes.RegMirror{tm.mirrorReg},
},
ValuesSet: valuesSets,
},
}

err = daemon.Reload(newConfig)
if err != nil {
t.Fatal(err)
}

serviceConfig := daemon.RegistryService.ServiceConfig()
if reg, ok := serviceConfig.RegMirrors[tm.domain]; !ok {
t.Fatalf("%s should be present in service config", tm.domain)
} else {
for _, mir := range tm.mirrors {
if !reg.ContainerMirror(mir) {
t.Fatalf("%s should be one of mirrors for %s, but it does not", mir, tm.domain)
}
}
}
}
}

func TestDaemonReloadInsecureRegistries(t *testing.T) {
daemon := &Daemon{
imageService: images.NewImageService(images.ImageServiceConfig{}),
Expand Down
7 changes: 7 additions & 0 deletions distribution/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
continue
}

if endpoint.Mirror {
ctx = addOriginDomain(ctx, endpoints[len(endpoints)-1])
}
if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil {
// Was this pull cancelled? If so, don't try to fall
// back.
Expand Down Expand Up @@ -194,3 +197,7 @@ func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.D

return store.AddDigest(dgstRef, id, true)
}

func addOriginDomain(ctx context.Context, originalEndpoint registry.APIEndpoint) context.Context {
return context.WithValue(ctx, "domain", originalEndpoint.URL.Scheme+"://"+originalEndpoint.URL.Host)
}
24 changes: 24 additions & 0 deletions distribution/pull_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package distribution // import "github.com/docker/docker/distribution"

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -65,7 +66,30 @@ type v2Puller struct {
confirmedV2 bool
}

func (p *v2Puller) setMirrorHeaderForRequest(ctx context.Context) {
var (
domain = ctx.Value("domain").(string)
authConfig = p.config.AuthConfig
)
if p.config.MetaHeaders == nil {
p.config.MetaHeaders = map[string][]string{}
}
// p.endpoint.URL.Scheme should be only http/https, so ignore the check.
p.config.MetaHeaders["domain"] = []string{domain}
authConfig.ServerAddress = domain
authJSON, err := json.Marshal(authConfig)
if err != nil {
logrus.Errorf("failed to marshal auth config for %s", authConfig.ServerAddress)
return
}

p.config.MetaHeaders["domain-auth"] = []string{base64.StdEncoding.EncodeToString(authJSON)}
}

func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) {
if ctx.Value("domain") != nil {
p.setMirrorHeaderForRequest(ctx)
}
// TODO(tiborvass): was ReceiveTimeout
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
if err != nil {
Expand Down
Loading

0 comments on commit 4919324

Please sign in to comment.