2016-12-30 18:10:04 +00:00
|
|
|
package registry
|
2015-01-12 21:26:49 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2015-01-31 19:28:11 +00:00
|
|
|
"net/http"
|
2015-01-12 21:26:49 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2015-04-18 16:46:47 +00:00
|
|
|
|
2017-01-07 01:23:18 +00:00
|
|
|
"github.com/opencontainers/go-digest"
|
2015-01-12 21:26:49 +00:00
|
|
|
)
|
|
|
|
|
2015-12-18 23:06:23 +00:00
|
|
|
const (
|
|
|
|
v2binary = "registry-v2"
|
|
|
|
v2binarySchema1 = "registry-v2-schema1"
|
|
|
|
)
|
2015-01-12 21:26:49 +00:00
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
type testingT interface {
|
|
|
|
logT
|
|
|
|
Fatal(...interface{})
|
|
|
|
Fatalf(string, ...interface{})
|
2015-01-12 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewV2 creates a v2 registry server
|
|
|
|
func NewV2(schema1 bool, auth, tokenURL, registryURL string) (*V2, error) {
|
2016-01-23 18:45:01 +00:00
|
|
|
tmp, err := ioutil.TempDir("", "registry-test-")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-01-12 21:26:49 +00:00
|
|
|
template := `version: 0.1
|
|
|
|
loglevel: debug
|
|
|
|
storage:
|
|
|
|
filesystem:
|
|
|
|
rootdirectory: %s
|
|
|
|
http:
|
2016-01-23 18:45:01 +00:00
|
|
|
addr: %s
|
|
|
|
%s`
|
|
|
|
var (
|
2016-03-14 20:11:35 +00:00
|
|
|
authTemplate string
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
email string
|
2016-01-23 18:45:01 +00:00
|
|
|
)
|
2016-03-14 20:11:35 +00:00
|
|
|
switch auth {
|
|
|
|
case "htpasswd":
|
2016-01-23 18:45:01 +00:00
|
|
|
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"
|
|
|
|
if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-03-14 20:11:35 +00:00
|
|
|
authTemplate = fmt.Sprintf(`auth:
|
2016-01-23 18:45:01 +00:00
|
|
|
htpasswd:
|
|
|
|
realm: basic-realm
|
|
|
|
path: %s
|
|
|
|
`, htpasswdPath)
|
2016-03-14 20:11:35 +00:00
|
|
|
case "token":
|
|
|
|
authTemplate = fmt.Sprintf(`auth:
|
|
|
|
token:
|
|
|
|
realm: %s
|
|
|
|
service: "registry"
|
|
|
|
issuer: "auth-registry"
|
|
|
|
rootcertbundle: "fixtures/registry/cert.pem"
|
|
|
|
`, tokenURL)
|
2015-01-12 21:26:49 +00:00
|
|
|
}
|
2016-01-23 18:45:01 +00:00
|
|
|
|
2015-01-12 21:26:49 +00:00
|
|
|
confPath := filepath.Join(tmp, "config.yaml")
|
|
|
|
config, err := os.Create(confPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-06-25 03:57:21 +00:00
|
|
|
defer config.Close()
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
if _, err := fmt.Fprintf(config, template, tmp, registryURL, authTemplate); err != nil {
|
2015-01-12 21:26:49 +00:00
|
|
|
os.RemoveAll(tmp)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-12-18 23:06:23 +00:00
|
|
|
binary := v2binary
|
|
|
|
if schema1 {
|
|
|
|
binary = v2binarySchema1
|
|
|
|
}
|
|
|
|
cmd := exec.Command(binary, confPath)
|
2015-01-12 21:26:49 +00:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
os.RemoveAll(tmp)
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-12-30 18:10:04 +00:00
|
|
|
return &V2{
|
|
|
|
cmd: cmd,
|
|
|
|
dir: tmp,
|
|
|
|
auth: auth,
|
|
|
|
username: username,
|
|
|
|
password: password,
|
|
|
|
email: email,
|
|
|
|
registryURL: registryURL,
|
2015-01-12 21:26:49 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
// Ping sends an http request to the current registry, and fail if it doesn't respond correctly
|
|
|
|
func (r *V2) Ping() error {
|
2015-01-31 19:28:11 +00:00
|
|
|
// We always ping through HTTP for our test registry.
|
2016-12-30 18:10:04 +00:00
|
|
|
resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
|
2015-01-31 19:28:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-01-23 18:45:01 +00:00
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
fail := resp.StatusCode != http.StatusOK
|
2016-12-30 18:10:04 +00:00
|
|
|
if r.auth != "" {
|
2016-01-23 18:45:01 +00:00
|
|
|
// unauthorized is a _good_ status when pinging v2/ and it needs auth
|
|
|
|
fail = fail && resp.StatusCode != http.StatusUnauthorized
|
|
|
|
}
|
|
|
|
if fail {
|
2015-02-02 22:53:20 +00:00
|
|
|
return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
|
2015-01-31 19:28:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
// Close kills the registry server
|
|
|
|
func (r *V2) Close() {
|
|
|
|
r.cmd.Process.Kill()
|
2017-01-14 01:45:14 +00:00
|
|
|
r.cmd.Process.Wait()
|
2016-12-30 18:10:04 +00:00
|
|
|
os.RemoveAll(r.dir)
|
2015-01-12 21:26:49 +00:00
|
|
|
}
|
2015-08-01 06:27:19 +00:00
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
|
2016-07-21 10:03:37 +00:00
|
|
|
// Split the digest into its algorithm and hex components.
|
2015-08-01 06:27:19 +00:00
|
|
|
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"
|
2016-12-30 18:10:04 +00:00
|
|
|
return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
|
2015-08-01 06:27:19 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
// ReadBlobContents read the file corresponding to the specified digest
|
|
|
|
func (r *V2) ReadBlobContents(t testingT, blobDigest digest.Digest) []byte {
|
2015-08-01 06:27:19 +00:00
|
|
|
// Load the target manifest blob.
|
2016-12-30 18:10:04 +00:00
|
|
|
manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest))
|
2015-08-01 06:27:19 +00:00
|
|
|
if err != nil {
|
2016-12-30 18:10:04 +00:00
|
|
|
t.Fatalf("unable to read blob: %s", err)
|
2015-08-01 06:27:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return manifestBlob
|
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
// WriteBlobContents write the file corresponding to the specified digest with the given content
|
|
|
|
func (r *V2) WriteBlobContents(t testingT, blobDigest digest.Digest, data []byte) {
|
|
|
|
if err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
|
|
|
|
t.Fatalf("unable to write malicious data blob: %s", err)
|
2015-08-01 06:27:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
// 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()) {
|
2015-08-01 06:27:19 +00:00
|
|
|
tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
|
|
|
|
if err != nil {
|
2016-12-30 18:10:04 +00:00
|
|
|
t.Fatalf("unable to get temporary blob file: %s", err)
|
2015-08-01 06:27:19 +00:00
|
|
|
}
|
|
|
|
tempFile.Close()
|
|
|
|
|
2016-12-30 18:10:04 +00:00
|
|
|
blobFilename := r.getBlobFilename(blobDigest)
|
2015-08-01 06:27:19 +00:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
os.Remove(tempFile.Name())
|
2016-12-30 18:10:04 +00:00
|
|
|
t.Fatalf("unable to move data blob: %s", err)
|
2015-08-01 06:27:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return func() {
|
|
|
|
os.Rename(tempFile.Name(), blobFilename)
|
|
|
|
os.Remove(tempFile.Name())
|
|
|
|
}
|
|
|
|
}
|
2016-12-30 18:10:04 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path returns the path where the registry write data
|
|
|
|
func (r *V2) Path() string {
|
|
|
|
return filepath.Join(r.dir, "docker", "registry", "v2")
|
|
|
|
}
|