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  }