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:
parent
dde006ba6b
commit
7efcb7496c
8 changed files with 336 additions and 196 deletions
163
docs/config.go
163
docs/config.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue