- 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>
| ... | ... |
@@ -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") |
| ... | ... |
@@ -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 |
+} |