Browse code

Move and refactor integration-cli/registry to internal/test

- Move the code from `integration-cli` to `internal/test`.
- Use `testingT` and `assert` when creating the registry.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2018/04/13 17:45:34
Showing 12 changed files
... ...
@@ -20,9 +20,9 @@ import (
20 20
 	"github.com/docker/docker/integration-cli/daemon"
21 21
 	"github.com/docker/docker/integration-cli/environment"
22 22
 	"github.com/docker/docker/integration-cli/fixtures/plugin"
23
-	"github.com/docker/docker/integration-cli/registry"
24 23
 	testdaemon "github.com/docker/docker/internal/test/daemon"
25 24
 	ienv "github.com/docker/docker/internal/test/environment"
25
+	"github.com/docker/docker/internal/test/registry"
26 26
 	"github.com/docker/docker/pkg/reexec"
27 27
 	"github.com/go-check/check"
28 28
 	"golang.org/x/net/context"
... ...
@@ -30,7 +30,7 @@ import (
30 30
 
31 31
 const (
32 32
 	// the private registry to use for tests
33
-	privateRegistryURL = "127.0.0.1:5000"
33
+	privateRegistryURL = registry.DefaultURL
34 34
 
35 35
 	// path to containerd's ctr binary
36 36
 	ctrBinary = "docker-containerd-ctr"
... ...
@@ -126,8 +126,9 @@ func (s *DockerRegistrySuite) OnTimeout(c *check.C) {
126 126
 }
127 127
 
128 128
 func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
129
-	testRequires(c, DaemonIsLinux, registry.Hosting, SameHostDaemon)
130
-	s.reg = setupRegistry(c, false, "", "")
129
+	testRequires(c, DaemonIsLinux, RegistryHosting, SameHostDaemon)
130
+	s.reg = registry.NewV2(c)
131
+	s.reg.WaitReady(c)
131 132
 	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
132 133
 		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
133 134
 	})
... ...
@@ -160,8 +161,9 @@ func (s *DockerSchema1RegistrySuite) OnTimeout(c *check.C) {
160 160
 }
161 161
 
162 162
 func (s *DockerSchema1RegistrySuite) SetUpTest(c *check.C) {
163
-	testRequires(c, DaemonIsLinux, registry.Hosting, NotArm64, SameHostDaemon)
164
-	s.reg = setupRegistry(c, true, "", "")
163
+	testRequires(c, DaemonIsLinux, RegistryHosting, NotArm64, SameHostDaemon)
164
+	s.reg = registry.NewV2(c, registry.Schema1)
165
+	s.reg.WaitReady(c)
165 166
 	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
166 167
 		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
167 168
 	})
... ...
@@ -194,8 +196,9 @@ func (s *DockerRegistryAuthHtpasswdSuite) OnTimeout(c *check.C) {
194 194
 }
195 195
 
196 196
 func (s *DockerRegistryAuthHtpasswdSuite) SetUpTest(c *check.C) {
197
-	testRequires(c, DaemonIsLinux, registry.Hosting, SameHostDaemon)
198
-	s.reg = setupRegistry(c, false, "htpasswd", "")
197
+	testRequires(c, DaemonIsLinux, RegistryHosting, SameHostDaemon)
198
+	s.reg = registry.NewV2(c, registry.Htpasswd)
199
+	s.reg.WaitReady(c)
199 200
 	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
200 201
 		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
201 202
 	})
... ...
@@ -230,7 +233,7 @@ func (s *DockerRegistryAuthTokenSuite) OnTimeout(c *check.C) {
230 230
 }
231 231
 
232 232
 func (s *DockerRegistryAuthTokenSuite) SetUpTest(c *check.C) {
233
-	testRequires(c, DaemonIsLinux, registry.Hosting, SameHostDaemon)
233
+	testRequires(c, DaemonIsLinux, RegistryHosting, SameHostDaemon)
234 234
 	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
235 235
 		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
236 236
 	})
... ...
@@ -252,7 +255,8 @@ func (s *DockerRegistryAuthTokenSuite) setupRegistryWithTokenService(c *check.C,
252 252
 	if s == nil {
253 253
 		c.Fatal("registry suite isn't initialized")
254 254
 	}
255
-	s.reg = setupRegistry(c, false, "token", tokenURL)
255
+	s.reg = registry.NewV2(c, registry.Token(tokenURL))
256
+	s.reg.WaitReady(c)
256 257
 }
