123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package registry // import "github.com/docker/docker/testutil/registry"
- import (
- "fmt"
- "io"
- "net/http"
- "os"
- "os/exec"
- "path/filepath"
- "testing"
- "time"
- "github.com/opencontainers/go-digest"
- "gotest.tools/v3/assert"
- )
- const (
- // V2binary is the name of the registry v2 binary
- V2binary = "registry-v2"
- // V2binarySchema1 is the name of the registry that serve schema1
- V2binarySchema1 = "registry-v2-schema1"
- // DefaultURL is the default url that will be used by the registry (if not specified otherwise)
- DefaultURL = "127.0.0.1:5000"
- )
- // V2 represent a registry version 2
- type V2 struct {
- cmd *exec.Cmd
- registryURL string
- dir string
- auth string
- username string
- password string
- email string
- }
- // Config contains the test registry configuration
- type Config struct {
- schema1 bool
- auth string
- tokenURL string
- registryURL string
- stdout io.Writer
- stderr io.Writer
- }
- // NewV2 creates a v2 registry server
- func NewV2(t testing.TB, ops ...func(*Config)) *V2 {
- t.Helper()
- c := &Config{
- registryURL: DefaultURL,
- }
- for _, op := range ops {
- op(c)
- }
- tmp, err := os.MkdirTemp("", "registry-test-")
- assert.NilError(t, err)
- template := `version: 0.1
- loglevel: debug
- storage:
- filesystem:
- rootdirectory: %s
- http:
- addr: %s
- %s`
- var (
- authTemplate string
- username string
- password string
- email string
- )
- switch c.auth {
- case "htpasswd":
- htpasswdPath := filepath.Join(tmp, "htpasswd")
- // generated with: htpasswd -Bbn testuser testpassword
- // #nosec G101
- userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
- username = "testuser"
- password = "testpassword"
- email = "test@test.org"
- err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0o644))
- assert.NilError(t, err)
- authTemplate = fmt.Sprintf(`auth:
- htpasswd:
- realm: basic-realm
- path: %s
- `, htpasswdPath)
- case "token":
- authTemplate = fmt.Sprintf(`auth:
- token:
- realm: %s
- service: "registry"
- issuer: "auth-registry"
- rootcertbundle: "fixtures/registry/cert.pem"
- `, c.tokenURL)
- }
- confPath := filepath.Join(tmp, "config.yaml")
- config, err := os.Create(confPath)
- assert.NilError(t, err)
- defer config.Close()
- if _, err := fmt.Fprintf(config, template, tmp, c.registryURL, authTemplate); err != nil {
- // FIXME(vdemeester) use a defer/clean func
- os.RemoveAll(tmp)
- t.Fatal(err)
- }
- binary := V2binary
- args := []string{"serve", confPath}
- if c.schema1 {
- binary = V2binarySchema1
- args = []string{confPath}
- }
- cmd := exec.Command(binary, args...)
- cmd.Stdout = c.stdout
- cmd.Stderr = c.stderr
- if err := cmd.Start(); err != nil {
- // FIXME(vdemeester) use a defer/clean func
- os.RemoveAll(tmp)
- t.Fatal(err)
- }
- return &V2{
- cmd: cmd,
- dir: tmp,
- auth: c.auth,
- username: username,
- password: password,
- email: email,
- registryURL: c.registryURL,
- }
- }
- // WaitReady waits for the registry to be ready to serve requests (or fail after a while)
- func (r *V2) WaitReady(t testing.TB) {
- t.Helper()
- var err error
- for i := 0; i != 50; i++ {
- if err = r.Ping(); err == nil {
- return
- }
- time.Sleep(100 * time.Millisecond)
- }
- t.Fatalf("timeout waiting for test registry to become available: %v", err)
- }
- // Ping sends an http request to the current registry, and fail if it doesn't respond correctly
- func (r *V2) Ping() error {
- // We always ping through HTTP for our test registry.
- resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
- if err != nil {
- return err
- }
- resp.Body.Close()
- fail := resp.StatusCode != http.StatusOK
- if r.auth != "" {
- // unauthorized is a _good_ status when pinging v2/ and it needs auth
- fail = fail && resp.StatusCode != http.StatusUnauthorized
- }
- if fail {
- return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
- }
- return nil
- }
- // Close kills the registry server
- func (r *V2) Close() {
- r.cmd.Process.Kill()
- r.cmd.Process.Wait()
- os.RemoveAll(r.dir)
- }
- func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
- // Split the digest into its algorithm and hex components.
- dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Encoded()
- // The path to the target blob data looks something like:
- // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
- return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
- }
- // ReadBlobContents read the file corresponding to the specified digest
- func (r *V2) ReadBlobContents(t testing.TB, blobDigest digest.Digest) []byte {
- t.Helper()
- // Load the target manifest blob.
- manifestBlob, err := os.ReadFile(r.getBlobFilename(blobDigest))
- assert.NilError(t, err, "unable to read blob")
- return manifestBlob
- }
- // WriteBlobContents write the file corresponding to the specified digest with the given content
- func (r *V2) WriteBlobContents(t testing.TB, blobDigest digest.Digest, data []byte) {
- t.Helper()
- err := os.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0o644))
- assert.NilError(t, err, "unable to write malicious data blob")
- }
- // TempMoveBlobData moves the existing data file aside, so that we can replace it with a
- // malicious blob of data for example.
- func (r *V2) TempMoveBlobData(t testing.TB, blobDigest digest.Digest) (undo func()) {
- t.Helper()
- tempFile, err := os.CreateTemp("", "registry-temp-blob-")
- assert.NilError(t, err, "unable to get temporary blob file")
- tempFile.Close()
- blobFilename := r.getBlobFilename(blobDigest)
- // Move the existing data file aside, so that we can replace it with a
- // another blob of data.
- if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
- // FIXME(vdemeester) use a defer/clean func
- os.Remove(tempFile.Name())
- t.Fatalf("unable to move data blob: %s", err)
- }
- return func() {
- os.Rename(tempFile.Name(), blobFilename)
- os.Remove(tempFile.Name())
- }
- }
- // Username returns the configured user name of the server
- func (r *V2) Username() string {
- return r.username
- }
- // Password returns the configured password of the server
- func (r *V2) Password() string {
- return r.password
- }
- // Email returns the configured email of the server
- func (r *V2) Email() string {
- return r.email
- }
- // Path returns the path where the registry write data
- func (r *V2) Path() string {
- return filepath.Join(r.dir, "docker", "registry", "v2")
- }
|