github.com/rish1988/moby@v25.0.2+incompatible/testutil/registry/registry.go (about) 1 package registry // import "github.com/docker/docker/testutil/registry" 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "testing" 11 "time" 12 13 "github.com/opencontainers/go-digest" 14 "gotest.tools/v3/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 // V2 represent a registry version 2 27 type V2 struct { 28 cmd *exec.Cmd 29 registryURL string 30 dir string 31 auth string 32 username string 33 password string 34 email string 35 } 36 37 // Config contains the test registry configuration 38 type Config struct { 39 schema1 bool 40 auth string 41 tokenURL string 42 registryURL string 43 stdout io.Writer 44 stderr io.Writer 45 } 46 47 // NewV2 creates a v2 registry server 48 func NewV2(t testing.TB, ops ...func(*Config)) *V2 { 49 t.Helper() 50 c := &Config{ 51 registryURL: DefaultURL, 52 } 53 for _, op := range ops { 54 op(c) 55 } 56 tmp, err := os.MkdirTemp("", "registry-test-") 57 assert.NilError(t, err) 58 template := `version: 0.1 59 loglevel: debug 60 storage: 61 filesystem: 62 rootdirectory: %s 63 http: 64 addr: %s 65 %s` 66 var ( 67 authTemplate string 68 username string 69 password string 70 email string 71 ) 72 switch c.auth { 73 case "htpasswd": 74 htpasswdPath := filepath.Join(tmp, "htpasswd") 75 // generated with: htpasswd -Bbn testuser testpassword 76 // #nosec G101 77 userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m" 78 username = "testuser" 79 password = "testpassword" 80 email = "test@test.org" 81 err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0o644)) 82 assert.NilError(t, err) 83 authTemplate = fmt.Sprintf(`auth: 84 htpasswd: 85 realm: basic-realm 86 path: %s 87 `, htpasswdPath) 88 case "token": 89 authTemplate = fmt.Sprintf(`auth: 90 token: 91 realm: %s 92 service: "registry" 93 issuer: "auth-registry" 94 rootcertbundle: "fixtures/registry/cert.pem" 95 `, c.tokenURL) 96 } 97 98 confPath := filepath.Join(tmp, "config.yaml") 99 config, err := os.Create(confPath) 100 assert.NilError(t, err) 101 defer config.Close() 102 103 if _, err := fmt.Fprintf(config, template, tmp, c.registryURL, authTemplate); err != nil { 104 // FIXME(vdemeester) use a defer/clean func 105 os.RemoveAll(tmp) 106 t.Fatal(err) 107 } 108 109 binary := V2binary 110 args := []string{"serve", confPath} 111 if c.schema1 { 112 binary = V2binarySchema1 113 args = []string{confPath} 114 } 115 cmd := exec.Command(binary, args...) 116 cmd.Stdout = c.stdout 117 cmd.Stderr = c.stderr 118 if err := cmd.Start(); err != nil { 119 // FIXME(vdemeester) use a defer/clean func 120 os.RemoveAll(tmp) 121 t.Fatal(err) 122 } 123 return &V2{ 124 cmd: cmd, 125 dir: tmp, 126 auth: c.auth, 127 username: username, 128 password: password, 129 email: email, 130 registryURL: c.registryURL, 131 } 132 } 133 134 // WaitReady waits for the registry to be ready to serve requests (or fail after a while) 135 func (r *V2) WaitReady(t testing.TB) { 136 t.Helper() 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.Encoded() 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 testing.TB, blobDigest digest.Digest) []byte { 185 t.Helper() 186 // Load the target manifest blob. 187 manifestBlob, err := os.ReadFile(r.getBlobFilename(blobDigest)) 188 assert.NilError(t, err, "unable to read blob") 189 return manifestBlob 190 } 191 192 // WriteBlobContents write the file corresponding to the specified digest with the given content 193 func (r *V2) WriteBlobContents(t testing.TB, blobDigest digest.Digest, data []byte) { 194 t.Helper() 195 err := os.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0o644)) 196 assert.NilError(t, err, "unable to write malicious data blob") 197 } 198 199 // TempMoveBlobData moves the existing data file aside, so that we can replace it with a 200 // malicious blob of data for example. 201 func (r *V2) TempMoveBlobData(t testing.TB, blobDigest digest.Digest) (undo func()) { 202 t.Helper() 203 tempFile, err := os.CreateTemp("", "registry-temp-blob-") 204 assert.NilError(t, err, "unable to get temporary blob file") 205 tempFile.Close() 206 207 blobFilename := r.getBlobFilename(blobDigest) 208 209 // Move the existing data file aside, so that we can replace it with a 210 // another blob of data. 211 if err := os.Rename(blobFilename, tempFile.Name()); err != nil { 212 // FIXME(vdemeester) use a defer/clean func 213 os.Remove(tempFile.Name()) 214 t.Fatalf("unable to move data blob: %s", err) 215 } 216 217 return func() { 218 os.Rename(tempFile.Name(), blobFilename) 219 os.Remove(tempFile.Name()) 220 } 221 } 222 223 // Username returns the configured user name of the server 224 func (r *V2) Username() string { 225 return r.username 226 } 227 228 // Password returns the configured password of the server 229 func (r *V2) Password() string { 230 return r.password 231 } 232 233 // Email returns the configured email of the server 234 func (r *V2) Email() string { 235 return r.email 236 } 237 238 // Path returns the path where the registry write data 239 func (r *V2) Path() string { 240 return filepath.Join(r.dir, "docker", "registry", "v2") 241 }