257 258
 
258 259
 func init() {
... ...
@@ -405,8 +409,9 @@ func (ps *DockerPluginSuite) getPluginRepoWithTag() string {
405 405
 }
406 406
 
407 407
 func (ps *DockerPluginSuite) SetUpSuite(c *check.C) {
408
-	testRequires(c, DaemonIsLinux, registry.Hosting)
409
-	ps.registry = setupRegistry(c, false, "", "")
408
+	testRequires(c, DaemonIsLinux, RegistryHosting)
409
+	ps.registry = registry.NewV2(c)
410
+	ps.registry.WaitReady(c)
410 411
 
411 412
 	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
412 413
 	defer cancel()
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/docker/integration-cli/daemon"
17 17
 	"github.com/docker/docker/integration-cli/fixtures/plugin"
18 18
 	testdaemon "github.com/docker/docker/internal/test/daemon"
19
+	"github.com/docker/docker/internal/test/registry"
19 20
 	"github.com/go-check/check"
20 21
 	"golang.org/x/net/context"
21 22
 	"golang.org/x/sys/unix"
... ...
@@ -615,7 +616,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesStateReporting(c *check.C) {
615 615
 func (s *DockerSwarmSuite) TestAPISwarmServicesPlugin(c *check.C) {
616 616
 	testRequires(c, ExperimentalDaemon, DaemonIsLinux, IsAmd64)
617 617
 
618
-	reg := setupRegistry(c, false, "", "")
618
+	reg := registry.NewV2(c)
619 619
 	defer reg.Close()
620 620
 
621 621
 	repo := path.Join(privateRegistryURL, "swarm", "test:v1")
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"os"
8 8
 	"regexp"
9 9
 
10
-	"github.com/docker/docker/integration-cli/registry"
10
+	"github.com/docker/docker/internal/test/registry"
11 11
 	"github.com/go-check/check"
12 12
 )
13 13
 
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"net/http"
7 7
 	"os"
8 8
 
9
-	"github.com/docker/docker/integration-cli/registry"
9
+	"github.com/docker/docker/internal/test/registry"
10 10
 	"github.com/go-check/check"
11 11
 )
12 12
 
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"github.com/docker/docker/integration-cli/checker"
19 19
 	"github.com/docker/docker/integration-cli/cli"
20 20
 	"github.com/docker/docker/integration-cli/daemon"
21
-	"github.com/docker/docker/integration-cli/registry"
22 21
 	"github.com/docker/docker/integration-cli/request"
23 22
 	"github.com/go-check/check"
24 23
 	"github.com/gotestyourself/gotestyourself/icmd"
... ...
@@ -284,22 +283,6 @@ func parseEventTime(t time.Time) string {
284 284
 	return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond()))
285 285
 }
286 286
 
287
-func setupRegistry(c *check.C, schema1 bool, auth, tokenURL string) *registry.V2 {
288
-	reg, err := registry.NewV2(schema1, auth, tokenURL, privateRegistryURL)
289
-	c.Assert(err, check.IsNil)
290
-
291
-	// Wait for registry to be ready to serve requests.
292
-	for i := 0; i != 50; i++ {
293
-		if err = reg.Ping(); err == nil {
294
-			break
295
-		}
296
-		time.Sleep(100 * time.Millisecond)
297
-	}
298
-
299
-	c.Assert(err, check.IsNil, check.Commentf("Timeout waiting for test registry to become available: %v", err))
300
-	return reg
301
-}
302
-
303 287
 // appendBaseEnv appends the minimum set of environment variables to exec the
304 288
 // docker cli binary for testing with correct configuration to the given env
305 289
 // list.
