Move and refactor integration-cli/registry to internal/test
- Move the code from `integration-cli` to `internal/test`. - Use `testingT` and `assert` when creating the registry. Signed-off-by: Vincent Demeester <vincent@sbr.pm> Upstream-commit: 66de2e6e3b6d927a3396743cd7c363aa9f7b776e Component: engine
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package registry
|
||||
|
||||
// Schema1 sets the registry to serve v1 api
|
||||
func Schema1(c *Config) {
|
||||
c.schema1 = true
|
||||
}
|
||||
|
||||
// Htpasswd sets the auth method with htpasswd
|
||||
func Htpasswd(c *Config) {
|
||||
c.auth = "htpasswd"
|
||||
}
|
||||
|
||||
// Token sets the auth method to token, with the specified token url
|
||||
func Token(tokenURL string) func(*Config) {
|
||||
return func(c *Config) {
|
||||
c.auth = "token"
|
||||
c.tokenURL = tokenURL
|
||||
}
|
||||
}
|
||||
|
||||
// URL sets the registry url
|
||||
func URL(registryURL string) func(*Config) {
|
||||
return func(c *Config) {
|
||||
c.registryURL = registryURL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package registry // import "github.com/docker/docker/internal/test/registry"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type testingT interface {
|
||||
assert.TestingT
|
||||
logT
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
}
|
||||
|
||||
type logT interface {
|
||||
Logf(string, ...interface{})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewV2 creates a v2 registry server
|
||||
func NewV2(t testingT, ops ...func(*Config)) *V2 {
|
||||
c := &Config{
|
||||
registryURL: DefaultURL,
|
||||
}
|
||||
for _, op := range ops {
|
||||
op(c)
|
||||
}
|
||||
tmp, err := ioutil.TempDir("", "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
|
||||
userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
|
||||
username = "testuser"
|
||||
password = "testpassword"
|
||||
email = "test@test.org"
|
||||
err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644))
|
||||
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
|
||||
if c.schema1 {
|
||||
binary = V2binarySchema1
|
||||
}
|
||||
cmd := exec.Command(binary, confPath)
|
||||
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 testingT) {
|
||||
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.Hex()
|
||||
|
||||
// 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 assert.TestingT, blobDigest digest.Digest) []byte {
|
||||
// Load the target manifest blob.
|
||||
manifestBlob, err := ioutil.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 assert.TestingT, blobDigest digest.Digest, data []byte) {
|
||||
err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644))
|
||||
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 testingT, blobDigest digest.Digest) (undo func()) {
|
||||
tempFile, err := ioutil.TempFile("", "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")
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package registry // import "github.com/docker/docker/internal/test/registry"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type handlerFunc func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Mock represent a registry mock
|
||||
type Mock struct {
|
||||
server *httptest.Server
|
||||
hostport string
|
||||
handlers map[string]handlerFunc
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// RegisterHandler register the specified handler for the registry mock
|
||||
func (tr *Mock) RegisterHandler(path string, h handlerFunc) {
|
||||
tr.mu.Lock()
|
||||
defer tr.mu.Unlock()
|
||||
tr.handlers[path] = h
|
||||
}
|
||||
|
||||
// NewMock creates a registry mock
|
||||
func NewMock(t testingT) (*Mock, error) {
|
||||
testReg := &Mock{handlers: make(map[string]handlerFunc)}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL.String()
|
||||
|
||||
var matched bool
|
||||
var err error
|
||||
for re, function := range testReg.handlers {
|
||||
matched, err = regexp.MatchString(re, url)
|
||||
if err != nil {
|
||||
t.Fatal("Error with handler regexp")
|
||||
}
|
||||
if matched {
|
||||
function(w, r)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
t.Fatalf("Unable to match %s with regexp", url)
|
||||
}
|
||||
}))
|
||||
|
||||
testReg.server = ts
|
||||
testReg.hostport = strings.Replace(ts.URL, "http://", "", 1)
|
||||
return testReg, nil
|
||||
}
|
||||
|
||||
// URL returns the url of the registry
|
||||
func (tr *Mock) URL() string {
|
||||
return tr.hostport
|
||||
}
|
||||
|
||||
// Close closes mock and releases resources
|
||||
func (tr *Mock) Close() {
|
||||
tr.server.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user