distribution/docs/registry_test.go
Richard Scothern ebaa771c3b Prevent push and pull to v1 registries by filtering the available endpoints.
Add a daemon flag to control this behaviour.  Add a warning message when pulling
an image from a v1 registry.  The default order of pull is slightly altered
with this changset.

Previously it was:
https v2, https v1, http v2, http v1

now it is:
https v2, http v2, https v1, http v1

Prevent login to v1 registries by explicitly setting the version before ping to
prevent fallback to v1.

Add unit tests for v2 only mode.  Create a mock server that can register
handlers for various endpoints.  Assert no v1 endpoints are hit with legacy
registries disabled for the following commands:  pull, push, build, run and
login.  Assert the opposite when legacy registries are not disabled.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
2015-09-25 15:59:42 -07:00

947 lines
27 KiB
Go

package registry
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"testing"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/cliconfig"
)
var (
token = []string{"fake-token"}
)
const (
imageID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d"
REPO = "foo42/bar"
)
func spawnTestRegistrySession(t *testing.T) *Session {
authConfig := &cliconfig.AuthConfig{}
endpoint, err := NewEndpoint(makeIndex("/v1/"), nil, APIVersionUnknown)
if err != nil {
t.Fatal(err)
}
var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log}
tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
client := HTTPClient(tr)
r, err := NewSession(client, authConfig, endpoint)
if err != nil {
t.Fatal(err)
}
// In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
// header while authenticating, in order to retrieve a token that can be later used to
// perform authenticated actions.
//
// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
//
// Because we know that the client's transport is an `*authTransport` we simply cast it,
// in order to set the internal cached token to the fake token, and thus send that fake token
// upon every subsequent requests.
r.client.Transport.(*authTransport).token = token
return r
}
func TestPingRegistryEndpoint(t *testing.T) {
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
ep, err := NewEndpoint(index, nil, APIVersionUnknown)
if err != nil {
t.Fatal(err)
}
regInfo, err := ep.Ping()
if err != nil {
t.Fatal(err)
}
assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
}
testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)")
testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
}
func TestEndpoint(t *testing.T) {
// Simple wrapper to fail test if err != nil
expandEndpoint := func(index *IndexInfo) *Endpoint {
endpoint, err := NewEndpoint(index, nil, APIVersionUnknown)
if err != nil {
t.Fatal(err)
}
return endpoint
}
assertInsecureIndex := func(index *IndexInfo) {
index.Secure = true
_, err := NewEndpoint(index, nil, APIVersionUnknown)
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
index.Secure = false
}
assertSecureIndex := func(index *IndexInfo) {
index.Secure = true
_, err := NewEndpoint(index, nil, APIVersionUnknown)
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
index.Secure = false
}
index := &IndexInfo{}
index.Name = makeURL("/v1/")
endpoint := expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
index.Name = makeURL("")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
httpURL := makeURL("")
index.Name = strings.SplitN(httpURL, "://", 2)[1]
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
index.Name = makeHTTPSURL("/v1/")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
index.Name = makeHTTPSURL("")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
httpsURL := makeHTTPSURL("")
index.Name = strings.SplitN(httpsURL, "://", 2)[1]
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
badEndpoints := []string{
"http://127.0.0.1/v1/",
"https://127.0.0.1/v1/",
"http://127.0.0.1",
"https://127.0.0.1",
"127.0.0.1",
}
for _, address := range badEndpoints {
index.Name = address
_, err := NewEndpoint(index, nil, APIVersionUnknown)
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
}
}
func TestGetRemoteHistory(t *testing.T) {
r := spawnTestRegistrySession(t)
hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
assertEqual(t, len(hist), 2, "Expected 2 images in history")
assertEqual(t, hist[0], imageID, "Expected "+imageID+"as first ancestry")
assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
"Unexpected second ancestry")
}
func TestLookupRemoteImage(t *testing.T) {
r := spawnTestRegistrySession(t)
err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
assertEqual(t, err, nil, "Expected error of remote lookup to nil")
if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
t.Fatal("Expected error of remote lookup to not nil")
}
}
func TestGetRemoteImageJSON(t *testing.T) {
r := spawnTestRegistrySession(t)
json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
assertEqual(t, size, int64(154), "Expected size 154")
if len(json) <= 0 {
t.Fatal("Expected non-empty json")
}
_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
if err == nil {
t.Fatal("Expected image not found error")
}
}
func TestGetRemoteImageLayer(t *testing.T) {
r := spawnTestRegistrySession(t)
data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
if err != nil {
t.Fatal(err)
}
if data == nil {
t.Fatal("Expected non-nil data result")
}
_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
if err == nil {
t.Fatal("Expected image not found error")
}
}
func TestGetRemoteTag(t *testing.T) {
r := spawnTestRegistrySession(t)
tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, REPO, "test")
if err != nil {
t.Fatal(err)
}
assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID)
_, err = r.GetRemoteTag([]string{makeURL("/v1/")}, "foo42/baz", "foo")
if err != ErrRepoNotFound {
t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo")
}
}
func TestGetRemoteTags(t *testing.T) {
r := spawnTestRegistrySession(t)
tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
if err != nil {
t.Fatal(err)
}
assertEqual(t, len(tags), 2, "Expected two tags")
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID)
_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
if err != ErrRepoNotFound {
t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo")
}
}
func TestGetRepositoryData(t *testing.T) {
r := spawnTestRegistrySession(t)
parsedURL, err := url.Parse(makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
host := "http://" + parsedURL.Host + "/v1/"
data, err := r.GetRepositoryData("foo42/bar")
if err != nil {
t.Fatal(err)
}
assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList")
assertEqual(t, len(data.Endpoints), 2,
fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints)))
assertEqual(t, data.Endpoints[0], host,
fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0]))
assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/",
fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1]))
}
func TestPushImageJSONRegistry(t *testing.T) {
r := spawnTestRegistrySession(t)
imgData := &ImgData{
ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
}
err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
}
func TestPushImageLayerRegistry(t *testing.T) {
r := spawnTestRegistrySession(t)
layer := strings.NewReader("")
_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
if err != nil {
t.Fatal(err)
}
}
func TestValidateRepositoryName(t *testing.T) {
validRepoNames := []string{
"docker/docker",
"library/debian",
"debian",
"docker.io/docker/docker",
"docker.io/library/debian",
"docker.io/debian",
"index.docker.io/docker/docker",
"index.docker.io/library/debian",
"index.docker.io/debian",
"127.0.0.1:5000/docker/docker",
"127.0.0.1:5000/library/debian",
"127.0.0.1:5000/debian",
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
}
invalidRepoNames := []string{
"https://github.com/docker/docker",
"docker/Docker",
"-docker",
"-docker/docker",
"-docker.io/docker/docker",
"docker///docker",
"docker.io/docker/Docker",
"docker.io/docker///docker",
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
}
for _, name := range invalidRepoNames {
err := ValidateRepositoryName(name)
assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
}
for _, name := range validRepoNames {
err := ValidateRepositoryName(name)
assertEqual(t, err, nil, "Expected valid repo name: "+name)
}
err := ValidateRepositoryName(invalidRepoNames[0])
assertEqual(t, err, ErrInvalidRepositoryName, "Expected ErrInvalidRepositoryName: "+invalidRepoNames[0])
}
func TestParseRepositoryInfo(t *testing.T) {
expectedRepoInfos := map[string]RepositoryInfo{
"fooo/bar": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "fooo/bar",
LocalName: "fooo/bar",
CanonicalName: "docker.io/fooo/bar",
Official: false,
},
"library/ubuntu": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "docker.io/library/ubuntu",
Official: true,
},
"nonlibrary/ubuntu": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "nonlibrary/ubuntu",
LocalName: "nonlibrary/ubuntu",
CanonicalName: "docker.io/nonlibrary/ubuntu",
Official: false,
},
"ubuntu": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "docker.io/library/ubuntu",
Official: true,
},
"other/library": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "other/library",
LocalName: "other/library",
CanonicalName: "docker.io/other/library",
Official: false,
},
"127.0.0.1:8000/private/moonbase": {
Index: &IndexInfo{
Name: "127.0.0.1:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "127.0.0.1:8000/private/moonbase",
CanonicalName: "127.0.0.1:8000/private/moonbase",
Official: false,
},
"127.0.0.1:8000/privatebase": {
Index: &IndexInfo{
Name: "127.0.0.1:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "127.0.0.1:8000/privatebase",
CanonicalName: "127.0.0.1:8000/privatebase",
Official: false,
},
"localhost:8000/private/moonbase": {
Index: &IndexInfo{
Name: "localhost:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost:8000/private/moonbase",
CanonicalName: "localhost:8000/private/moonbase",
Official: false,
},
"localhost:8000/privatebase": {
Index: &IndexInfo{
Name: "localhost:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost:8000/privatebase",
CanonicalName: "localhost:8000/privatebase",
Official: false,
},
"example.com/private/moonbase": {
Index: &IndexInfo{
Name: "example.com",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com/private/moonbase",
CanonicalName: "example.com/private/moonbase",
Official: false,
},
"example.com/privatebase": {
Index: &IndexInfo{
Name: "example.com",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com/privatebase",
CanonicalName: "example.com/privatebase",
Official: false,
},
"example.com:8000/private/moonbase": {
Index: &IndexInfo{
Name: "example.com:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com:8000/private/moonbase",
CanonicalName: "example.com:8000/private/moonbase",
Official: false,
},
"example.com:8000/privatebase": {
Index: &IndexInfo{
Name: "example.com:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com:8000/privatebase",
CanonicalName: "example.com:8000/privatebase",
Official: false,
},
"localhost/private/moonbase": {
Index: &IndexInfo{
Name: "localhost",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost/private/moonbase",
CanonicalName: "localhost/private/moonbase",
Official: false,
},
"localhost/privatebase": {
Index: &IndexInfo{
Name: "localhost",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost/privatebase",
CanonicalName: "localhost/privatebase",
Official: false,
},
IndexName + "/public/moonbase": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "docker.io/public/moonbase",
Official: false,
},
"index." + IndexName + "/public/moonbase": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "docker.io/public/moonbase",
Official: false,
},
"ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
Official: true,
},
IndexName + "/ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
Official: true,
},
"index." + IndexName + "/ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
Official: true,
},
}
for reposName, expectedRepoInfo := range expectedRepoInfos {
repoInfo, err := ParseRepositoryInfo(reposName)
if err != nil {
t.Error(err)
} else {
checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
checkEqual(t, repoInfo.RemoteName, expectedRepoInfo.RemoteName, reposName)
checkEqual(t, repoInfo.LocalName, expectedRepoInfo.LocalName, reposName)
checkEqual(t, repoInfo.CanonicalName, expectedRepoInfo.CanonicalName, reposName)
checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
}
}
}
func TestNewIndexInfo(t *testing.T) {
testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
for indexName, expectedIndexInfo := range expectedIndexInfos {
index, err := config.NewIndexInfo(indexName)
if err != nil {
t.Fatal(err)
} else {
checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
}
}
}
config := NewServiceConfig(nil)
noMirrors := []string{}
expectedIndexInfos := map[string]*IndexInfo{
IndexName: {
Name: IndexName,
Official: true,
Secure: true,
Mirrors: noMirrors,
},
"index." + IndexName: {
Name: IndexName,
Official: true,
Secure: true,
Mirrors: noMirrors,
},
"example.com": {
Name: "example.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"}
config = makeServiceConfig(publicMirrors, []string{"example.com"})
expectedIndexInfos = map[string]*IndexInfo{
IndexName: {
Name: IndexName,
Official: true,
Secure: true,
Mirrors: publicMirrors,
},
"index." + IndexName: {
Name: IndexName,
Official: true,
Secure: true,
Mirrors: publicMirrors,
},
"example.com": {
Name: "example.com",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"example.com:5000": {
Name: "example.com:5000",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
"127.0.0.1": {
Name: "127.0.0.1",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"other.com": {
Name: "other.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
config = makeServiceConfig(nil, []string{"42.42.0.0/16"})
expectedIndexInfos = map[string]*IndexInfo{
"example.com": {
Name: "example.com",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"example.com:5000": {
Name: "example.com:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1": {
Name: "127.0.0.1",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"other.com": {
Name: "other.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
}
func TestMirrorEndpointLookup(t *testing.T) {
containsMirror := func(endpoints []APIEndpoint) bool {
for _, pe := range endpoints {
if pe.URL == "my.mirror" {
return true
}
}
return false
}
s := Service{Config: makeServiceConfig([]string{"my.mirror"}, nil)}
imageName := IndexName + "/test/image"
pushAPIEndpoints, err := s.LookupPushEndpoints(imageName)
if err != nil {
t.Fatal(err)
}
if containsMirror(pushAPIEndpoints) {
t.Fatal("Push endpoint should not contain mirror")
}
pullAPIEndpoints, err := s.LookupPullEndpoints(imageName)
if err != nil {
t.Fatal(err)
}
if !containsMirror(pullAPIEndpoints) {
t.Fatal("Pull endpoint should contain mirror")
}
}
func TestPushRegistryTag(t *testing.T) {
r := spawnTestRegistrySession(t)
err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
}
func TestPushImageJSONIndex(t *testing.T) {
r := spawnTestRegistrySession(t)
imgData := []*ImgData{
{
ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
},
{
ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
},
}
repoData, err := r.PushImageJSONIndex("foo42/bar", imgData, false, nil)
if err != nil {
t.Fatal(err)
}
if repoData == nil {
t.Fatal("Expected RepositoryData object")
}
repoData, err = r.PushImageJSONIndex("foo42/bar", imgData, true, []string{r.indexEndpoint.String()})
if err != nil {
t.Fatal(err)
}
if repoData == nil {
t.Fatal("Expected RepositoryData object")
}
}
func TestSearchRepositories(t *testing.T) {
r := spawnTestRegistrySession(t)
results, err := r.SearchRepositories("fakequery")
if err != nil {
t.Fatal(err)
}
if results == nil {
t.Fatal("Expected non-nil SearchResults object")
}
assertEqual(t, results.NumResults, 1, "Expected 1 search results")
assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query")
assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
}
func TestValidRemoteName(t *testing.T) {
validRepositoryNames := []string{
// Sanity check.
"docker/docker",
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
// Allow embedded hyphens.
"docker-rules/docker",
//Username doc and image name docker being tested.
"doc/docker",
// single character names are now allowed.
"d/docker",
"jess/t",
}
for _, repositoryName := range validRepositoryNames {
if err := validateRemoteName(repositoryName); err != nil {
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
}
}
invalidRepositoryNames := []string{
// Disallow capital letters.
"docker/Docker",
// Only allow one slash.
"docker///docker",
// Disallow 64-character hexadecimal.
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
// Disallow leading and trailing hyphens in namespace.
"-docker/docker",
"docker-/docker",
"-docker-/docker",
// Don't allow underscores everywhere (as opposed to hyphens).
"____/____",
"_docker/_docker",
// Disallow consecutive hyphens.
"dock--er/docker",
// No repository.
"docker/",
//namespace too long
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
}
for _, repositoryName := range invalidRepositoryNames {
if err := validateRemoteName(repositoryName); err == nil {
t.Errorf("Repository name should be invalid: %v", repositoryName)
}
}
}
func TestTrustedLocation(t *testing.T) {
for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} {
req, _ := http.NewRequest("GET", url, nil)
if trustedLocation(req) == true {
t.Fatalf("'%s' shouldn't be detected as a trusted location", url)
}
}
for _, url := range []string{"https://docker.io", "https://test.docker.com:80"} {
req, _ := http.NewRequest("GET", url, nil)
if trustedLocation(req) == false {
t.Fatalf("'%s' should be detected as a trusted location", url)
}
}
}
func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
for _, urls := range [][]string{
{"http://docker.io", "https://docker.com"},
{"https://foo.docker.io:7777", "http://bar.docker.com"},
{"https://foo.docker.io", "https://example.com"},
} {
reqFrom, _ := http.NewRequest("GET", urls[0], nil)
reqFrom.Header.Add("Content-Type", "application/json")
reqFrom.Header.Add("Authorization", "super_secret")
reqTo, _ := http.NewRequest("GET", urls[1], nil)
addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
if len(reqTo.Header) != 1 {
t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
}
if reqTo.Header.Get("Content-Type") != "application/json" {
t.Fatal("'Content-Type' should be 'application/json'")
}
if reqTo.Header.Get("Authorization") != "" {
t.Fatal("'Authorization' should be empty")
}
}
for _, urls := range [][]string{
{"https://docker.io", "https://docker.com"},
{"https://foo.docker.io:7777", "https://bar.docker.com"},
} {
reqFrom, _ := http.NewRequest("GET", urls[0], nil)
reqFrom.Header.Add("Content-Type", "application/json")
reqFrom.Header.Add("Authorization", "super_secret")
reqTo, _ := http.NewRequest("GET", urls[1], nil)
addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
if len(reqTo.Header) != 2 {
t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
}
if reqTo.Header.Get("Content-Type") != "application/json" {
t.Fatal("'Content-Type' should be 'application/json'")
}
if reqTo.Header.Get("Authorization") != "super_secret" {
t.Fatal("'Authorization' should be 'super_secret'")
}
}
}
func TestIsSecureIndex(t *testing.T) {
tests := []struct {
addr string
insecureRegistries []string
expected bool
}{
{IndexName, nil, true},
{"example.com", []string{}, true},
{"example.com", []string{"example.com"}, false},
{"localhost", []string{"localhost:5000"}, false},
{"localhost:5000", []string{"localhost:5000"}, false},
{"localhost", []string{"example.com"}, false},
{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false},
{"localhost", nil, false},
{"localhost:5000", nil, false},
{"127.0.0.1", nil, false},
{"localhost", []string{"example.com"}, false},
{"127.0.0.1", []string{"example.com"}, false},
{"example.com", nil, true},
{"example.com", []string{"example.com"}, false},
{"127.0.0.1", []string{"example.com"}, false},
{"127.0.0.1:5000", []string{"example.com"}, false},
{"example.com:5000", []string{"42.42.0.0/16"}, false},
{"example.com", []string{"42.42.0.0/16"}, false},
{"example.com:5000", []string{"42.42.42.42/8"}, false},
{"127.0.0.1:5000", []string{"127.0.0.0/8"}, false},
{"42.42.42.42:5000", []string{"42.1.1.1/8"}, false},
{"invalid.domain.com", []string{"42.42.0.0/16"}, true},
{"invalid.domain.com", []string{"invalid.domain.com"}, false},
{"invalid.domain.com:5000", []string{"invalid.domain.com"}, true},
{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false},
}
for _, tt := range tests {
config := makeServiceConfig(nil, tt.insecureRegistries)
if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
}
}
}
type debugTransport struct {
http.RoundTripper
log func(...interface{})
}
func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
dump, err := httputil.DumpRequestOut(req, false)
if err != nil {
tr.log("could not dump request")
}
tr.log(string(dump))
resp, err := tr.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
dump, err = httputil.DumpResponse(resp, false)
if err != nil {
tr.log("could not dump response")
}
tr.log(string(dump))
return resp, err
}