306 290
deleted file mode 100644
... ...
@@ -1,214 +0,0 @@
1
-package registry // import "github.com/docker/docker/integration-cli/registry"
2
-
3
-import (
4
-	"fmt"
5
-	"io/ioutil"
6
-	"net/http"
7
-	"os"
8
-	"os/exec"
9
-	"path/filepath"
10
-
11
-	"github.com/opencontainers/go-digest"
12
-)
13
-
14
-const (
15
-	v2binary        = "registry-v2"
16
-	v2binarySchema1 = "registry-v2-schema1"
17
-)
18
-
19
-type testingT interface {
20
-	logT
21
-	Fatal(...interface{})
22
-	Fatalf(string, ...interface{})
23
-}
24
-
25
-type logT interface {
26
-	Logf(string, ...interface{})
27
-}
28
-
29
-// V2 represent a registry version 2
30
-type V2 struct {
31
-	cmd         *exec.Cmd
32
-	registryURL string
33
-	dir         string
34
-	auth        string
35
-	username    string
36
-	password    string
37
-	email       string
38
-}
39
-
40
-// NewV2 creates a v2 registry server
41
-func NewV2(schema1 bool, auth, tokenURL, registryURL string) (*V2, error) {
42
-	tmp, err := ioutil.TempDir("", "registry-test-")
43
-	if err != nil {
44
-		return nil, err
45
-	}
46
-	template := `version: 0.1
47
-loglevel: debug
48
-storage:
49
-    filesystem:
50
-        rootdirectory: %s
51
-http:
52
-    addr: %s
53
-%s`
54
-	var (
55
-		authTemplate string
56
-		username     string
57
-		password     string
58
-		email        string
59
-	)
60
-	switch auth {
61
-	case "htpasswd":
62
-		htpasswdPath := filepath.Join(tmp, "htpasswd")
63
-		// generated with: htpasswd -Bbn testuser testpassword
64
-		userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
65
-		username = "testuser"
66
-		password = "testpassword"
67
-		email = "test@test.org"
68
-		if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
69
-			return nil, err
70
-		}
71
-		authTemplate = fmt.Sprintf(`auth:
72
-    htpasswd:
73
-        realm: basic-realm
74
-        path: %s
75
-`, htpasswdPath)
76
-	case "token":
77
-		authTemplate = fmt.Sprintf(`auth:
78
-    token:
79
-        realm: %s
80
-        service: "registry"
81
-        issuer: "auth-registry"
82
-        rootcertbundle: "fixtures/registry/cert.pem"
83
-`, tokenURL)
84
-	}
85
-
86
-	confPath := filepath.Join(tmp, "config.yaml")
87
-	config, err := os.Create(confPath)
88
-	if err != nil {
89
-		return nil, err
90
-	}
91
-	defer config.Close()
92
-
93
-	if _, err := fmt.Fprintf(config, template, tmp, registryURL, authTemplate); err != nil {
94
-		os.RemoveAll(tmp)
95
-		return nil, err
96
-	}
97
-
98
-	binary := v2binary
99
-	if schema1 {
100
-		binary = v2binarySchema1
101
-	}
102
-	cmd := exec.Command(binary, confPath)
103
-	if err := cmd.Start(); err != nil {
104
-		os.RemoveAll(tmp)
105
-		return nil, err
106
-	}
107
-	return &V2{
108
-		cmd:         cmd,
109
-		dir:         tmp,
110
-		auth:        auth,
111
-		username:    username,
112
-		password:    password,
113
-		email:       email,
114
-		registryURL: registryURL,
115
-	}, nil
116
-}
117
-
118
-// Ping sends an http request to the current registry, and fail if it doesn't respond correctly
119
-func (r *V2) Ping() error {
120
-	// We always ping through HTTP for our test registry.
121
-	resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
122
-	if err != nil {
123
-		return err
124
-	}
125
-	resp.Body.Close()
126
-
127
-	fail := resp.StatusCode != http.StatusOK
128
-	if r.auth != "" {
129
-		// unauthorized is a _good_ status when pinging v2/ and it needs auth
130
-		fail = fail && resp.StatusCode != http.StatusUnauthorized
131
-	}
132
-	if fail {
133
-		return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
134
-	}
135
-	return nil
136
-}
137
-
138
-// Close kills the registry server
139
-func (r *V2) Close() {
140
-	r.cmd.Process.Kill()
141
-	r.cmd.Process.Wait()
142
-	os.RemoveAll(r.dir)
143
-}
144
-
145
-func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
146
-	// Split the digest into its algorithm and hex components.
147
-	dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex()
148
-
149
-	// The path to the target blob data looks something like:
150
-	//   baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
151
-	return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
152
-}
153
-
154
-// ReadBlobContents read the file corresponding to the specified digest
155
-func (r *V2) ReadBlobContents(t testingT, blobDigest digest.Digest) []byte {
156
-	// Load the target manifest blob.
157
-	manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest))
158
-	if err != nil {
159
-		t.Fatalf("unable to read blob: %s", err)
160
-	}
161
-
162
-	return manifestBlob
163
-}
164
-
165
-// WriteBlobContents write the file corresponding to the specified digest with the given content
166
-func (r *V2) WriteBlobContents(t testingT, blobDigest digest.Digest, data []byte) {
167
-	if err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
168
-		t.Fatalf("unable to write malicious data blob: %s", err)
169
-	}
170
-}
171
-
172
-// TempMoveBlobData moves the existing data file aside, so that we can replace it with a
173
-// malicious blob of data for example.
174
-func (r *V2) TempMoveBlobData(t testingT, blobDigest digest.Digest) (undo func()) {
175
-	tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
176
-	if err != nil {
177
-		t.Fatalf("unable to get temporary blob file: %s", err)
178
-	}
179
-	tempFile.Close()
180
-
181
-	blobFilename := r.getBlobFilename(blobDigest)
182
-
183
-	// Move the existing data file aside, so that we can replace it with a
184
-	// another blob of data.
185
-	if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
186
-		os.Remove(tempFile.Name())
187
-		t.Fatalf("unable to move data blob: %s", err)
188
-	}
189
-
190
-	return func() {
191
-		os.Rename(tempFile.Name(), blobFilename)
192
-		os.Remove(tempFile.Name())
193
-	}
194
-}
195
-
196
-// Username returns the configured user name of the server
197
-func (r *V2) Username() string {
198
-	return r.username
199
-}
200
-
201
-// Password returns the configured password of the server
202
-func (r *V2) Password() string {
203
-	return r.password
204
-}
205
-
206
-// Email returns the configured email of the server
207
-func (r *V2) Email() string {
208
-	return r.email
209
-}
210
-
211
-// Path returns the path where the registry write data
212
-func (r *V2) Path() string {
213
-	return filepath.Join(r.dir, "docker", "registry", "v2")
214
-}
215 1
deleted file mode 100644
... ...
@@ -1,66 +0,0 @@
1
-package registry // import "github.com/docker/docker/integration-cli/registry"
2
-
3
-import (
4
-	"net/http"
5
-	"net/http/httptest"
6
-	"regexp"
7
-	"strings"
8
-	"sync"
9
-)
10
-
11
-type handlerFunc func(w http.ResponseWriter, r *http.Request)
12
-
13
-// Mock represent a registry mock
14
-type Mock struct {
15
-	server   *httptest.Server
16
-	hostport string
17
-	handlers map[string]handlerFunc
18
-	mu       sync.Mutex
19
-}
20
-
21
-// RegisterHandler register the specified handler for the registry mock
22
-func (tr *Mock) RegisterHandler(path string, h handlerFunc) {
23
-	tr.mu.Lock()
24
-	defer tr.mu.Unlock()
25
-	tr.handlers[path] = h
26
-}
27
-
28
-// NewMock creates a registry mock
29
-func NewMock(t testingT) (*Mock, error) {
30
-	testReg := &Mock{handlers: make(map[string]handlerFunc)}
31
-
32
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33
-		url := r.URL.String()
34
-
35
-		var matched bool
36
-		var err error
37
-		for re, function := range testReg.handlers {
38
-			matched, err = regexp.MatchString(re, url)
39
-			if err != nil {
40
-				t.Fatal("Error with handler regexp")
41
-			}
42
-			if matched {
43
-				function(w, r)
44
-				break
45
-			}
46
-		}
47
-
48
-		if !matched {
49
-			t.Fatalf("Unable to match %s with regexp", url)
50
-		}
51
-	}))
52
-
53
-	testReg.server = ts
54
-	testReg.hostport = strings.Replace(ts.URL, "http://", "", 1)
55
-	return testReg, nil
56
-}
57
-
58
-// URL returns the url of the registry
59
-func (tr *Mock) URL() string {
60
-	return tr.hostport
61
-}
62
-
63
-// Close closes mock and releases resources
64
-func (tr *Mock) Close() {
65
-	tr.server.Close()
66
-}
67 1
deleted file mode 100644
... ...
@@ -1,12 +0,0 @@
1
-package registry // import "github.com/docker/docker/integration-cli/registry"
2
-
3
-import "os/exec"
4
-
5
-// Hosting returns wether the host can host a registry (v2) or not
6
-func Hosting() bool {
7
-	// for now registry binary is built only if we're running inside
8
-	// container through `make test`. Figure that out by testing if
9
-	// registry binary is in PATH.
10
-	_, err := exec.LookPath(v2binary)
11
-	return err == nil
12
-}
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/api/types"
15 15
 	"github.com/docker/docker/client"
