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  }