Browse code

Cleanup legacy mirror string to registry host

Move the conversion to its own function and add unit tests.

Signed-off-by: Derek McGowan <derek@mcg.dev>

Derek McGowan authored on 2024/04/27 13:39:28
Showing 2 changed files
... ...
@@ -50,29 +50,7 @@ func (daemon *Daemon) mergeLegacyConfig(host string, hosts []docker.RegistryHost
50 50
 	}
51 51
 	sc := daemon.registryService.ServiceConfig()
52 52
 	if host == "docker.io" && len(sc.Mirrors) > 0 {
53
-		var mirrorHosts []docker.RegistryHost
54
-		for _, mirror := range sc.Mirrors {
55
-			h := hosts[0]
56
-			h.Capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve
57
-
58
-			u, err := url.Parse(mirror)
59
-			if err != nil || u.Host == "" {
60
-				u, err = url.Parse(fmt.Sprintf("//%s", mirror))
61
-			}
62
-			if err == nil && u.Host != "" {
63
-				h.Host = u.Host
64
-				h.Path = strings.TrimRight(u.Path, "/")
65
-				if !strings.HasSuffix(h.Path, defaultPath) {
66
-					h.Path = path.Join(defaultPath, h.Path)
67
-				}
68
-			} else {
69
-				h.Host = mirror
70
-				h.Path = defaultPath
71
-			}
72
-
73
-			mirrorHosts = append(mirrorHosts, h)
74
-		}
75
-		hosts = append(mirrorHosts, hosts[0])
53
+		hosts = mirrorsToRegistryHosts(sc.Mirrors, hosts[0])
76 54
 	}
77 55
 	hostDir := hostconfig.HostDirFromRoot(registry.CertsDir())
78 56
 	for i := range hosts {
... ...
@@ -93,13 +71,10 @@ func (daemon *Daemon) mergeLegacyConfig(host string, hosts []docker.RegistryHost
93 93
 			}
94 94
 		}