16 16
 	"github.com/docker/docker/integration-cli/requirement"
17
+	"github.com/docker/docker/internal/test/registry"
17 18
 )
18 19
 
19 20
 func ArchitectureIsNot(arch string) bool {
... ...
@@ -183,6 +184,15 @@ func IsolationIsProcess() bool {
183 183
 	return IsolationIs("process")
184 184
 }
185 185
 
186
+// RegistryHosting returns wether the host can host a registry (v2) or not
187
+func RegistryHosting() bool {
188
+	// for now registry binary is built only if we're running inside
189
+	// container through `make test`. Figure that out by testing if
190
+	// registry binary is in PATH.
191
+	_, err := exec.LookPath(registry.V2binary)
192
+	return err == nil
193
+}
194
+
186 195
 // testRequires checks if the environment satisfies the requirements
187 196
 // for the test to run or skips the tests.
188 197
 func testRequires(c requirement.SkipT, requirements ...requirement.Test) {
189 198
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package registry
1
+
2
+// Schema1 sets the registry to serve v1 api
3
+func Schema1(c *Config) {
4
+	c.schema1 = true
5
+}
6
+
7
+// Htpasswd sets the auth method with htpasswd
8
+func Htpasswd(c *Config) {
9
+	c.auth = "htpasswd"
10
+}
11
+
12
+// Token sets the auth method to token, with the specified token url
13
+func Token(tokenURL string) func(*Config) {
14
+	return func(c *Config) {
15
+		c.auth = "token"
16
+		c.tokenURL = tokenURL
17
+	}
18
+}
19
+
20
+// URL sets the registry url
21
+func URL(registryURL string) func(*Config) {
22
+	return func(c *Config) {
23
+		c.registryURL = registryURL
24
+	}
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,239 @@
0
+package registry // import "github.com/docker/docker/internal/test/registry"
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"net/http"
6
+	"os"
7
+	"os/exec"
8
+	"path/filepath"
9
+	"time"
10
+
11
+	"github.com/gotestyourself/gotestyourself/assert"
12
+	"github.com/opencontainers/go-digest"
13
+)
14
+
15
+const (
16
+	// V2binary is the name of the registry v2 binary
17
+	V2binary = "registry-v2"
18
+	// V2binarySchema1 is the name of the registry that serve schema1
19
+	V2binarySchema1 = "registry-v2-schema1"
20
+	// DefaultURL is the default url that will be used by the registry (if not specified otherwise)
21
+	DefaultURL = "127.0.0.1:5000"
22
+)
23
+
24
+type testingT interface {
25
+	assert.TestingT
26
+	logT
27
+	Fatal(...interface{})
28
+	Fatalf(string, ...interface{})
29
+}
30
+
31
+type logT interface {
32
+	Logf(string, ...interface{})
33
+}
34
+
35
+// V2 represent a registry version 2
36
+type V2 struct {
37
+	cmd         *exec.Cmd
38
+	registryURL string
39
+	dir         string
40
+	auth        string
41
+	username    string
42
+	password    string
43
+	email       string
44
+}
45
+
46
+// Config contains the test registry configuration
47
+type Config struct {
48
+	schema1     bool
49
+	auth        string
50
+	tokenURL    string
51
+	registryURL string
52
+}
53
+
54
+// NewV2 creates a v2 registry server
55
+func NewV2(t testingT, ops ...func(*Config)) *V2 {
56
+	c := &Config{
57
+		registryURL: DefaultURL,
58
+	}
59
+	for _, op := range ops {
60
+		op(c)
61
+	}
62
+	tmp, err := ioutil.TempDir("", "registry-test-")
63
+	assert.NilError(t, err)
64
+	template := `version: 0.1
65
+loglevel: debug
66
+storage:
67
+    filesystem:
68
+        rootdirectory: %s
69
+http:
70
+    addr: %s
71
+%s`
72
+	var (
73
+		authTemplate string
74
+		username     string
75
+		password     string
76
+		email        string
77
+	)
78
+	switch c.auth {
79
+	case "htpasswd":
80
+		htpasswdPath := filepath.Join(tmp, "htpasswd")
81
+		// generated with: htpasswd -Bbn testuser testpassword
82
+		userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
83
+		username = "testuser"
84
+		password = "testpassword"
85
+		email = "test@test.org"
86
+		err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644))
87
+		assert.NilError(t, err)
88
+		authTemplate = fmt.Sprintf(`auth:
89
+    htpasswd:
90
+        realm: basic-realm
91
+        path: %s
92
+`, htpasswdPath)
93
+	case "token":
94
+		authTemplate = fmt.Sprintf(`auth:
95
+    token:
96
+        realm: %s
97
+        service: "registry"
98
+        issuer: "auth-registry"
99
+        rootcertbundle: "fixtures/registry/cert.pem"
100
+`, c.tokenURL)
101
+	}
102
+
103
+	confPath := filepath.Join(tmp, "config.yaml")
104
+	config, err := os.Create(confPath)
105
+	assert.NilError(t, err)
106
+	defer config.Close()
107
+
108
+	if _, err := fmt.Fprintf(config, template, tmp, c.registryURL, authTemplate); err != nil {
109
+		// FIXME(vdemeester) use a defer/clean func
110
+		os.RemoveAll(tmp)
111
+		t.Fatal(err)
112
+	}
113
+
114
+	binary := V2binary
115
+	if c.schema1 {
116
+		binary = V2binarySchema1
117
+	}
118
+	cmd := exec.Command(binary, confPath)
119
+	if err := cmd.Start(); err != nil {
120
+		// FIXME(vdemeester) use a defer/clean func
121
+		os.RemoveAll(tmp)
122
+		t.Fatal(err)
123
+	}
124
+	return &V2{
125
+		cmd:         cmd,
126
+		dir:         tmp,
127
+		auth:        c.auth,
128
+		username:    username,
129
+		password:    password,
130
+		email:       email,
131
+		registryURL: c.registryURL,
132
+	}
133
+}
134
+
135
+// WaitReady waits for the registry to be ready to serve requests (or fail after a while)
136
+func (r *V2) WaitReady(t testingT) {
137
+	var err error
138
+	for i := 0; i != 50; i++ {
139
+		if err = r.Ping(); err == nil {
140
+			return
141
+		}
142
+		time.Sleep(100 * time.Millisecond)
143
+	}
144
+	t.Fatalf("timeout waiting for test registry to become available: %v", err)
145
+}
146
+
147
+// Ping sends an http request to the current registry, and fail if it doesn't respond correctly
148
+func (r *V2) Ping() error {
149
+	// We always ping through HTTP for our test registry.
150
+	resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
151
+	if err != nil {
152
+		return err
153
+	}
154
+	resp.Body.Close()
155
+
156
+	fail := resp.StatusCode != http.StatusOK
157
+	if r.auth != "" {
158
+		// unauthorized is a _good_ status when pinging v2/ and it needs auth
159
+		fail = fail && resp.StatusCode != http.StatusUnauthorized
160
+	}
161
+	if fail {
162
+		return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
163
+	}
164
+	return nil
165
+}
166
+
167
+// Close kills the registry server
168
+func (r *V2) Close() {
169
+	r.cmd.Process.Kill()
170
+	r.cmd.Process.Wait()
171
+	os.RemoveAll(r.dir)
172
+}
173
+
174
+func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
175
+	// Split the digest into its algorithm and hex components.
176
+	dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex()
177
+
178
+	// The path to the target blob data looks something like:
179
+	//   baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
180
+	return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
181
+}
182
+
183
+// ReadBlobContents read the file corresponding to the specified digest
184
+func (r *V2) ReadBlobContents(t assert.TestingT, blobDigest digest.Digest) []byte {
185
+	// Load the target manifest blob.
186
+	manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest))
187
+	assert.NilError(t, err, "unable to read blob")
188
+	return manifestBlob
189
+}
190
+
191
+// WriteBlobContents write the file corresponding to the specified digest with the given content
192
+func (r *V2) WriteBlobContents(t assert.TestingT, blobDigest digest.Digest, data []byte) {
193
+	err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644))
194
+	assert.NilError(t, err, "unable to write malicious data blob")
195
+}
196
+
197
+// TempMoveBlobData moves the existing data file aside, so that we can replace it with a
198
+// malicious blob of data for example.
199
+func (r *V2) TempMoveBlobData(t testingT, blobDigest digest.Digest) (undo func()) {
200
+	tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
201
+	assert.NilError(t, err, "unable to get temporary blob file")
202
+	tempFile.Close()
203
+
204
+	blobFilename := r.getBlobFilename(blobDigest)
205
+
206
+	// Move the existing data file aside, so that we can replace it with a
207
+	// another blob of data.
208
+	if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
209
+		// FIXME(vdemeester) use a defer/clean func
210
+		os.Remove(tempFile.Name())
211
+		t.Fatalf("unable to move data blob: %s", err)
212
+	}
213
+
214
+	return func() {
215
+		os.Rename(tempFile.Name(), blobFilename)
216
+		os.Remove(tempFile.Name())
217
+	}
218
+}
219
+
220
+// Username returns the configured user name of the server
221
+func (r *V2) Username() string {
222
+	return r.username
223
+}
224
+
225
+// Password returns the configured password of the server
226
+func (r *V2) Password() string {
227
+	return r.password
228
+}
229
+
230
+// Email returns the configured email of the server
231
+func (r *V2) Email() string {
232
+	return r.email
233
+}
234
+
235
+// Path returns the path where the registry write data
236
+func (r *V2) Path() string {
237
+	return filepath.Join(r.dir, "docker", "registry", "v2")
238
+}
0 239
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package registry // import "github.com/docker/docker/internal/test/registry"
1
+
2
+import (
3
+	"net/http"
4
+	"net/http/httptest"
5
+	"regexp"
6
+	"strings"
7
+	"sync"
8
+)
9
+
10
+type handlerFunc func(w http.ResponseWriter, r *http.Request)
11
+
12
+// Mock represent a registry mock
13
+type Mock struct {
14
+	server   *httptest.Server
15
+	hostport string
16
+	handlers map[string]handlerFunc
17
+	mu       sync.Mutex
18
+}
19
+
20
+// RegisterHandler register the specified handler for the registry mock
21
+func (tr *Mock) RegisterHandler(path string, h handlerFunc) {
22
+	tr.mu.Lock()
23
+	defer tr.mu.Unlock()
24
+	tr.handlers[path] = h
25
+}
26
+
27
+// NewMock creates a registry mock
28
+func NewMock(t testingT) (*Mock, error) {
29
+	testReg := &Mock{handlers: make(map[string]handlerFunc)}
30
+
31
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32
+		url := r.URL.String()
33
+
34
+		var matched bool
35
+		var err error
36
+		for re, function := range testReg.handlers {
37
+			matched, err = regexp.MatchString(re, url)
38
+			if err != nil {
39
+				t.Fatal("Error with handler regexp")
40
+			}
41
+			if matched {
42
+				function(w, r)
43
+				break
44
+			}
45
+		}
46
+
47
+		if !matched {
48
+			t.Fatalf("Unable to match %s with regexp", url)
49
+		}
50
+	}))
51
+
52
+	testReg.server = ts
53
+	testReg.hostport = strings.Replace(ts.URL, "http://", "", 1)
54
+	return testReg, nil
55
+}
56
+
57
+// URL returns the url of the registry
58
+func (tr *Mock) URL() string {
59
+	return tr.hostport
60
+}
61
+
62
+// Close closes mock and releases resources
63
+func (tr *Mock) Close() {
64
+	tr.server.Close()
65
+}