github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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(0644)) 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 if c.schema1 { 111 binary = V2binarySchema1 112 } 113 cmd := exec.Command(binary, confPath) 114 cmd.Stdout = c.stdout 115 cmd.Stderr = c.stderr 116 if err := cmd.Start(); err != nil { 117 // FIXME(vdemeester) use a defer/clean func 118 os.RemoveAll(tmp) 119 t.Fatal(err) 120 } 121 return &V2{ 122 cmd: cmd, 123 dir: tmp, 124 auth: c.auth, 125 username: username, 126 password: password, 127 email: email, 128 registryURL: c.registryURL, 129 } 130 } 131 132 // WaitReady waits for the registry to be ready to serve requests (or fail after a while) 133 func (r *V2) WaitReady(t testing.TB) { 134 t.Helper() 135 var err error 136 for i := 0; i != 50; i++ { 137 if err = r.Ping(); err == nil { 138 return 139 } 140 time.Sleep(100 * time.Millisecond) 141 } 142 t.Fatalf("timeout waiting for test registry to become available: %v", err) 143 } 144 145 // Ping sends an http request to the current registry, and fail if it doesn't respond correctly 146 func (r *V2) Ping() error { 147 // We always ping through HTTP for our test registry. 148 resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL)) 149 if err != nil { 150 return err 151 } 152 resp.Body.Close() 153 154 fail := resp.StatusCode != http.StatusOK 155 if r.auth != "" { 156 // unauthorized is a _good_ status when pinging v2/ and it needs auth 157 fail = fail && resp.StatusCode != http.StatusUnauthorized 158 } 159 if fail { 160 return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode) 161 } 162 return nil 163 } 164 165 // Close kills the registry server 166 func (r *V2) Close() { 167 r.cmd.Process.Kill() 168 r.cmd.Process.Wait() 169 os.RemoveAll(r.dir) 170 } 171 172 func (r *V2) getBlobFilename(blobDigest digest.Digest) string { 173 // Split the digest into its algorithm and hex components. 174 dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex() 175 176 // The path to the target blob data looks something like: 177 // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data" 178 return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex) 179 } 180 181 // ReadBlobContents read the file corresponding to the specified digest 182 func (r *V2) ReadBlobContents(t testing.TB, blobDigest digest.Digest) []byte { 183 t.Helper() 184 // Load the target manifest blob. 185 manifestBlob, err := os.ReadFile(r.getBlobFilename(blobDigest)) 186 assert.NilError(t, err, "unable to read blob") 187 return manifestBlob 188 } 189 190 // WriteBlobContents write the file corresponding to the specified digest with the given content 191 func (r *V2) WriteBlobContents(t testing.TB, blobDigest digest.Digest, data []byte) { 192 t.Helper() 193 err := os.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 testing.TB, blobDigest digest.Digest) (undo func()) { 200 t.Helper() 201 tempFile, err := os.CreateTemp("", "registry-temp-blob-") 202 assert.NilError(t, err, "unable to get temporary blob file") 203 tempFile.Close() 204 205 blobFilename := r.getBlobFilename(blobDigest) 206 207 // Move the existing data file aside, so that we can replace it with a 208 // another blob of data. 209 if err := os.Rename(blobFilename, tempFile.Name()); err != nil { 210 // FIXME(vdemeester) use a defer/clean func 211 os.Remove(tempFile.Name()) 212 t.Fatalf("unable to move data blob: %s", err) 213 } 214 215 return func() { 216 os.Rename(tempFile.Name(), blobFilename) 217 os.Remove(tempFile.Name()) 218 } 219 } 220 221 // Username returns the configured user name of the server 222 func (r *V2) Username() string { 223 return r.username 224 } 225 226 // Password returns the configured password of the server 227 func (r *V2) Password() string { 228 return r.password 229 } 230 231 // Email returns the configured email of the server 232 func (r *V2) Email() string { 233 return r.email 234 } 235 236 // Path returns the path where the registry write data 237 func (r *V2) Path() string { 238 return filepath.Join(r.dir, "docker", "registry", "v2") 239 }