github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/internal/test/registry/registry.go (about) 1 package registry // import "github.com/docker/docker/internal/test/registry" 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "time" 11 12 "github.com/docker/docker/internal/test" 13 "github.com/opencontainers/go-digest" 14 "gotest.tools/assert" 15 ) 16 17 const ( 18 // V2binary is the name of the registry v2 binary 19 V2binary = "registry-v2" 20 // V2binarySchema1 is the name of the registry that serve schema1 21 V2binarySchema1 = "registry-v2-schema1" 22 // DefaultURL is the default url that will be used by the registry (if not specified otherwise) 23 DefaultURL = "127.0.0.1:5000" 24 ) 25 26 type testingT interface { 27 assert.TestingT 28 logT 29 Fatal(...interface{}) 30 Fatalf(string, ...interface{}) 31 } 32 33 type logT interface { 34 Logf(string, ...interface{}) 35 } 36 37 // V2 represent a registry version 2 38 type V2 struct { 39 cmd *exec.Cmd 40 registryURL string 41 dir string 42 auth string 43 username string 44 password string 45 email string 46 } 47 48 // Config contains the test registry configuration 49 type Config struct { 50 schema1 bool 51 auth string 52 tokenURL string 53 registryURL string 54 } 55 56 // NewV2 creates a v2 registry server 57 func NewV2(t testingT, ops ...func(*Config)) *V2 { 58 if ht, ok := t.(test.HelperT); ok { 59 ht.Helper() 60 } 61 c := &Config{ 62 registryURL: DefaultURL, 63 } 64 for _, op := range ops { 65 op(c) 66 } 67 tmp, err := ioutil.TempDir("", "registry-test-") 68 assert.NilError(t, err) 69 template := `version: 0.1 70 loglevel: debug 71 storage: 72 filesystem: 73 rootdirectory: %s 74 http: 75 addr: %s 76 %s` 77 var ( 78 authTemplate string 79 username string 80 password string 81 email string 82 ) 83 switch c.auth { 84 case "htpasswd": 85 htpasswdPath := filepath.Join(tmp, "htpasswd") 86 // generated with: htpasswd -Bbn testuser testpassword 87 userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m" 88 username = "testuser" 89 password = "testpassword" 90 email = "test@test.org" 91 err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)) 92 assert.NilError(t, err) 93 authTemplate = fmt.Sprintf(`auth: 94 htpasswd: 95 realm: basic-realm 96 path: %s 97 `, htpasswdPath) 98 case "token": 99 authTemplate = fmt.Sprintf(`auth: 100 token: 101 realm: %s 102 service: "registry" 103 issuer: "auth-registry" 104 rootcertbundle: "fixtures/registry/cert.pem" 105 `, c.tokenURL) 106 } 107 108 confPath := filepath.Join(tmp, "config.yaml") 109 config, err := os.Create(confPath) 110 assert.NilError(t, err) 111 defer config.Close() 112 113 if _, err := fmt.Fprintf(config, template, tmp, c.registryURL, authTemplate); err != nil { 114 // FIXME(vdemeester) use a defer/clean func 115 os.RemoveAll(tmp) 116 t.Fatal(err) 117 } 118 119 binary := V2binary 120 if c.schema1 { 121 binary = V2binarySchema1 122 } 123 cmd := exec.Command(binary, confPath) 124 if err := cmd.Start(); err != nil { 125 // FIXME(vdemeester) use a defer/clean func 126 os.RemoveAll(tmp) 127 t.Fatal(err) 128 } 129 return &V2{ 130 cmd: cmd, 131 dir: tmp, 132 auth: c.auth, 133 username: username, 134 password: password, 135 email: email, 136 registryURL: c.registryURL, 137 } 138 } 139 140 // WaitReady waits for the registry to be ready to serve requests (or fail after a while) 141 func (r *V2) WaitReady(t testingT) { 142 if ht, ok := t.(test.HelperT); ok { 143 ht.Helper() 144 } 145 var err error 146 for i := 0; i != 50; i++ { 147 if err = r.Ping(); err == nil { 148 return 149 } 150 time.Sleep(100 * time.Millisecond) 151 } 152 t.Fatalf("timeout waiting for test registry to become available: %v", err) 153 } 154 155 // Ping sends an http request to the current registry, and fail if it doesn't respond correctly 156 func (r *V2) Ping() error { 157 // We always ping through HTTP for our test registry. 158 resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL)) 159 if err != nil { 160 return err 161 } 162 resp.Body.Close() 163 164 fail := resp.StatusCode != http.StatusOK 165 if r.auth != "" { 166 // unauthorized is a _good_ status when pinging v2/ and it needs auth 167 fail = fail && resp.StatusCode != http.StatusUnauthorized 168 } 169 if fail { 170 return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode) 171 } 172 return nil 173 } 174 175 // Close kills the registry server 176 func (r *V2) Close() { 177 r.cmd.Process.Kill() 178 r.cmd.Process.Wait() 179 os.RemoveAll(r.dir) 180 } 181 182 func (r *V2) getBlobFilename(blobDigest digest.Digest) string { 183 // Split the digest into its algorithm and hex components. 184 dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex() 185 186 // The path to the target blob data looks something like: 187 // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data" 188 return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex) 189 } 190 191 // ReadBlobContents read the file corresponding to the specified digest 192 func (r *V2) ReadBlobContents(t assert.TestingT, blobDigest digest.Digest) []byte { 193 if ht, ok := t.(test.HelperT); ok { 194 ht.Helper() 195 } 196 // Load the target manifest blob. 197 manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest)) 198 assert.NilError(t, err, "unable to read blob") 199 return manifestBlob 200 } 201 202 // WriteBlobContents write the file corresponding to the specified digest with the given content 203 func (r *V2) WriteBlobContents(t assert.TestingT, blobDigest digest.Digest, data []byte) { 204 if ht, ok := t.(test.HelperT); ok { 205 ht.Helper() 206 } 207 err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644)) 208 assert.NilError(t, err, "unable to write malicious data blob") 209 } 210 211 // TempMoveBlobData moves the existing data file aside, so that we can replace it with a 212 // malicious blob of data for example. 213 func (r *V2) TempMoveBlobData(t testingT, blobDigest digest.Digest) (undo func()) { 214 if ht, ok := t.(test.HelperT); ok { 215 ht.Helper() 216 } 217 tempFile, err := ioutil.TempFile("", "registry-temp-blob-") 218 assert.NilError(t, err, "unable to get temporary blob file") 219 tempFile.Close() 220 221 blobFilename := r.getBlobFilename(blobDigest) 222 223 // Move the existing data file aside, so that we can replace it with a 224 // another blob of data. 225 if err := os.Rename(blobFilename, tempFile.Name()); err != nil { 226 // FIXME(vdemeester) use a defer/clean func 227 os.Remove(tempFile.Name()) 228 t.Fatalf("unable to move data blob: %s", err) 229 } 230 231 return func() { 232 os.Rename(tempFile.Name(), blobFilename) 233 os.Remove(tempFile.Name()) 234 } 235 } 236 237 // Username returns the configured user name of the server 238 func (r *V2) Username() string { 239 return r.username 240 } 241 242 // Password returns the configured password of the server 243 func (r *V2) Password() string { 244 return r.password 245 } 246 247 // Email returns the configured email of the server 248 func (r *V2) Email() string { 249 return r.email 250 } 251 252 // Path returns the path where the registry write data 253 func (r *V2) Path() string { 254 return filepath.Join(r.dir, "docker", "registry", "v2") 255 }