95 95
 		if daemon.registryService.IsInsecureRegistry(hosts[i].Host) {
96
-			if t.TLSClientConfig != nil {
97
-				t.TLSClientConfig.InsecureSkipVerify = true
98
-			} else {
99
-				t.TLSClientConfig = &tls.Config{
100
-					InsecureSkipVerify: true,
101
-				}
96
+			if t.TLSClientConfig == nil {
97
+				t.TLSClientConfig = &tls.Config{} //nolint: gosec // G402: TLS MinVersion too low.
102 98
 			}
99
+			t.TLSClientConfig.InsecureSkipVerify = true
103 100
 
104 101
 			hosts[i].Client.Transport = docker.NewHTTPFallback(hosts[i].Client.Transport)
105 102
 		}
... ...
@@ -107,6 +82,39 @@ func (daemon *Daemon) mergeLegacyConfig(host string, hosts []docker.RegistryHost
107 107
 	return hosts, nil
108 108
 }
109 109
 
110
+func mirrorsToRegistryHosts(mirrors []string, dHost docker.RegistryHost) []docker.RegistryHost {
111
+	var mirrorHosts []docker.RegistryHost
112
+	for _, mirror := range mirrors {
113
+		h := dHost
114
+		h.Capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve
115
+
116
+		u, err := url.Parse(mirror)
117
+		if err != nil || u.Host == "" {
118
+			u, err = url.Parse(fmt.Sprintf("dummy://%s", mirror))
119
+		}
120
+		if err == nil && u.Host != "" {
121
+			h.Host = u.Host
122
+			h.Path = strings.TrimSuffix(u.Path, "/")
123
+
124
+			// For compatibility with legacy mirrors, ensure ends with /v2
125
+			// NOTE: Use newer configuration to completely override the path
126
+			if !strings.HasSuffix(h.Path, defaultPath) {
127
+				h.Path = path.Join(h.Path, defaultPath)
128
+			}
129
+			if u.Scheme != "dummy" {
130
+				h.Scheme = u.Scheme
131
+			}
132
+		} else {
133
+			h.Host = mirror
134
+			h.Path = defaultPath
135
+		}
136
+
137
+		mirrorHosts = append(mirrorHosts, h)
138
+	}
139
+	return append(mirrorHosts, dHost)
140
+
141
+}
142
+
110 143
 func loadTLSConfig(d string) (*tls.Config, error) {
111 144
 	fs, err := os.ReadDir(d)
112 145
 	if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) {
... ...
@@ -121,10 +129,10 @@ func loadTLSConfig(d string) (*tls.Config, error) {
121 121
 		keyPairs []keyPair
122 122
 	)
123 123
 	for _, f := range fs {
124
-		if strings.HasSuffix(f.Name(), ".crt") {
124
+		switch filepath.Ext(f.Name()) {
125
+		case ".crt":
125 126
 			rootCAs = append(rootCAs, filepath.Join(d, f.Name()))
126
-		}
127
-		if strings.HasSuffix(f.Name(), ".cert") {
127
+		case ".cert":
128 128
 			keyPairs = append(keyPairs, keyPair{
129 129
 				Certificate: filepath.Join(d, f.Name()),
130 130
 				Key:         filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"),
... ...
@@ -150,7 +158,7 @@ func loadTLSConfig(d string) (*tls.Config, error) {
150 150
 	for _, p := range rootCAs {
151 151
 		dt, err := os.ReadFile(p)
152 152
 		if err != nil {
153
-			return nil, errors.Wrapf(err, "failed to read %s", p)
153
+			return nil, err
154 154
 		}
155 155
 		tc.RootCAs.AppendCertsFromPEM(dt)
156 156
 	}
157 157
new file mode 100644
... ...
@@ -0,0 +1,107 @@
0
+package daemon // import "github.com/docker/docker/daemon"
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/containerd/containerd/remotes/docker"
6
+	"gotest.tools/v3/assert"
7
+	is "gotest.tools/v3/assert/cmp"
8
+)
9
+
10
+func TestMirrorsToHosts(t *testing.T) {
11
+	pullCaps := docker.HostCapabilityPull | docker.HostCapabilityResolve
12
+	allCaps := docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
13
+	defaultRegistry := testRegistryHost("https", "registry-1.docker.com", "/v2", allCaps)
14
+	for _, tc := range []struct {
15
+		mirrors  []string
16
+		dhost    docker.RegistryHost
17
+		expected []docker.RegistryHost
18
+	}{
19
+		{
20
+			mirrors: []string{"https://localhost:5000"},
21
+			dhost:   defaultRegistry,
22
+			expected: []docker.RegistryHost{
23
+				testRegistryHost("https", "localhost:5000", "/v2", pullCaps),
24
+				defaultRegistry,
25
+			},
26
+		},
27
+		{
28
+			mirrors: []string{"http://localhost:5000"},
29
+			dhost:   defaultRegistry,
30
+			expected: []docker.RegistryHost{
31
+				testRegistryHost("http", "localhost:5000", "/v2", pullCaps),
32
+				defaultRegistry,
33
+			},
34
+		},
35
+		{
36
+			mirrors: []string{"http://localhost:5000/v2"},
37
+			dhost:   defaultRegistry,
38
+			expected: []docker.RegistryHost{
39
+				testRegistryHost("http", "localhost:5000", "/v2", pullCaps),
40
+				defaultRegistry,
41
+			},
42
+		},
43
+		{
44
+			mirrors: []string{"localhost:5000"},
45
+			dhost:   defaultRegistry,
46
+			expected: []docker.RegistryHost{
47
+				testRegistryHost("https", "localhost:5000", "/v2", pullCaps),
48
+				defaultRegistry,
49
+			},
50
+		},
51
+		{
52
+			mirrors: []string{"localhost:5000/trailingslash/"},
53
+			dhost:   defaultRegistry,
54
+			expected: []docker.RegistryHost{
55
+				testRegistryHost("https", "localhost:5000", "/trailingslash/v2", pullCaps),
56
+				defaultRegistry,
57
+			},
58
+		},
59
+		{
60
+			mirrors: []string{"localhost:5000/2trailingslash//"},
61
+			dhost:   defaultRegistry,
62
+			expected: []docker.RegistryHost{
63
+				testRegistryHost("https", "localhost:5000", "/2trailingslash/v2", pullCaps),
64
+				defaultRegistry,
65
+			},
66
+		},
67
+		{
68
+			mirrors: []string{"localhost:5000/v2/"},
69
+			dhost:   defaultRegistry,
70
+			expected: []docker.RegistryHost{
71
+				testRegistryHost("https", "localhost:5000", "/v2", pullCaps),
72
+				defaultRegistry,
73
+			},
74
+		},
75
+		{
76
+			mirrors: []string{"localhost:5000/base"},
77
+			dhost:   defaultRegistry,
78
+			expected: []docker.RegistryHost{
79
+				testRegistryHost("https", "localhost:5000", "/base/v2", pullCaps),
80
+				defaultRegistry,
81
+			},
82
+		},
83
+		{
84
+			// Legacy mirror configuration always appended /v2, keep functionality the same
85
+			mirrors: []string{"localhost:5000/v2/base"},
86
+			dhost:   defaultRegistry,
87
+			expected: []docker.RegistryHost{
88
+				testRegistryHost("https", "localhost:5000", "/v2/base/v2", pullCaps),
89
+				defaultRegistry,
90
+			},
91
+		},
92
+	} {
93
+		actual := mirrorsToRegistryHosts(tc.mirrors, tc.dhost)
94
+
95
+		assert.Check(t, is.DeepEqual(actual, tc.expected))
96
+	}
97
+}
98
+
99
+func testRegistryHost(scheme, host, path string, caps docker.HostCapabilities) docker.RegistryHost {
100
+	return docker.RegistryHost{
101
+		Host:         host,
102
+		Scheme:       scheme,
103
+		Path:         path,
104
+		Capabilities: caps,
105
+	}
106
+}