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