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" "strings"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/image" "github.com/docker/docker/image/v1"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
) )
@ -216,18 +216,15 @@ func ValidateIndexName(val string) (string, error) {
return val, nil return val, nil
} }
func validateRemoteName(remoteName string) error { func validateRemoteName(remoteName reference.Named) error {
remoteNameStr := remoteName.Name()
if !strings.Contains(remoteName, "/") { if !strings.Contains(remoteNameStr, "/") {
// the repository name must not be a valid image ID // 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) return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName)
} }
} }
return nil
_, err := reference.WithName(remoteName)
return err
} }
func validateNoSchema(reposName string) error { func validateNoSchema(reposName string) error {
@ -239,27 +236,24 @@ func validateNoSchema(reposName string) error {
} }
// ValidateRepositoryName validates a repository name // ValidateRepositoryName validates a repository name
func ValidateRepositoryName(reposName string) error { func ValidateRepositoryName(reposName reference.Named) error {
_, _, err := loadRepositoryName(reposName, true) _, _, err := loadRepositoryName(reposName)
return err return err
} }
// loadRepositoryName returns the repo name splitted into index name // loadRepositoryName returns the repo name splitted into index name
// and remote repo name. It returns an error if the name is not valid. // and remote repo name. It returns an error if the name is not valid.
func loadRepositoryName(reposName string, checkRemoteName bool) (string, string, error) { func loadRepositoryName(reposName reference.Named) (string, reference.Named, error) {
if err := validateNoSchema(reposName); err != nil { if err := validateNoSchema(reposName.Name()); err != nil {
return "", "", err return "", nil, err
} }
indexName, remoteName := splitReposName(reposName) indexName, remoteName, err := splitReposName(reposName)
var err error
if indexName, err = ValidateIndexName(indexName); err != nil { if indexName, err = ValidateIndexName(indexName); err != nil {
return "", "", err return "", nil, err
} }
if checkRemoteName { if err = validateRemoteName(remoteName); err != nil {
if err = validateRemoteName(remoteName); err != nil { return "", nil, err
return "", "", err
}
} }
return indexName, remoteName, nil return indexName, remoteName, nil
} }
@ -297,31 +291,36 @@ func (index *IndexInfo) GetAuthConfigKey() string {
} }
// splitReposName breaks a reposName into an index name and remote name // splitReposName breaks a reposName into an index name and remote name
func splitReposName(reposName string) (string, string) { func splitReposName(reposName reference.Named) (indexName string, remoteName reference.Named, err error) {
nameParts := strings.SplitN(reposName, "/", 2) var remoteNameStr string
var indexName, remoteName string indexName, remoteNameStr = reference.SplitHostname(reposName)
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && if indexName == "" || (!strings.Contains(indexName, ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { !strings.Contains(indexName, ":") && indexName != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu) // This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io' // 'docker.io'
indexName = IndexName indexName = IndexName
remoteName = reposName remoteName = reposName
} else { } else {
indexName = nameParts[0] remoteName, err = reference.WithName(remoteNameStr)
remoteName = nameParts[1]
} }
return indexName, remoteName return
} }
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo // NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) (*RepositoryInfo, error) { func (config *ServiceConfig) NewRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
indexName, remoteName, err := loadRepositoryName(reposName, !bySearch) if err := validateNoSchema(reposName.Name()); err != nil {
if err != nil {
return nil, err return nil, err
} }
repoInfo := &RepositoryInfo{ repoInfo := &RepositoryInfo{}
RemoteName: remoteName, var (
indexName string
err error
)
indexName, repoInfo.RemoteName, err = loadRepositoryName(reposName)
if err != nil {
return nil, err
} }
repoInfo.Index, err = config.NewIndexInfo(indexName) repoInfo.Index, err = config.NewIndexInfo(indexName)
@ -330,46 +329,47 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool)
} }
if repoInfo.Index.Official { 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") // If the normalized name does not contain a '/' (e.g. "foo")
// then it is an official repo. // then it is an official repo.
if strings.IndexRune(normalizedName, '/') == -1 { if strings.IndexRune(repoInfo.RemoteName.Name(), '/') == -1 {
repoInfo.Official = true repoInfo.Official = true
// Fix up remote name for official repos. // 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 { } 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 repoInfo.CanonicalName = repoInfo.LocalName
} }
return repoInfo, nil 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 // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
// lacks registry configuration. // lacks registry configuration.
func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) { func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
return emptyServiceConfig.NewRepositoryInfo(reposName, false) return emptyServiceConfig.NewRepositoryInfo(reposName)
} }
// ParseIndexInfo will use repository name to get back an indexInfo. // ParseSearchIndexInfo will use repository name to get back an indexInfo.
func ParseIndexInfo(reposName string) (*IndexInfo, error) { func ParseSearchIndexInfo(reposName string) (*IndexInfo, error) {
indexName, _ := splitReposName(reposName) indexName, _ := splitReposSearchTerm(reposName)
indexInfo, err := emptyServiceConfig.NewIndexInfo(indexName) indexInfo, err := emptyServiceConfig.NewIndexInfo(indexName)
if err != nil { if err != nil {
@ -378,12 +378,12 @@ func ParseIndexInfo(reposName string) (*IndexInfo, error) {
return indexInfo, nil 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) // 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 // It does not use the repository info because we don't want to load
// the repository index and do request over the network. // the repository index and do request over the network.
func NormalizeLocalName(name string) string { func NormalizeLocalName(name reference.Named) reference.Named {
indexName, remoteName, err := loadRepositoryName(name, true) indexName, remoteName, err := loadRepositoryName(name)
if err != nil { if err != nil {
return name return name
} }
@ -395,23 +395,52 @@ func NormalizeLocalName(name string) string {
} }
if officialIndex { 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 // normalizeLibraryRepoName removes the library prefix from
// the repository name for official repos. // the repository name for official repos.
func normalizeLibraryRepoName(name string) string { func normalizeLibraryRepoName(name reference.Named) (reference.Named, error) {
if strings.HasPrefix(name, "library/") { if strings.HasPrefix(name.Name(), "library/") {
// If pull "library/foo", it's stored locally under "foo" // 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 // localNameFromRemote combines the index name and the repo remote name
// to generate a repo local name. // to generate a repo local name.
func localNameFromRemote(indexName, remoteName string) string { func localNameFromRemote(indexName string, remoteName reference.Named) (reference.Named, error) {
return indexName + "/" + remoteName 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" "testing"
"time" "time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -349,15 +350,19 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
if !requiresAuth(w, r) { if !requiresAuth(w, r) {
return 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) repositoryName = NormalizeLocalName(repositoryName)
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName.String()]
if !exists { if !exists {
apiError(w, "Repository not found", 404) apiError(w, "Repository not found", 404)
return return
} }
if r.Method == "DELETE" { if r.Method == "DELETE" {
delete(testRepositories, repositoryName) delete(testRepositories, repositoryName.String())
writeResponse(w, true, 200) writeResponse(w, true, 200)
return return
} }
@ -369,10 +374,14 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
return return
} }
vars := mux.Vars(r) 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) repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"] tagName := vars["tag"]
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName.String()]
if !exists { if !exists {
apiError(w, "Repository not found", 404) apiError(w, "Repository not found", 404)
return return
@ -390,13 +399,17 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
return return
} }
vars := mux.Vars(r) 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) repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"] tagName := vars["tag"]
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName.String()]
if !exists { if !exists {
tags := make(map[string]string) tags = make(map[string]string)
testRepositories[repositoryName] = tags testRepositories[repositoryName.String()] = tags
} }
tagValue := "" tagValue := ""
readJSON(r, tagValue) readJSON(r, tagValue)

View file

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

View file

@ -4,7 +4,9 @@ import (
"crypto/tls" "crypto/tls"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/auth"
"github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig"
) )
@ -51,17 +53,39 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
return Login(authConfig, endpoint) 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 queries the public registry for images matching the specified
// search terms, and returns the results. // search terms, and returns the results.
func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) { 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 { if err != nil {
return nil, err return nil, err
} }
// *TODO: Search multiple indexes. // *TODO: Search multiple indexes.
endpoint, err := NewEndpoint(repoInfo.Index, http.Header(headers), APIVersionUnknown) endpoint, err := NewEndpoint(index, http.Header(headers), APIVersionUnknown)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,19 +94,23 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers
if err != nil { if err != nil {
return nil, err 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 // ResolveRepository splits a repository name into its components
// and configuration of the associated registry. // and configuration of the associated registry.
func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) { func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
return s.Config.NewRepositoryInfo(name, false) return s.Config.NewRepositoryInfo(name)
}
// 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)
} }
// ResolveIndex takes indexName and returns index info // 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. // 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 // It gives preference to v2 endpoints over v1, mirrors over the actual
// registry, and HTTPS over plain HTTP. // 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) return s.lookupEndpoints(repoName)
} }
// LookupPushEndpoints creates an list of endpoints to try to push to, in order of preference. // 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. // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
// Mirrors are not included. // 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) allEndpoints, err := s.lookupEndpoints(repoName)
if err == nil { if err == nil {
for _, endpoint := range allEndpoints { for _, endpoint := range allEndpoints {
@ -142,7 +170,7 @@ func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint,
return endpoints, err 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) endpoints, err = s.lookupV2Endpoints(repoName)
if err != nil { if err != nil {
return nil, err return nil, err

View file

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

View file

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

View file

@ -20,6 +20,7 @@ import (
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/reference"
"github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils" "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 // repository. It queries each of the registries supplied in the registries
// argument, and returns data from the first one that answers the query // argument, and returns data from the first one that answers the query
// successfully. // 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 { if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on // This will be removed once the registry supports auto-resolution on
// the "library" namespace // 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 // 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 // the first one that answers the query successfully. It returns a map with
// tag names as the keys and image IDs as the values. // 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 { if strings.Count(repository, "/") == 0 {
// This will be removed once the registry supports auto-resolution on // This will be removed once the registry supports auto-resolution on
// the "library" namespace // 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 // GetRepositoryData returns lists of images and endpoints for the repository
func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { func (r *Session) GetRepositoryData(remote reference.Named) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote) repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote.Name())
logrus.Debugf("[registry] Calling GET %s", repositoryTarget) logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
@ -443,7 +448,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
if err != nil { if err != nil {
logrus.Debugf("Error reading response body: %s", err) 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 var endpoints []string
@ -595,10 +600,10 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
// PushRegistryTag pushes a tag on the registry. // PushRegistryTag pushes a tag on the registry.
// Remote has the format '<user>/<repo> // 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 // "jsonify" the string
revision = "\"" + revision + "\"" 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)) req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
if err != nil { if err != nil {
@ -612,13 +617,13 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error
} }
res.Body.Close() res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 201 { 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 return nil
} }
// PushImageJSONIndex uploads an image list to the repository // 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{} cleanImgList := []*ImgData{}
if validate { if validate {
for _, elem := range imgList { for _, elem := range imgList {
@ -638,7 +643,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if validate { if validate {
suffix = "images" 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("[registry] PUT %s", u)
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON) logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
headers := map[string][]string{ headers := map[string][]string{
@ -676,7 +681,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if err != nil { if err != nil {
logrus.Debugf("Error reading response body: %s", err) 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"] tokens = res.Header["X-Docker-Token"]
logrus.Debugf("Auth token: %v", tokens) logrus.Debugf("Auth token: %v", tokens)
@ -694,7 +699,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if err != nil { if err != nil {
logrus.Debugf("Error reading response body: %s", err) 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 package registry
import (
"github.com/docker/distribution/reference"
)
// SearchResult describes a search result returned from a registry // SearchResult describes a search result returned from a registry
type SearchResult struct { type SearchResult struct {
// StarCount indicates the number of stars this repository has // StarCount indicates the number of stars this repository has
@ -126,13 +130,13 @@ type RepositoryInfo struct {
Index *IndexInfo Index *IndexInfo
// RemoteName is the remote name of the repository, such as // RemoteName is the remote name of the repository, such as
// "library/ubuntu-12.04-base" // "library/ubuntu-12.04-base"
RemoteName string RemoteName reference.Named
// LocalName is the local name of the repository, such as // LocalName is the local name of the repository, such as
// "ubuntu-12.04-base" // "ubuntu-12.04-base"
LocalName string LocalName reference.Named
// CanonicalName is the canonical name of the repository, such as // CanonicalName is the canonical name of the repository, such as
// "docker.io/library/ubuntu-12.04-base" // "docker.io/library/ubuntu-12.04-base"
CanonicalName string CanonicalName reference.Named
// Official indicates whether the repository is considered official. // Official indicates whether the repository is considered official.
// If the registry is official, and the normalized name does not // If the registry is official, and the normalized name does not
// contain a '/' (e.g. "foo"), then it is considered an official repo. // contain a '/' (e.g. "foo"), then it is considered an official repo.