github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/internal/test/registry/registry.go (about)

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