Update daemon and docker core to use new content addressable storage

Add distribution package for managing pulls and pushes. This is based on
the old code in the graph package, with major changes to work with the
new image/layer model.

Add v1 migration code.

Update registry, api/*, and daemon packages to use the reference
package's types where applicable.

Update daemon package to use image/layer/tag stores instead of the graph
package

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi 2015-11-18 14:20:54 -08:00 committed by Aaron Lehmann
parent dde006ba6b
commit 7efcb7496c
8 changed files with 336 additions and 196 deletions

View file

@ -9,7 +9,7 @@ import (
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker/image"
"github.com/docker/docker/image/v1"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
)
@ -216,18 +216,15 @@ func ValidateIndexName(val string) (string, error) {
return val, nil
}
func validateRemoteName(remoteName string) error {
if !strings.Contains(remoteName, "/") {
func validateRemoteName(remoteName reference.Named) error {
remoteNameStr := remoteName.Name()
if !strings.Contains(remoteNameStr, "/") {
// the repository name must not be a valid image ID
if err := image.ValidateID(remoteName); err == nil {
if err := v1.ValidateID(remoteNameStr); err == nil {
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName)
}
}
_, err := reference.WithName(remoteName)
return err
return nil
}
func validateNoSchema(reposName string) error {
@ -239,27 +236,24 @@ func validateNoSchema(reposName string) error {
}
// ValidateRepositoryName validates a repository name
func ValidateRepositoryName(reposName string) error {
_, _, err := loadRepositoryName(reposName, true)
func ValidateRepositoryName(reposName reference.Named) error {
_, _, err := loadRepositoryName(reposName)
return err
}
// loadRepositoryName returns the repo name splitted into index name
// and remote repo name. It returns an error if the name is not valid.
func loadRepositoryName(reposName string, checkRemoteName bool) (string, string, error) {
if err := validateNoSchema(reposName); err != nil {
return "", "", err
func loadRepositoryName(reposName reference.Named) (string, reference.Named, error) {
if err := validateNoSchema(reposName.Name()); err != nil {
return "", nil, err
}
indexName, remoteName := splitReposName(reposName)
indexName, remoteName, err := splitReposName(reposName)
var err error
if indexName, err = ValidateIndexName(indexName); err != nil {
return "", "", err
return "", nil, err
}
if checkRemoteName {
if err = validateRemoteName(remoteName); err != nil {
return "", "", err
}
if err = validateRemoteName(remoteName); err != nil {
return "", nil, err
}
return indexName, remoteName, nil
}
@ -297,31 +291,36 @@ func (index *IndexInfo) GetAuthConfigKey() string {
}
// splitReposName breaks a reposName into an index name and remote name
func splitReposName(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
func splitReposName(reposName reference.Named) (indexName string, remoteName reference.Named, err error) {
var remoteNameStr string
indexName, remoteNameStr = reference.SplitHostname(reposName)
if indexName == "" || (!strings.Contains(indexName, ".") &&
!strings.Contains(indexName, ":") && indexName != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = IndexName
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
remoteName, err = reference.WithName(remoteNameStr)
}
return indexName, remoteName
return
}
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) (*RepositoryInfo, error) {
indexName, remoteName, err := loadRepositoryName(reposName, !bySearch)
if err != nil {
func (config *ServiceConfig) NewRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
if err := validateNoSchema(reposName.Name()); err != nil {
return nil, err
}
repoInfo := &RepositoryInfo{
RemoteName: remoteName,
repoInfo := &RepositoryInfo{}
var (
indexName string
err error
)
indexName, repoInfo.RemoteName, err = loadRepositoryName(reposName)
if err != nil {
return nil, err
}
repoInfo.Index, err = config.NewIndexInfo(indexName)
@ -330,46 +329,47 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool)
}
if repoInfo.Index.Official {
normalizedName := normalizeLibraryRepoName(repoInfo.RemoteName)
repoInfo.LocalName, err = normalizeLibraryRepoName(repoInfo.RemoteName)
if err != nil {
return nil, err
}
repoInfo.RemoteName = repoInfo.LocalName
repoInfo.LocalName = normalizedName
repoInfo.RemoteName = normalizedName
// If the normalized name does not contain a '/' (e.g. "foo")
// then it is an official repo.
if strings.IndexRune(normalizedName, '/') == -1 {
if strings.IndexRune(repoInfo.RemoteName.Name(), '/') == -1 {
repoInfo.Official = true
// Fix up remote name for official repos.
repoInfo.RemoteName = "library/" + normalizedName
repoInfo.RemoteName, err = reference.WithName("library/" + repoInfo.RemoteName.Name())
if err != nil {
return nil, err
}
}
repoInfo.CanonicalName = "docker.io/" + repoInfo.RemoteName
repoInfo.CanonicalName, err = reference.WithName("docker.io/" + repoInfo.RemoteName.Name())
if err != nil {
return nil, err
}
} else {
repoInfo.LocalName = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName)
repoInfo.LocalName, err = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName)
if err != nil {
return nil, err
}
repoInfo.CanonicalName = repoInfo.LocalName
}
return repoInfo, nil
}
// GetSearchTerm special-cases using local name for official index, and
// remote name for private indexes.
func (repoInfo *RepositoryInfo) GetSearchTerm() string {
if repoInfo.Index.Official {
return repoInfo.LocalName
}
return repoInfo.RemoteName
}
// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
// lacks registry configuration.
func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
return emptyServiceConfig.NewRepositoryInfo(reposName, false)
func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
return emptyServiceConfig.NewRepositoryInfo(reposName)
}
// ParseIndexInfo will use repository name to get back an indexInfo.
func ParseIndexInfo(reposName string) (*IndexInfo, error) {
indexName, _ := splitReposName(reposName)
// ParseSearchIndexInfo will use repository name to get back an indexInfo.
func ParseSearchIndexInfo(reposName string) (*IndexInfo, error) {
indexName, _ := splitReposSearchTerm(reposName)
indexInfo, err := emptyServiceConfig.NewIndexInfo(indexName)
if err != nil {
@ -378,12 +378,12 @@ func ParseIndexInfo(reposName string) (*IndexInfo, error) {
return indexInfo, nil
}
// NormalizeLocalName transforms a repository name into a normalize LocalName
// NormalizeLocalName transforms a repository name into a normalized LocalName
// Passes through the name without transformation on error (image id, etc)
// It does not use the repository info because we don't want to load
// the repository index and do request over the network.
func NormalizeLocalName(name string) string {
indexName, remoteName, err := loadRepositoryName(name, true)
func NormalizeLocalName(name reference.Named) reference.Named {
indexName, remoteName, err := loadRepositoryName(name)
if err != nil {
return name
}
@ -395,23 +395,52 @@ func NormalizeLocalName(name string) string {
}
if officialIndex {
return normalizeLibraryRepoName(remoteName)
localName, err := normalizeLibraryRepoName(remoteName)
if err != nil {
return name
}
return localName
}
return localNameFromRemote(indexName, remoteName)
localName, err := localNameFromRemote(indexName, remoteName)
if err != nil {
return name
}
return localName
}
// normalizeLibraryRepoName removes the library prefix from
// the repository name for official repos.
func normalizeLibraryRepoName(name string) string {
if strings.HasPrefix(name, "library/") {
func normalizeLibraryRepoName(name reference.Named) (reference.Named, error) {
if strings.HasPrefix(name.Name(), "library/") {
// If pull "library/foo", it's stored locally under "foo"
name = strings.SplitN(name, "/", 2)[1]
return reference.WithName(strings.SplitN(name.Name(), "/", 2)[1])
}
return name
return name, nil
}
// localNameFromRemote combines the index name and the repo remote name
// to generate a repo local name.
func localNameFromRemote(indexName, remoteName string) string {
return indexName + "/" + remoteName
func localNameFromRemote(indexName string, remoteName reference.Named) (reference.Named, error) {
return reference.WithName(indexName + "/" + remoteName.Name())
}
// NormalizeLocalReference transforms a reference to use a normalized LocalName
// for the name poriton. Passes through the reference without transformation on
// error.
func NormalizeLocalReference(ref reference.Named) reference.Named {
localName := NormalizeLocalName(ref)
if tagged, isTagged := ref.(reference.Tagged); isTagged {
newRef, err := reference.WithTag(localName, tagged.Tag())
if err != nil {
return ref
}
return newRef
} else if digested, isDigested := ref.(reference.Digested); isDigested {
newRef, err := reference.WithDigest(localName, digested.Digest())
if err != nil {
return ref
}
return newRef
}
return localName
}

View file

@ -15,6 +15,7 @@ import (
"testing"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/opts"
"github.com/gorilla/mux"
@ -349,15 +350,19 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
if !requiresAuth(w, r) {
return
}
repositoryName := mux.Vars(r)["repository"]
repositoryName, err := reference.WithName(mux.Vars(r)["repository"])
if err != nil {
apiError(w, "Could not parse repository", 400)
return
}
repositoryName = NormalizeLocalName(repositoryName)
tags, exists := testRepositories[repositoryName]
tags, exists := testRepositories[repositoryName.String()]
if !exists {
apiError(w, "Repository not found", 404)
return
}
if r.Method == "DELETE" {
delete(testRepositories, repositoryName)
delete(testRepositories, repositoryName.String())
writeResponse(w, true, 200)
return
}
@ -369,10 +374,14 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
return
}
vars := mux.Vars(r)
repositoryName := vars["repository"]
repositoryName, err := reference.WithName(vars["repository"])
if err != nil {
apiError(w, "Could not parse repository", 400)
return
}
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"]
tags, exists := testRepositories[repositoryName]
tags, exists := testRepositories[repositoryName.String()]
if !exists {
apiError(w, "Repository not found", 404)
return
@ -390,13 +399,17 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
return
}
vars := mux.Vars(r)
repositoryName := vars["repository"]
repositoryName, err := reference.WithName(vars["repository"])
if err != nil {
apiError(w, "Could not parse repository", 400)
return
}
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"]
tags, exists := testRepositories[repositoryName]
tags, exists := testRepositories[repositoryName.String()]
if !exists {
tags := make(map[string]string)
testRepositories[repositoryName] = tags
tags = make(map[string]string)
testRepositories[repositoryName.String()] = tags
}
tagValue := ""
readJSON(r, tagValue)

View file

@ -8,6 +8,7 @@ import (
"strings"
"testing"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/cliconfig"
)
@ -214,13 +215,21 @@ func TestGetRemoteImageLayer(t *testing.T) {
func TestGetRemoteTag(t *testing.T) {
r := spawnTestRegistrySession(t)
tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, REPO, "test")
repoRef, err := reference.ParseNamed(REPO)
if err != nil {
t.Fatal(err)
}
tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "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")
bazRef, err := reference.ParseNamed("foo42/baz")
if err != nil {
t.Fatal(err)
}
_, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo")
if err != ErrRepoNotFound {
t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo")
}
@ -228,7 +237,11 @@ func TestGetRemoteTag(t *testing.T) {
func TestGetRemoteTags(t *testing.T) {
r := spawnTestRegistrySession(t)
tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
repoRef, err := reference.ParseNamed(REPO)
if err != nil {
t.Fatal(err)
}
tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef)
if err != nil {
t.Fatal(err)
}
@ -236,7 +249,11 @@ func TestGetRemoteTags(t *testing.T) {
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")
bazRef, err := reference.ParseNamed("foo42/baz")
if err != nil {
t.Fatal(err)
}
_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef)
if err != ErrRepoNotFound {
t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo")
}
@ -249,7 +266,11 @@ func TestGetRepositoryData(t *testing.T) {
t.Fatal(err)
}
host := "http://" + parsedURL.Host + "/v1/"
data, err := r.GetRepositoryData("foo42/bar")
repoRef, err := reference.ParseNamed(REPO)
if err != nil {
t.Fatal(err)
}
data, err := r.GetRepositoryData(repoRef)
if err != nil {
t.Fatal(err)
}
@ -315,29 +336,41 @@ func TestValidateRepositoryName(t *testing.T) {
}
for _, name := range invalidRepoNames {
err := ValidateRepositoryName(name)
assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
named, err := reference.WithName(name)
if err == nil {
err := ValidateRepositoryName(named)
assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
}
}
for _, name := range validRepoNames {
err := ValidateRepositoryName(name)
named, err := reference.WithName(name)
if err != nil {
t.Fatalf("could not parse valid name: %s", name)
}
err = ValidateRepositoryName(named)
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) {
withName := func(name string) reference.Named {
named, err := reference.WithName(name)
if err != nil {
t.Fatalf("could not parse reference %s", name)
}
return named
}
expectedRepoInfos := map[string]RepositoryInfo{
"fooo/bar": {
Index: &IndexInfo{
Name: IndexName,
Official: true,
},
RemoteName: "fooo/bar",
LocalName: "fooo/bar",
CanonicalName: "docker.io/fooo/bar",
RemoteName: withName("fooo/bar"),
LocalName: withName("fooo/bar"),
CanonicalName: withName("docker.io/fooo/bar"),
Official: false,
},
"library/ubuntu": {
@ -345,9 +378,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "docker.io/library/ubuntu",
RemoteName: withName("library/ubuntu"),
LocalName: withName("ubuntu"),
CanonicalName: withName("docker.io/library/ubuntu"),
Official: true,
},
"nonlibrary/ubuntu": {
@ -355,9 +388,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "nonlibrary/ubuntu",
LocalName: "nonlibrary/ubuntu",
CanonicalName: "docker.io/nonlibrary/ubuntu",
RemoteName: withName("nonlibrary/ubuntu"),
LocalName: withName("nonlibrary/ubuntu"),
CanonicalName: withName("docker.io/nonlibrary/ubuntu"),
Official: false,
},
"ubuntu": {
@ -365,9 +398,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "docker.io/library/ubuntu",
RemoteName: withName("library/ubuntu"),
LocalName: withName("ubuntu"),
CanonicalName: withName("docker.io/library/ubuntu"),
Official: true,
},
"other/library": {
@ -375,9 +408,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "other/library",
LocalName: "other/library",
CanonicalName: "docker.io/other/library",
RemoteName: withName("other/library"),
LocalName: withName("other/library"),
CanonicalName: withName("docker.io/other/library"),
Official: false,
},
"127.0.0.1:8000/private/moonbase": {
@ -385,9 +418,9 @@ func TestParseRepositoryInfo(t *testing.T) {
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",
RemoteName: withName("private/moonbase"),
LocalName: withName("127.0.0.1:8000/private/moonbase"),
CanonicalName: withName("127.0.0.1:8000/private/moonbase"),
Official: false,
},
"127.0.0.1:8000/privatebase": {
@ -395,9 +428,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "127.0.0.1:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "127.0.0.1:8000/privatebase",
CanonicalName: "127.0.0.1:8000/privatebase",
RemoteName: withName("privatebase"),
LocalName: withName("127.0.0.1:8000/privatebase"),
CanonicalName: withName("127.0.0.1:8000/privatebase"),
Official: false,
},
"localhost:8000/private/moonbase": {
@ -405,9 +438,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "localhost:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost:8000/private/moonbase",
CanonicalName: "localhost:8000/private/moonbase",
RemoteName: withName("private/moonbase"),
LocalName: withName("localhost:8000/private/moonbase"),
CanonicalName: withName("localhost:8000/private/moonbase"),
Official: false,
},
"localhost:8000/privatebase": {
@ -415,9 +448,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "localhost:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost:8000/privatebase",
CanonicalName: "localhost:8000/privatebase",
RemoteName: withName("privatebase"),
LocalName: withName("localhost:8000/privatebase"),
CanonicalName: withName("localhost:8000/privatebase"),
Official: false,
},
"example.com/private/moonbase": {
@ -425,9 +458,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "example.com",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com/private/moonbase",
CanonicalName: "example.com/private/moonbase",
RemoteName: withName("private/moonbase"),
LocalName: withName("example.com/private/moonbase"),
CanonicalName: withName("example.com/private/moonbase"),
Official: false,
},
"example.com/privatebase": {
@ -435,9 +468,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "example.com",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com/privatebase",
CanonicalName: "example.com/privatebase",
RemoteName: withName("privatebase"),
LocalName: withName("example.com/privatebase"),
CanonicalName: withName("example.com/privatebase"),
Official: false,
},
"example.com:8000/private/moonbase": {
@ -445,9 +478,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "example.com:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com:8000/private/moonbase",
CanonicalName: "example.com:8000/private/moonbase",
RemoteName: withName("private/moonbase"),
LocalName: withName("example.com:8000/private/moonbase"),
CanonicalName: withName("example.com:8000/private/moonbase"),
Official: false,
},
"example.com:8000/privatebase": {
@ -455,9 +488,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "example.com:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com:8000/privatebase",
CanonicalName: "example.com:8000/privatebase",
RemoteName: withName("privatebase"),
LocalName: withName("example.com:8000/privatebase"),
CanonicalName: withName("example.com:8000/privatebase"),
Official: false,
},
"localhost/private/moonbase": {
@ -465,9 +498,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "localhost",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost/private/moonbase",
CanonicalName: "localhost/private/moonbase",
RemoteName: withName("private/moonbase"),
LocalName: withName("localhost/private/moonbase"),
CanonicalName: withName("localhost/private/moonbase"),
Official: false,
},
"localhost/privatebase": {
@ -475,9 +508,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: "localhost",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost/privatebase",
CanonicalName: "localhost/privatebase",
RemoteName: withName("privatebase"),
LocalName: withName("localhost/privatebase"),
CanonicalName: withName("localhost/privatebase"),
Official: false,
},
IndexName + "/public/moonbase": {
@ -485,9 +518,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "docker.io/public/moonbase",
RemoteName: withName("public/moonbase"),
LocalName: withName("public/moonbase"),
CanonicalName: withName("docker.io/public/moonbase"),
Official: false,
},
"index." + IndexName + "/public/moonbase": {
@ -495,9 +528,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "docker.io/public/moonbase",
RemoteName: withName("public/moonbase"),
LocalName: withName("public/moonbase"),
CanonicalName: withName("docker.io/public/moonbase"),
Official: false,
},
"ubuntu-12.04-base": {
@ -505,9 +538,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
RemoteName: withName("library/ubuntu-12.04-base"),
LocalName: withName("ubuntu-12.04-base"),
CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
Official: true,
},
IndexName + "/ubuntu-12.04-base": {
@ -515,9 +548,9 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
RemoteName: withName("library/ubuntu-12.04-base"),
LocalName: withName("ubuntu-12.04-base"),
CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
Official: true,
},
"index." + IndexName + "/ubuntu-12.04-base": {
@ -525,22 +558,27 @@ func TestParseRepositoryInfo(t *testing.T) {
Name: IndexName,
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "docker.io/library/ubuntu-12.04-base",
RemoteName: withName("library/ubuntu-12.04-base"),
LocalName: withName("ubuntu-12.04-base"),
CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
Official: true,
},
}
for reposName, expectedRepoInfo := range expectedRepoInfos {
repoInfo, err := ParseRepositoryInfo(reposName)
named, err := reference.WithName(reposName)
if err != nil {
t.Error(err)
}
repoInfo, err := ParseRepositoryInfo(named)
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.RemoteName.String(), expectedRepoInfo.RemoteName.String(), reposName)
checkEqual(t, repoInfo.LocalName.String(), expectedRepoInfo.LocalName.String(), reposName)
checkEqual(t, repoInfo.CanonicalName.String(), expectedRepoInfo.CanonicalName.String(), reposName)
checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
}
@ -687,8 +725,11 @@ func TestMirrorEndpointLookup(t *testing.T) {
return false
}
s := Service{Config: makeServiceConfig([]string{"my.mirror"}, nil)}
imageName := IndexName + "/test/image"
imageName, err := reference.WithName(IndexName + "/test/image")
if err != nil {
t.Error(err)
}
pushAPIEndpoints, err := s.LookupPushEndpoints(imageName)
if err != nil {
t.Fatal(err)
@ -708,7 +749,11 @@ func TestMirrorEndpointLookup(t *testing.T) {
func TestPushRegistryTag(t *testing.T) {
r := spawnTestRegistrySession(t)
err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
repoRef, err := reference.ParseNamed(REPO)
if err != nil {
t.Fatal(err)
}
err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@ -726,14 +771,18 @@ func TestPushImageJSONIndex(t *testing.T) {
Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
},
}
repoData, err := r.PushImageJSONIndex("foo42/bar", imgData, false, nil)
repoRef, err := reference.ParseNamed(REPO)
if err != nil {
t.Fatal(err)
}
repoData, err := r.PushImageJSONIndex(repoRef, 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()})
repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()})
if err != nil {
t.Fatal(err)
}
@ -781,7 +830,11 @@ func TestValidRemoteName(t *testing.T) {
"dock__er/docker",
}
for _, repositoryName := range validRepositoryNames {
if err := validateRemoteName(repositoryName); err != nil {
repositoryRef, err := reference.WithName(repositoryName)
if err != nil {
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
}
if err := validateRemoteName(repositoryRef); err != nil {
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
}
}
@ -818,7 +871,11 @@ func TestValidRemoteName(t *testing.T) {
"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 {
repositoryRef, err := reference.ParseNamed(repositoryName)
if err != nil {
continue
}
if err := validateRemoteName(repositoryRef); err == nil {
t.Errorf("Repository name should be invalid: %v", repositoryName)
}
}

View file

@ -4,7 +4,9 @@ import (
"crypto/tls"
"net/http"
"net/url"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/docker/cliconfig"
)
@ -51,17 +53,39 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
return Login(authConfig, endpoint)
}
// splitReposSearchTerm breaks a search term into an index name and remote name
func splitReposSearchTerm(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = IndexName
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
}
return indexName, remoteName
}
// Search queries the public registry for images matching the specified
// search terms, and returns the results.
func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) {
if err := validateNoSchema(term); err != nil {
return nil, err
}
repoInfo, err := s.ResolveRepositoryBySearch(term)
indexName, remoteName := splitReposSearchTerm(term)
index, err := s.Config.NewIndexInfo(indexName)
if err != nil {
return nil, err
}
// *TODO: Search multiple indexes.
endpoint, err := NewEndpoint(repoInfo.Index, http.Header(headers), APIVersionUnknown)
endpoint, err := NewEndpoint(index, http.Header(headers), APIVersionUnknown)
if err != nil {
return nil, err
}
@ -70,19 +94,23 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers
if err != nil {
return nil, err
}
return r.SearchRepositories(repoInfo.GetSearchTerm())
if index.Official {
localName := remoteName
if strings.HasPrefix(localName, "library/") {
// If pull "library/foo", it's stored locally under "foo"
localName = strings.SplitN(localName, "/", 2)[1]
}
return r.SearchRepositories(localName)
}
return r.SearchRepositories(remoteName)
}
// ResolveRepository splits a repository name into its components
// and configuration of the associated registry.
func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) {
return s.Config.NewRepositoryInfo(name, false)
}
// ResolveRepositoryBySearch splits a repository name into its components
// and configuration of the associated registry.
func (s *Service) ResolveRepositoryBySearch(name string) (*RepositoryInfo, error) {
return s.Config.NewRepositoryInfo(name, true)
func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
return s.Config.NewRepositoryInfo(name)
}
// ResolveIndex takes indexName and returns index info
@ -123,14 +151,14 @@ func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) {
// LookupPullEndpoints creates an list of endpoints to try to pull from, in order of preference.
// It gives preference to v2 endpoints over v1, mirrors over the actual
// registry, and HTTPS over plain HTTP.
func (s *Service) LookupPullEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
func (s *Service) LookupPullEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
return s.lookupEndpoints(repoName)
}
// LookupPushEndpoints creates an list of endpoints to try to push to, in order of preference.
// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
// Mirrors are not included.
func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
func (s *Service) LookupPushEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
allEndpoints, err := s.lookupEndpoints(repoName)
if err == nil {
for _, endpoint := range allEndpoints {
@ -142,7 +170,7 @@ func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint,
return endpoints, err
}
func (s *Service) lookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
func (s *Service) lookupEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
endpoints, err = s.lookupV2Endpoints(repoName)
if err != nil {
return nil, err

View file

@ -4,13 +4,15 @@ import (
"fmt"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker/pkg/tlsconfig"
)
func (s *Service) lookupV1Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
var cfg = tlsconfig.ServerDefault
tlsConfig := &cfg
if strings.HasPrefix(repoName, DefaultNamespace+"/") {
nameString := repoName.Name()
if strings.HasPrefix(nameString, DefaultNamespace+"/") {
endpoints = append(endpoints, APIEndpoint{
URL: DefaultV1Registry,
Version: APIVersion1,
@ -21,11 +23,11 @@ func (s *Service) lookupV1Endpoints(repoName string) (endpoints []APIEndpoint, e
return endpoints, nil
}
slashIndex := strings.IndexRune(repoName, '/')
slashIndex := strings.IndexRune(nameString, '/')
if slashIndex <= 0 {
return nil, fmt.Errorf("invalid repo name: missing '/': %s", repoName)
return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString)
}
hostname := repoName[:slashIndex]
hostname := nameString[:slashIndex]
tlsConfig, err = s.TLSConfig(hostname)
if err != nil {

View file

@ -4,14 +4,16 @@ import (
"fmt"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/docker/pkg/tlsconfig"
)
func (s *Service) lookupV2Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
var cfg = tlsconfig.ServerDefault
tlsConfig := &cfg
if strings.HasPrefix(repoName, DefaultNamespace+"/") {
nameString := repoName.Name()
if strings.HasPrefix(nameString, DefaultNamespace+"/") {
// v2 mirrors
for _, mirror := range s.Config.Mirrors {
mirrorTLSConfig, err := s.tlsConfigForMirror(mirror)
@ -39,11 +41,11 @@ func (s *Service) lookupV2Endpoints(repoName string) (endpoints []APIEndpoint, e
return endpoints, nil
}
slashIndex := strings.IndexRune(repoName, '/')
slashIndex := strings.IndexRune(nameString, '/')
if slashIndex <= 0 {
return nil, fmt.Errorf("invalid repo name: missing '/': %s", repoName)
return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString)
}
hostname := repoName[:slashIndex]
hostname := nameString[:slashIndex]
tlsConfig, err = s.TLSConfig(hostname)
if err != nil {

View file

@ -20,6 +20,7 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/reference"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
@ -320,7 +321,9 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io
// repository. It queries each of the registries supplied in the registries
// argument, and returns data from the first one that answers the query
// successfully.
func (r *Session) GetRemoteTag(registries []string, repository string, askedTag string) (string, error) {
func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) {
repository := repositoryRef.Name()
if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on
// the "library" namespace
@ -356,7 +359,9 @@ func (r *Session) GetRemoteTag(registries []string, repository string, askedTag
// of the registries supplied in the registries argument, and returns data from
// the first one that answers the query successfully. It returns a map with
// tag names as the keys and image IDs as the values.
func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) {
repository := repositoryRef.Name()
if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on
// the "library" namespace
@ -408,8 +413,8 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
}
// GetRepositoryData returns lists of images and endpoints for the repository
func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote)
func (r *Session) GetRepositoryData(remote reference.Named) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote.Name())
logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
@ -443,7 +448,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote.Name(), errBody), res)
}
var endpoints []string
@ -595,10 +600,10 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
// PushRegistryTag pushes a tag on the registry.
// Remote has the format '<user>/<repo>
func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
path := fmt.Sprintf("repositories/%s/tags/%s", remote.Name(), tag)
req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
if err != nil {
@ -612,13 +617,13 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error
}
res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 201 {
return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.Name()), res)
}
return nil
}
// PushImageJSONIndex uploads an image list to the repository
func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
cleanImgList := []*ImgData{}
if validate {
for _, elem := range imgList {
@ -638,7 +643,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if validate {
suffix = "images"
}
u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote, suffix)
u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote.Name(), suffix)
logrus.Debugf("[registry] PUT %s", u)
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
headers := map[string][]string{
@ -676,7 +681,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote.Name(), errBody), res)
}
tokens = res.Header["X-Docker-Token"]
logrus.Debugf("Auth token: %v", tokens)
@ -694,7 +699,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if err != nil {
logrus.Debugf("Error reading response body: %s", err)
}
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res)
return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote.Name(), errBody), res)
}
}

View file

@ -1,5 +1,9 @@
package registry
import (
"github.com/docker/distribution/reference"
)
// SearchResult describes a search result returned from a registry
type SearchResult struct {
// StarCount indicates the number of stars this repository has
@ -126,13 +130,13 @@ type RepositoryInfo struct {
Index *IndexInfo
// RemoteName is the remote name of the repository, such as
// "library/ubuntu-12.04-base"
RemoteName string
RemoteName reference.Named
// LocalName is the local name of the repository, such as
// "ubuntu-12.04-base"
LocalName string
LocalName reference.Named
// CanonicalName is the canonical name of the repository, such as
// "docker.io/library/ubuntu-12.04-base"
CanonicalName string
CanonicalName reference.Named
// Official indicates whether the repository is considered official.
// If the registry is official, and the normalized name does not
// contain a '/' (e.g. "foo"), then it is considered an official repo.