From ffded61dad76e4c6530359d879afe83907e409a7 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 11 Dec 2015 11:00:13 -0800 Subject: [PATCH] Update Named reference with validation of conversions Signed-off-by: Tonis Tiigi --- api/client/build.go | 6 +- api/client/commit.go | 4 - api/client/import.go | 7 +- api/client/tag.go | 6 - api/client/trust.go | 18 +- daemon/daemon.go | 2 - distribution/pull.go | 10 +- distribution/pull_v1.go | 37 ++-- distribution/pull_v2.go | 22 +-- distribution/push.go | 14 +- distribution/push_v1.go | 10 +- distribution/push_v2.go | 8 +- distribution/registry.go | 13 +- distribution/registry_unit_test.go | 6 +- image/tarexport/save.go | 2 - reference/reference.go | 138 +++++++++++++-- reference/reference_test.go | 275 +++++++++++++++++++++++++++++ reference/store.go | 3 - registry/config.go | 178 +------------------ registry/registry_mock_test.go | 3 - registry/registry_test.go | 263 ++++++++------------------- registry/service_v1.go | 2 +- registry/service_v2.go | 2 +- registry/session.go | 20 +-- registry/types.go | 10 +- 25 files changed, 550 insertions(+), 509 deletions(-) create mode 100644 reference/reference_test.go diff --git a/api/client/build.go b/api/client/build.go index ec47c6e49c..f592ba915e 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -260,15 +260,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // validateTag checks if the given image name can be resolved. func validateTag(rawRepo string) (string, error) { - ref, err := reference.ParseNamed(rawRepo) + _, err := reference.ParseNamed(rawRepo) if err != nil { return "", err } - if err := registry.ValidateRepositoryName(ref); err != nil { - return "", err - } - return rawRepo, nil } diff --git a/api/client/commit.go b/api/client/commit.go index 9209cec22c..e7550dc27b 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -10,7 +10,6 @@ import ( "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" ) @@ -44,9 +43,6 @@ func (cli *DockerCli) CmdCommit(args ...string) error { if err != nil { return err } - if err := registry.ValidateRepositoryName(ref); err != nil { - return err - } repositoryName = ref.Name() diff --git a/api/client/import.go b/api/client/import.go index 1b829c4374..7d0fdb870c 100644 --- a/api/client/import.go +++ b/api/client/import.go @@ -12,7 +12,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/reference" - "github.com/docker/docker/registry" ) // CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image. @@ -45,11 +44,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { if repository != "" { //Check if the given image name can be resolved - ref, err := reference.ParseNamed(repository) - if err != nil { - return err - } - if err := registry.ValidateRepositoryName(ref); err != nil { + if _, err := reference.ParseNamed(repository); err != nil { return err } } diff --git a/api/client/tag.go b/api/client/tag.go index 203d64421f..b169194b96 100644 --- a/api/client/tag.go +++ b/api/client/tag.go @@ -7,7 +7,6 @@ import ( Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" - "github.com/docker/docker/registry" ) // CmdTag tags an image into a repository. @@ -35,11 +34,6 @@ func (cli *DockerCli) CmdTag(args ...string) error { tag = tagged.Tag() } - //Check if the given image name can be resolved - if err := registry.ValidateRepositoryName(ref); err != nil { - return err - } - options := types.ImageTagOptions{ ImageID: cmd.Arg(0), RepositoryName: ref.Name(), diff --git a/api/client/trust.go b/api/client/trust.go index 9a18628b28..a609cc8c8d 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -167,12 +167,12 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut } creds := simpleCredentialStore{auth: authConfig} - tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName.Name(), "push", "pull") + tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull") basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) tr := transport.NewTransport(base, modifiers...) - return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName.Name(), server, tr, cli.getPassphraseRetriever()) + return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever()) } func convertTarget(t client.Target) (target, error) { @@ -298,7 +298,7 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr for _, tgt := range targets { t, err := convertTarget(*tgt) if err != nil { - fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.LocalName) + fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name()) continue } refs = append(refs, t) @@ -321,19 +321,19 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr if displayTag != "" { displayTag = ":" + displayTag } - fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest) + fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) - if err := cli.imagePullPrivileged(authConfig, repoInfo.LocalName.Name(), r.digest.String(), requestPrivilege); err != nil { + if err := cli.imagePullPrivileged(authConfig, repoInfo.Name(), r.digest.String(), requestPrivilege); err != nil { return err } // If reference is not trusted, tag by trusted reference if !r.reference.HasDigest() { - tagged, err := reference.WithTag(repoInfo.LocalName, r.reference.String()) + tagged, err := reference.WithTag(repoInfo, r.reference.String()) if err != nil { return err } - trustedRef, err := reference.WithDigest(repoInfo.LocalName, r.digest) + trustedRef, err := reference.WithDigest(repoInfo, r.digest) if err := cli.tagTrusted(trustedRef, tagged); err != nil { return err @@ -384,7 +384,7 @@ func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig types.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error { streamOut, targetChan := targetStream(cli.out) - reqError := cli.imagePushPrivileged(authConfig, repoInfo.LocalName.Name(), tag, streamOut, requestPrivilege) + reqError := cli.imagePushPrivileged(authConfig, repoInfo.Name(), tag, streamOut, requestPrivilege) // Close stream channel to finish target parsing if err := streamOut.Close(); err != nil { @@ -455,7 +455,7 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, if err := repo.Initialize(rootKeyID); err != nil { return notaryError(err) } - fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.CanonicalName) + fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName()) return notaryError(repo.Publish()) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 73dc8d5b1b..681c1c61a2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1043,7 +1043,6 @@ func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error { if err != nil { return err } - newTag = registry.NormalizeLocalReference(newTag) if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil { return err } @@ -1301,7 +1300,6 @@ func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) { // Treat it as a possible tag or digest reference if ref, err := reference.ParseNamed(refOrID); err == nil { - ref = registry.NormalizeLocalReference(ref) if id, err := daemon.referenceStore.Get(ref); err == nil { return id, nil } diff --git a/distribution/pull.go b/distribution/pull.go index e9408d5620..456288286a 100644 --- a/distribution/pull.go +++ b/distribution/pull.go @@ -87,17 +87,15 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo } // makes sure name is not empty or `scratch` - if err := validateRepoName(repoInfo.LocalName.Name()); err != nil { + if err := validateRepoName(repoInfo.Name()); err != nil { return err } - endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo.CanonicalName) + endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo) if err != nil { return err } - localName := registry.NormalizeLocalReference(ref) - var ( // use a slice to append the error strings and return a joined string to caller errors []string @@ -112,7 +110,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo discardNoSupportErrors bool ) for _, endpoint := range endpoints { - logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version) + logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version) puller, err := newPuller(endpoint, repoInfo, imagePullConfig) if err != nil { @@ -148,7 +146,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo } } - imagePullConfig.EventsService.Log("pull", localName.String(), "") + imagePullConfig.EventsService.Log("pull", ref.String(), "") return nil } diff --git a/distribution/pull_v1.go b/distribution/pull_v1.go index 0a04216583..36d5d4852f 100644 --- a/distribution/pull_v1.go +++ b/distribution/pull_v1.go @@ -65,18 +65,18 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool // TODO(dmcgowan): Check if should fallback return false, err } - progress.Message(p.config.ProgressOutput, "", p.repoInfo.CanonicalName.Name()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.") + progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.") return false, nil } func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error { - progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.CanonicalName.Name()) + progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.FullName()) - repoData, err := p.session.GetRepositoryData(p.repoInfo.RemoteName) + repoData, err := p.session.GetRepositoryData(p.repoInfo) if err != nil { if strings.Contains(err.Error(), "HTTP code: 404") { - return fmt.Errorf("Error: image %s not found", p.repoInfo.RemoteName.Name()) + return fmt.Errorf("Error: image %s not found", p.repoInfo.RemoteName()) } // Unexpected HTTP error return err @@ -86,13 +86,13 @@ func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) erro var tagsList map[string]string tagged, isTagged := ref.(reference.NamedTagged) if !isTagged { - tagsList, err = p.session.GetRemoteTags(repoData.Endpoints, p.repoInfo.RemoteName) + tagsList, err = p.session.GetRemoteTags(repoData.Endpoints, p.repoInfo) } else { var tagID string tagsList = make(map[string]string) - tagID, err = p.session.GetRemoteTag(repoData.Endpoints, p.repoInfo.RemoteName, tagged.Tag()) + tagID, err = p.session.GetRemoteTag(repoData.Endpoints, p.repoInfo, tagged.Tag()) if err == registry.ErrRepoNotFound { - return fmt.Errorf("Tag %s not found in repository %s", tagged.Tag(), p.repoInfo.CanonicalName.Name()) + return fmt.Errorf("Tag %s not found in repository %s", tagged.Tag(), p.repoInfo.FullName()) } tagsList[tagged.Tag()] = tagID } @@ -121,14 +121,7 @@ func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) erro } } - localNameRef := p.repoInfo.LocalName - if isTagged { - localNameRef, err = reference.WithTag(localNameRef, tagged.Tag()) - if err != nil { - localNameRef = p.repoInfo.LocalName - } - } - writeStatus(localNameRef.String(), p.config.ProgressOutput, layersDownloaded) + writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) return nil } @@ -138,7 +131,7 @@ func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.Reposit return nil } - localNameRef, err := reference.WithTag(p.repoInfo.LocalName, img.Tag) + localNameRef, err := reference.WithTag(p.repoInfo, img.Tag) if err != nil { retErr := fmt.Errorf("Image (id: %s) has invalid tag: %s", img.ID, img.Tag) logrus.Debug(retErr.Error()) @@ -149,15 +142,15 @@ func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.Reposit return err } - progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, p.repoInfo.CanonicalName.Name()) + progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, p.repoInfo.FullName()) success := false var lastErr error for _, ep := range p.repoInfo.Index.Mirrors { ep += "v1/" - progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, p.repoInfo.CanonicalName.Name(), ep)) + progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, p.repoInfo.FullName(), ep)) if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { // Don't report errors when pulling from mirrors. - logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, p.repoInfo.CanonicalName.Name(), ep, err) + logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err) continue } success = true @@ -165,12 +158,12 @@ func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.Reposit } if !success { for _, ep := range repoData.Endpoints { - progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s, endpoint: %s", img.Tag, p.repoInfo.CanonicalName.Name(), ep) + progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s, endpoint: %s", img.Tag, p.repoInfo.FullName(), ep) if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { // It's not ideal that only the last error is returned, it would be better to concatenate the errors. // As the error is also given to the output stream the user will see the error. lastErr = err - progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, p.repoInfo.CanonicalName.Name(), ep, err) + progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err) continue } success = true @@ -178,7 +171,7 @@ func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.Reposit } } if !success { - err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, p.repoInfo.CanonicalName.Name(), lastErr) + err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, p.repoInfo.FullName(), lastErr) progress.Update(p.config.ProgressOutput, stringid.TruncateID(img.ID), err.Error()) return err } diff --git a/distribution/pull_v2.go b/distribution/pull_v2.go index f9c47087bd..d00ddc6c90 100644 --- a/distribution/pull_v2.go +++ b/distribution/pull_v2.go @@ -54,19 +54,11 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) { var refs []reference.Named - taggedName := p.repoInfo.LocalName - if tagged, isTagged := ref.(reference.NamedTagged); isTagged { - taggedName, err = reference.WithTag(p.repoInfo.LocalName, tagged.Tag()) - if err != nil { - return err - } - refs = []reference.Named{taggedName} - } else if digested, isCanonical := ref.(reference.Canonical); isCanonical { - taggedName, err = reference.WithDigest(p.repoInfo.LocalName, digested.Digest()) - if err != nil { - return err - } - refs = []reference.Named{taggedName} + taggedName := ref + if _, isTagged := ref.(reference.NamedTagged); isTagged { + refs = []reference.Named{ref} + } else if _, isCanonical := ref.(reference.Canonical); isCanonical { + refs = []reference.Named{ref} } else { manSvc, err := p.repo.Manifests(ctx) if err != nil { @@ -81,7 +73,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e // This probably becomes a lot nicer after the manifest // refactor... for _, tag := range tags { - tagRef, err := reference.WithTag(p.repoInfo.LocalName, tag) + tagRef, err := reference.WithTag(ref, tag) if err != nil { return err } @@ -291,7 +283,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat return false, err } - manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo.LocalName.Name()) + manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo) if err != nil { return false, err } diff --git a/distribution/push.go b/distribution/push.go index 956532a148..9a72963610 100644 --- a/distribution/push.go +++ b/distribution/push.go @@ -104,21 +104,21 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo return err } - endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(repoInfo.CanonicalName) + endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(repoInfo) if err != nil { return err } - progress.Messagef(imagePushConfig.ProgressOutput, "", "The push refers to a repository [%s]", repoInfo.CanonicalName.String()) + progress.Messagef(imagePushConfig.ProgressOutput, "", "The push refers to a repository [%s]", repoInfo.FullName()) - associations := imagePushConfig.ReferenceStore.ReferencesByName(repoInfo.LocalName) + associations := imagePushConfig.ReferenceStore.ReferencesByName(repoInfo) if len(associations) == 0 { - return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) + return fmt.Errorf("Repository does not exist: %s", repoInfo.Name()) } var lastErr error for _, endpoint := range endpoints { - logrus.Debugf("Trying to push %s to %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version) + logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version) pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig) if err != nil { @@ -143,12 +143,12 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo } - imagePushConfig.EventsService.Log("push", repoInfo.LocalName.Name(), "") + imagePushConfig.EventsService.Log("push", repoInfo.Name(), "") return nil } if lastErr == nil { - lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.CanonicalName) + lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.FullName()) } return lastErr } diff --git a/distribution/push_v1.go b/distribution/push_v1.go index 012216a68e..3b71364065 100644 --- a/distribution/push_v1.go +++ b/distribution/push_v1.go @@ -351,8 +351,8 @@ func (p *v1Pusher) pushImageToEndpoint(ctx context.Context, endpoint string, ima } if topImage, isTopImage := img.(*v1TopImage); isTopImage { for _, tag := range tags[topImage.imageID] { - progress.Messagef(p.config.ProgressOutput, "", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(v1ID), endpoint+"repositories/"+p.repoInfo.RemoteName.Name()+"/tags/"+tag) - if err := p.session.PushRegistryTag(p.repoInfo.RemoteName, v1ID, tag, endpoint); err != nil { + progress.Messagef(p.config.ProgressOutput, "", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(v1ID), endpoint+"repositories/"+p.repoInfo.RemoteName()+"/tags/"+tag) + if err := p.session.PushRegistryTag(p.repoInfo, v1ID, tag, endpoint); err != nil { return err } } @@ -381,18 +381,18 @@ func (p *v1Pusher) pushRepository(ctx context.Context) error { // Register all the images in a repository with the registry // If an image is not in this list it will not be associated with the repository - repoData, err := p.session.PushImageJSONIndex(p.repoInfo.RemoteName, imageIndex, false, nil) + repoData, err := p.session.PushImageJSONIndex(p.repoInfo, imageIndex, false, nil) if err != nil { return err } - progress.Message(p.config.ProgressOutput, "", "Pushing repository "+p.repoInfo.CanonicalName.String()) + progress.Message(p.config.ProgressOutput, "", "Pushing repository "+p.repoInfo.FullName()) // push the repository to each of the endpoints only if it does not exist. for _, endpoint := range repoData.Endpoints { if err := p.pushImageToEndpoint(ctx, endpoint, imgList, tags, repoData); err != nil { return err } } - _, err = p.session.PushImageJSONIndex(p.repoInfo.RemoteName, imageIndex, true, repoData.Endpoints) + _, err = p.session.PushImageJSONIndex(p.repoInfo, imageIndex, true, repoData.Endpoints) return err } diff --git a/distribution/push_v2.go b/distribution/push_v2.go index f687518fd1..efea1926d1 100644 --- a/distribution/push_v2.go +++ b/distribution/push_v2.go @@ -52,8 +52,6 @@ func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) { return true, err } - localName := p.repoInfo.LocalName.Name() - var associations []reference.Association if _, isTagged := p.ref.(reference.NamedTagged); isTagged { imageID, err := p.config.ReferenceStore.Get(p.ref) @@ -72,10 +70,10 @@ func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) { associations = p.config.ReferenceStore.ReferencesByName(p.ref) } if err != nil { - return false, fmt.Errorf("error getting tags for %s: %s", localName, err) + return false, fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err) } if len(associations) == 0 { - return false, fmt.Errorf("no tags to push for %s", localName) + return false, fmt.Errorf("no tags to push for %s", p.repoInfo.Name()) } for _, association := range associations { @@ -159,7 +157,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Associat return err } - manifestDigest, manifestSize, err := digestFromManifest(signed, p.repo.Name()) + manifestDigest, manifestSize, err := digestFromManifest(signed, ref) if err != nil { return err } diff --git a/distribution/registry.go b/distribution/registry.go index f46d38237c..300daf6ef5 100644 --- a/distribution/registry.go +++ b/distribution/registry.go @@ -19,6 +19,7 @@ import ( "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/api/types" "github.com/docker/docker/distribution/xfer" + "github.com/docker/docker/reference" "github.com/docker/docker/registry" "golang.org/x/net/context" ) @@ -37,10 +38,10 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) { func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (distribution.Repository, error) { ctx := context.Background() - repoName := repoInfo.CanonicalName + repoName := repoInfo.FullName() // If endpoint does not support CanonicalName, use the RemoteName instead if endpoint.TrimHostname { - repoName = repoInfo.RemoteName + repoName = repoInfo.RemoteName() } // TODO(dmcgowan): Call close idle connections when complete, use keep alive @@ -99,16 +100,16 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { creds := dumbCredentialStore{auth: authConfig} - tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName.Name(), actions...) + tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) } tr := transport.NewTransport(base, modifiers...) - return client.NewRepository(ctx, repoName.Name(), endpoint.URL, tr) + return client.NewRepository(ctx, repoName, endpoint.URL, tr) } -func digestFromManifest(m *schema1.SignedManifest, localName string) (digest.Digest, int, error) { +func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) { payload, err := m.Payload() if err != nil { // If this failed, the signatures section was corrupted @@ -117,7 +118,7 @@ func digestFromManifest(m *schema1.SignedManifest, localName string) (digest.Dig } manifestDigest, err := digest.FromBytes(payload) if err != nil { - logrus.Infof("Could not compute manifest digest for %s:%s : %v", localName, m.Tag, err) + logrus.Infof("Could not compute manifest digest for %s:%s : %v", name.Name(), m.Tag, err) } return manifestDigest, len(payload), nil } diff --git a/distribution/registry_unit_test.go b/distribution/registry_unit_test.go index f6543bf837..ca8fbe3970 100644 --- a/distribution/registry_unit_test.go +++ b/distribution/registry_unit_test.go @@ -58,16 +58,14 @@ func TestTokenPassThru(t *testing.T) { } n, _ := reference.ParseNamed("testremotename") repoInfo := ®istry.RepositoryInfo{ + Named: n, Index: ®istrytypes.IndexInfo{ Name: "testrepo", Mirrors: nil, Secure: false, Official: false, }, - RemoteName: n, - LocalName: n, - CanonicalName: n, - Official: false, + Official: false, } imagePullConfig := &ImagePullConfig{ MetaHeaders: http.Header{}, diff --git a/image/tarexport/save.go b/image/tarexport/save.go index 8626e95523..c67009e16a 100644 --- a/image/tarexport/save.go +++ b/image/tarexport/save.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/layer" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/reference" - "github.com/docker/docker/registry" ) type imageDescriptor struct { @@ -74,7 +73,6 @@ func (l *tarexporter) parseNames(names []string) (map[image.ID]*imageDescriptor, if err != nil { return nil, err } - ref = registry.NormalizeLocalReference(ref) if ref.Name() == string(digest.Canonical) { imgID, err := l.is.Search(name) if err != nil { diff --git a/reference/reference.go b/reference/reference.go index 70196a14d2..d2d733587a 100644 --- a/reference/reference.go +++ b/reference/reference.go @@ -1,21 +1,37 @@ package reference import ( + "fmt" + "strings" + "github.com/docker/distribution/digest" distreference "github.com/docker/distribution/reference" + "github.com/docker/docker/image/v1" ) -// Reference is an opaque object reference identifier that may include -// modifiers such as a hostname, name, tag, and digest. -type Reference interface { - // String returns the full reference - String() string -} +const ( + // DefaultTag defines the default tag used when performing images related actions and no tag or digest is specified + DefaultTag = "latest" + // DefaultHostname is the default built-in hostname + DefaultHostname = "docker.io" + // LegacyDefaultHostname is automatically converted to DefaultHostname + LegacyDefaultHostname = "index.docker.io" + // DefaultRepoPrefix is the prefix used for default repositories in default host + DefaultRepoPrefix = "library/" +) // Named is an object with a full name type Named interface { - Reference + // Name returns normalized repository name, like "ubuntu". Name() string + // String returns full reference, like "ubuntu@sha256:abcdef..." + String() string + // FullName returns full repository name with hostname, like "docker.io/library/ubuntu" + FullName() string + // Hostname returns hostname for the reference, like "docker.io" + Hostname() string + // RemoteName returns the repository component of the full name, like "library/ubuntu" + RemoteName() string } // NamedTagged is an object including a name and tag. @@ -35,26 +51,122 @@ type Canonical interface { // the Named interface. The reference must have a name, otherwise an error is // returned. // If an error was encountered it is returned, along with a nil Reference. -// NOTE: ParseNamed will not handle short digests. func ParseNamed(s string) (Named, error) { - // todo: docker specific validation - return distreference.ParseNamed(s) + named, err := distreference.ParseNamed(s) + if err != nil { + return nil, err + } + r, err := WithName(named.Name()) + if err != nil { + return nil, err + } + if canonical, isCanonical := named.(distreference.Canonical); isCanonical { + return WithDigest(r, canonical.Digest()) + } + if tagged, isTagged := named.(distreference.NamedTagged); isTagged { + return WithTag(r, tagged.Tag()) + } + return r, nil } // WithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. func WithName(name string) (Named, error) { - return distreference.WithName(name) + name = normalize(name) + if err := validateName(name); err != nil { + return nil, err + } + r, err := distreference.WithName(name) + if err != nil { + return nil, err + } + return &namedRef{r}, nil } // WithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. func WithTag(name Named, tag string) (NamedTagged, error) { - return distreference.WithTag(name, tag) + r, err := distreference.WithTag(name, tag) + if err != nil { + return nil, err + } + return &taggedRef{namedRef{r}}, nil } // WithDigest combines the name from "name" and the digest from "digest" to form // a reference incorporating both the name and the digest. func WithDigest(name Named, digest digest.Digest) (Canonical, error) { - return distreference.WithDigest(name, digest) + r, err := distreference.WithDigest(name, digest) + if err != nil { + return nil, err + } + return &canonicalRef{namedRef{r}}, nil +} + +type namedRef struct { + distreference.Named +} +type taggedRef struct { + namedRef +} +type canonicalRef struct { + namedRef +} + +func (r *namedRef) FullName() string { + hostname, remoteName := splitHostname(r.Name()) + return hostname + "/" + remoteName +} +func (r *namedRef) Hostname() string { + hostname, _ := splitHostname(r.Name()) + return hostname +} +func (r *namedRef) RemoteName() string { + _, remoteName := splitHostname(r.Name()) + return remoteName +} +func (r *taggedRef) Tag() string { + return r.namedRef.Named.(distreference.NamedTagged).Tag() +} +func (r *canonicalRef) Digest() digest.Digest { + return r.namedRef.Named.(distreference.Canonical).Digest() +} + +// splitHostname splits a repository name to hostname and remotename string. +// If no valid hostname is found, the default hostname is used. Repository name +// needs to be already validated before. +func splitHostname(name string) (hostname, remoteName string) { + i := strings.IndexRune(name, '/') + if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { + hostname, remoteName = DefaultHostname, name + } else { + hostname, remoteName = name[:i], name[i+1:] + } + if hostname == LegacyDefaultHostname { + hostname = DefaultHostname + } + if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') { + remoteName = DefaultRepoPrefix + remoteName + } + return +} + +// normalize returns a repository name in its normalized form, meaning it +// will not contain default hostname nor library/ prefix for official images. +func normalize(name string) string { + host, remoteName := splitHostname(name) + if host == DefaultHostname { + if strings.HasPrefix(remoteName, DefaultRepoPrefix) { + return strings.TrimPrefix(remoteName, DefaultRepoPrefix) + } + return remoteName + } + return name +} + +func validateName(name string) error { + if err := v1.ValidateID(name); err == nil { + return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) + } + return nil } diff --git a/reference/reference_test.go b/reference/reference_test.go new file mode 100644 index 0000000000..ff35ba3da2 --- /dev/null +++ b/reference/reference_test.go @@ -0,0 +1,275 @@ +package reference + +import ( + "testing" + + "github.com/docker/distribution/digest" +) + +func TestValidateReferenceName(t *testing.T) { + validRepoNames := []string{ + "docker/docker", + "library/debian", + "debian", + "docker.io/docker/docker", + "docker.io/library/debian", + "docker.io/debian", + "index.docker.io/docker/docker", + "index.docker.io/library/debian", + "index.docker.io/debian", + "127.0.0.1:5000/docker/docker", + "127.0.0.1:5000/library/debian", + "127.0.0.1:5000/debian", + "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + } + invalidRepoNames := []string{ + "https://github.com/docker/docker", + "docker/Docker", + "-docker", + "-docker/docker", + "-docker.io/docker/docker", + "docker///docker", + "docker.io/docker/Docker", + "docker.io/docker///docker", + "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + } + + for _, name := range invalidRepoNames { + _, err := ParseNamed(name) + if err == nil { + t.Fatalf("Expected invalid repo name for %q", name) + } + } + + for _, name := range validRepoNames { + _, err := ParseNamed(name) + if err != nil { + t.Fatalf("Error parsing repo name %s, got: %q", name, err) + } + } +} + +func TestValidateRemoteName(t *testing.T) { + validRepositoryNames := []string{ + // Sanity check. + "docker/docker", + + // Allow 64-character non-hexadecimal names (hexadecimal names are forbidden). + "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + + // Allow embedded hyphens. + "docker-rules/docker", + + // Allow multiple hyphens as well. + "docker---rules/docker", + + //Username doc and image name docker being tested. + "doc/docker", + + // single character names are now allowed. + "d/docker", + "jess/t", + + // Consecutive underscores. + "dock__er/docker", + } + for _, repositoryName := range validRepositoryNames { + _, err := ParseNamed(repositoryName) + if err != nil { + t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) + } + } + + invalidRepositoryNames := []string{ + // Disallow capital letters. + "docker/Docker", + + // Only allow one slash. + "docker///docker", + + // Disallow 64-character hexadecimal. + "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + + // Disallow leading and trailing hyphens in namespace. + "-docker/docker", + "docker-/docker", + "-docker-/docker", + + // Don't allow underscores everywhere (as opposed to hyphens). + "____/____", + + "_docker/_docker", + + // Disallow consecutive periods. + "dock..er/docker", + "dock_.er/docker", + "dock-.er/docker", + + // No repository. + "docker/", + + //namespace too long + "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", + } + for _, repositoryName := range invalidRepositoryNames { + if _, err := ParseNamed(repositoryName); err == nil { + t.Errorf("Repository name should be invalid: %v", repositoryName) + } + } +} + +func TestParseRepositoryInfo(t *testing.T) { + type tcase struct { + RemoteName, NormalizedName, FullName, AmbiguousName, Hostname string + } + + tcases := []tcase{ + { + RemoteName: "fooo/bar", + NormalizedName: "fooo/bar", + FullName: "docker.io/fooo/bar", + AmbiguousName: "index.docker.io/fooo/bar", + Hostname: "docker.io", + }, + { + RemoteName: "library/ubuntu", + NormalizedName: "ubuntu", + FullName: "docker.io/library/ubuntu", + AmbiguousName: "library/ubuntu", + Hostname: "docker.io", + }, + { + RemoteName: "nonlibrary/ubuntu", + NormalizedName: "nonlibrary/ubuntu", + FullName: "docker.io/nonlibrary/ubuntu", + AmbiguousName: "", + Hostname: "docker.io", + }, + { + RemoteName: "other/library", + NormalizedName: "other/library", + FullName: "docker.io/other/library", + AmbiguousName: "", + Hostname: "docker.io", + }, + { + RemoteName: "private/moonbase", + NormalizedName: "127.0.0.1:8000/private/moonbase", + FullName: "127.0.0.1:8000/private/moonbase", + AmbiguousName: "", + Hostname: "127.0.0.1:8000", + }, + { + RemoteName: "privatebase", + NormalizedName: "127.0.0.1:8000/privatebase", + FullName: "127.0.0.1:8000/privatebase", + AmbiguousName: "", + Hostname: "127.0.0.1:8000", + }, + { + RemoteName: "private/moonbase", + NormalizedName: "example.com/private/moonbase", + FullName: "example.com/private/moonbase", + AmbiguousName: "", + Hostname: "example.com", + }, + { + RemoteName: "privatebase", + NormalizedName: "example.com/privatebase", + FullName: "example.com/privatebase", + AmbiguousName: "", + Hostname: "example.com", + }, + { + RemoteName: "private/moonbase", + NormalizedName: "example.com:8000/private/moonbase", + FullName: "example.com:8000/private/moonbase", + AmbiguousName: "", + Hostname: "example.com:8000", + }, + { + RemoteName: "privatebasee", + NormalizedName: "example.com:8000/privatebasee", + FullName: "example.com:8000/privatebasee", + AmbiguousName: "", + Hostname: "example.com:8000", + }, + { + RemoteName: "library/ubuntu-12.04-base", + NormalizedName: "ubuntu-12.04-base", + FullName: "docker.io/library/ubuntu-12.04-base", + AmbiguousName: "index.docker.io/library/ubuntu-12.04-base", + Hostname: "docker.io", + }, + } + + for _, tcase := range tcases { + refStrings := []string{tcase.NormalizedName, tcase.FullName} + if tcase.AmbiguousName != "" { + refStrings = append(refStrings, tcase.AmbiguousName) + } + + var refs []Named + for _, r := range refStrings { + named, err := ParseNamed(r) + if err != nil { + t.Fatal(err) + } + refs = append(refs, named) + named, err = WithName(r) + if err != nil { + t.Fatal(err) + } + refs = append(refs, named) + } + + for _, r := range refs { + if expected, actual := tcase.NormalizedName, r.Name(); expected != actual { + t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.FullName, r.FullName(); expected != actual { + t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.Hostname, r.Hostname(); expected != actual { + t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.RemoteName, r.RemoteName(); expected != actual { + t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) + } + + } + } +} + +func TestParseReferenceWithTagAndDigest(t *testing.T) { + ref, err := ParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") + if err != nil { + t.Fatal(err) + } + if _, isTagged := ref.(NamedTagged); isTagged { + t.Fatalf("Reference from %q should not support tag", ref) + } + if _, isCanonical := ref.(Canonical); !isCanonical { + t.Fatalf("Reference from %q should not support digest", ref) + } + if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected { + t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) + } +} + +func TestInvalidReferenceComponents(t *testing.T) { + if _, err := WithName("-foo"); err == nil { + t.Fatal("Expected WithName to detect invalid name") + } + ref, err := WithName("busybox") + if err != nil { + t.Fatal(err) + } + if _, err := WithTag(ref, "-foo"); err == nil { + t.Fatal("Expected WithName to detect invalid tag") + } + if _, err := WithDigest(ref, digest.Digest("foo")); err == nil { + t.Fatal("Expected WithName to detect invalid digest") + } +} diff --git a/reference/store.go b/reference/store.go index 660c9e2019..85608f7e87 100644 --- a/reference/store.go +++ b/reference/store.go @@ -14,9 +14,6 @@ import ( "github.com/docker/docker/image" ) -// DefaultTag defines the default tag used when performing images related actions and no tag string is specified -const DefaultTag = "latest" - var ( // ErrDoesNotExist is returned if a reference is not found in the // store. diff --git a/registry/config.go b/registry/config.go index 45caf57891..ca7beec45a 100644 --- a/registry/config.go +++ b/registry/config.go @@ -7,9 +7,7 @@ import ( "net/url" "strings" - distreference "github.com/docker/distribution/reference" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/image/v1" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" @@ -182,28 +180,15 @@ func ValidateMirror(val string) (string, error) { // ValidateIndexName validates an index name. func ValidateIndexName(val string) (string, error) { - // 'index.docker.io' => 'docker.io' - if val == "index."+IndexName { - val = IndexName + if val == reference.LegacyDefaultHostname { + val = reference.DefaultHostname } if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val) } - // *TODO: Check if valid hostname[:port]/ip[:port]? return val, nil } -func validateRemoteName(remoteName reference.Named) error { - remoteNameStr := remoteName.Name() - if !strings.Contains(remoteNameStr, "/") { - // the repository name must not be a valid image ID - if err := v1.ValidateID(remoteNameStr); err == nil { - return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName) - } - } - return nil -} - func validateNoSchema(reposName string) error { if strings.Contains(reposName, "://") { // It cannot contain a scheme! @@ -212,29 +197,6 @@ func validateNoSchema(reposName string) error { return nil } -// ValidateRepositoryName validates a repository name -func ValidateRepositoryName(reposName reference.Named) error { - _, _, err := loadRepositoryName(reposName) - return err -} - -// loadRepositoryName returns the repo name splitted into index name -// and remote repo name. It returns an error if the name is not valid. -func loadRepositoryName(reposName reference.Named) (string, reference.Named, error) { - if err := validateNoSchema(reposName.Name()); err != nil { - return "", nil, err - } - indexName, remoteName, err := splitReposName(reposName) - - if indexName, err = ValidateIndexName(indexName); err != nil { - return "", nil, err - } - if err = validateRemoteName(remoteName); err != nil { - return "", nil, err - } - return indexName, remoteName, nil -} - // newIndexInfo returns IndexInfo configuration from indexName func newIndexInfo(config *registrytypes.ServiceConfig, indexName string) (*registrytypes.IndexInfo, error) { var err error @@ -267,75 +229,14 @@ func GetAuthConfigKey(index *registrytypes.IndexInfo) string { return index.Name } -// splitReposName breaks a reposName into an index name and remote name -func splitReposName(reposName reference.Named) (indexName string, remoteName reference.Named, err error) { - var remoteNameStr string - indexName, remoteNameStr = distreference.SplitHostname(reposName) - if indexName == "" || (!strings.Contains(indexName, ".") && - !strings.Contains(indexName, ":") && indexName != "localhost") { - // This is a Docker Index repos (ex: samalba/hipache or ubuntu) - // 'docker.io' - indexName = IndexName - remoteName = reposName - } else { - remoteName, err = reference.WithName(remoteNameStr) - } - return -} - // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo -func newRepositoryInfo(config *registrytypes.ServiceConfig, reposName reference.Named) (*RepositoryInfo, error) { - if err := validateNoSchema(reposName.Name()); err != nil { - return nil, err - } - - repoInfo := &RepositoryInfo{} - var ( - indexName string - err error - ) - - indexName, repoInfo.RemoteName, err = loadRepositoryName(reposName) +func newRepositoryInfo(config *registrytypes.ServiceConfig, name reference.Named) (*RepositoryInfo, error) { + index, err := newIndexInfo(config, name.Hostname()) if err != nil { return nil, err } - - repoInfo.Index, err = newIndexInfo(config, indexName) - if err != nil { - return nil, err - } - - if repoInfo.Index.Official { - repoInfo.LocalName, err = normalizeLibraryRepoName(repoInfo.RemoteName) - if err != nil { - return nil, err - } - repoInfo.RemoteName = repoInfo.LocalName - - // If the normalized name does not contain a '/' (e.g. "foo") - // then it is an official repo. - if strings.IndexRune(repoInfo.RemoteName.Name(), '/') == -1 { - repoInfo.Official = true - // Fix up remote name for official repos. - repoInfo.RemoteName, err = reference.WithName("library/" + repoInfo.RemoteName.Name()) - if err != nil { - return nil, err - } - } - - repoInfo.CanonicalName, err = reference.WithName("docker.io/" + repoInfo.RemoteName.Name()) - if err != nil { - return nil, err - } - } else { - repoInfo.LocalName, err = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName) - if err != nil { - return nil, err - } - repoInfo.CanonicalName = repoInfo.LocalName - } - - return repoInfo, nil + official := !strings.ContainsRune(name.Name(), '/') + return &RepositoryInfo{name, index, official}, nil } // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but @@ -354,70 +255,3 @@ func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) { } return indexInfo, nil } - -// NormalizeLocalName transforms a repository name into a normalized LocalName -// Passes through the name without transformation on error (image id, etc) -// It does not use the repository info because we don't want to load -// the repository index and do request over the network. -func NormalizeLocalName(name reference.Named) reference.Named { - indexName, remoteName, err := loadRepositoryName(name) - if err != nil { - return name - } - - var officialIndex bool - // Return any configured index info, first. - if index, ok := emptyServiceConfig.IndexConfigs[indexName]; ok { - officialIndex = index.Official - } - - if officialIndex { - localName, err := normalizeLibraryRepoName(remoteName) - if err != nil { - return name - } - return localName - } - localName, err := localNameFromRemote(indexName, remoteName) - if err != nil { - return name - } - return localName -} - -// normalizeLibraryRepoName removes the library prefix from -// the repository name for official repos. -func normalizeLibraryRepoName(name reference.Named) (reference.Named, error) { - if strings.HasPrefix(name.Name(), "library/") { - // If pull "library/foo", it's stored locally under "foo" - return reference.WithName(strings.SplitN(name.Name(), "/", 2)[1]) - } - return name, nil -} - -// localNameFromRemote combines the index name and the repo remote name -// to generate a repo local name. -func localNameFromRemote(indexName string, remoteName reference.Named) (reference.Named, error) { - return reference.WithName(indexName + "/" + remoteName.Name()) -} - -// NormalizeLocalReference transforms a reference to use a normalized LocalName -// for the name poriton. Passes through the reference without transformation on -// error. -func NormalizeLocalReference(ref reference.Named) reference.Named { - localName := NormalizeLocalName(ref) - if tagged, isTagged := ref.(reference.NamedTagged); isTagged { - newRef, err := reference.WithTag(localName, tagged.Tag()) - if err != nil { - return ref - } - return newRef - } else if digested, isCanonical := ref.(reference.Canonical); isCanonical { - newRef, err := reference.WithDigest(localName, digested.Digest()) - if err != nil { - return ref - } - return newRef - } - return localName -} diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 017d08bbe5..be04e34689 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -356,7 +356,6 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) { apiError(w, "Could not parse repository", 400) return } - repositoryName = NormalizeLocalName(repositoryName) tags, exists := testRepositories[repositoryName.String()] if !exists { apiError(w, "Repository not found", 404) @@ -380,7 +379,6 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) { apiError(w, "Could not parse repository", 400) return } - repositoryName = NormalizeLocalName(repositoryName) tagName := vars["tag"] tags, exists := testRepositories[repositoryName.String()] if !exists { @@ -405,7 +403,6 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) { apiError(w, "Could not parse repository", 400) return } - repositoryName = NormalizeLocalName(repositoryName) tagName := vars["tag"] tags, exists := testRepositories[repositoryName.String()] if !exists { diff --git a/registry/registry_test.go b/registry/registry_test.go index 31ae2d5fbb..46d2818fb7 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -307,71 +307,24 @@ func TestPushImageLayerRegistry(t *testing.T) { } } -func TestValidateRepositoryName(t *testing.T) { - validRepoNames := []string{ - "docker/docker", - "library/debian", - "debian", - "docker.io/docker/docker", - "docker.io/library/debian", - "docker.io/debian", - "index.docker.io/docker/docker", - "index.docker.io/library/debian", - "index.docker.io/debian", - "127.0.0.1:5000/docker/docker", - "127.0.0.1:5000/library/debian", - "127.0.0.1:5000/debian", - "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", - } - invalidRepoNames := []string{ - "https://github.com/docker/docker", - "docker/Docker", - "-docker", - "-docker/docker", - "-docker.io/docker/docker", - "docker///docker", - "docker.io/docker/Docker", - "docker.io/docker///docker", - "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - } - - for _, name := range invalidRepoNames { - named, err := reference.WithName(name) - if err == nil { - err := ValidateRepositoryName(named) - assertNotEqual(t, err, nil, "Expected invalid repo name: "+name) - } - } - - for _, name := range validRepoNames { - 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) - } -} - 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 + type staticRepositoryInfo struct { + Index *registrytypes.IndexInfo + RemoteName string + CanonicalName string + LocalName string + Official bool } - expectedRepoInfos := map[string]RepositoryInfo{ + expectedRepoInfos := map[string]staticRepositoryInfo{ "fooo/bar": { Index: ®istrytypes.IndexInfo{ Name: IndexName, Official: true, }, - RemoteName: withName("fooo/bar"), - LocalName: withName("fooo/bar"), - CanonicalName: withName("docker.io/fooo/bar"), + RemoteName: "fooo/bar", + LocalName: "fooo/bar", + CanonicalName: "docker.io/fooo/bar", Official: false, }, "library/ubuntu": { @@ -379,9 +332,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("library/ubuntu"), - LocalName: withName("ubuntu"), - CanonicalName: withName("docker.io/library/ubuntu"), + RemoteName: "library/ubuntu", + LocalName: "ubuntu", + CanonicalName: "docker.io/library/ubuntu", Official: true, }, "nonlibrary/ubuntu": { @@ -389,9 +342,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("nonlibrary/ubuntu"), - LocalName: withName("nonlibrary/ubuntu"), - CanonicalName: withName("docker.io/nonlibrary/ubuntu"), + RemoteName: "nonlibrary/ubuntu", + LocalName: "nonlibrary/ubuntu", + CanonicalName: "docker.io/nonlibrary/ubuntu", Official: false, }, "ubuntu": { @@ -399,9 +352,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("library/ubuntu"), - LocalName: withName("ubuntu"), - CanonicalName: withName("docker.io/library/ubuntu"), + RemoteName: "library/ubuntu", + LocalName: "ubuntu", + CanonicalName: "docker.io/library/ubuntu", Official: true, }, "other/library": { @@ -409,9 +362,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("other/library"), - LocalName: withName("other/library"), - CanonicalName: withName("docker.io/other/library"), + RemoteName: "other/library", + LocalName: "other/library", + CanonicalName: "docker.io/other/library", Official: false, }, "127.0.0.1:8000/private/moonbase": { @@ -419,9 +372,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "127.0.0.1:8000", Official: false, }, - RemoteName: withName("private/moonbase"), - LocalName: withName("127.0.0.1:8000/private/moonbase"), - CanonicalName: withName("127.0.0.1:8000/private/moonbase"), + RemoteName: "private/moonbase", + LocalName: "127.0.0.1:8000/private/moonbase", + CanonicalName: "127.0.0.1:8000/private/moonbase", Official: false, }, "127.0.0.1:8000/privatebase": { @@ -429,9 +382,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "127.0.0.1:8000", Official: false, }, - RemoteName: withName("privatebase"), - LocalName: withName("127.0.0.1:8000/privatebase"), - CanonicalName: withName("127.0.0.1:8000/privatebase"), + RemoteName: "privatebase", + LocalName: "127.0.0.1:8000/privatebase", + CanonicalName: "127.0.0.1:8000/privatebase", Official: false, }, "localhost:8000/private/moonbase": { @@ -439,9 +392,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "localhost:8000", Official: false, }, - RemoteName: withName("private/moonbase"), - LocalName: withName("localhost:8000/private/moonbase"), - CanonicalName: withName("localhost:8000/private/moonbase"), + RemoteName: "private/moonbase", + LocalName: "localhost:8000/private/moonbase", + CanonicalName: "localhost:8000/private/moonbase", Official: false, }, "localhost:8000/privatebase": { @@ -449,9 +402,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "localhost:8000", Official: false, }, - RemoteName: withName("privatebase"), - LocalName: withName("localhost:8000/privatebase"), - CanonicalName: withName("localhost:8000/privatebase"), + RemoteName: "privatebase", + LocalName: "localhost:8000/privatebase", + CanonicalName: "localhost:8000/privatebase", Official: false, }, "example.com/private/moonbase": { @@ -459,9 +412,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "example.com", Official: false, }, - RemoteName: withName("private/moonbase"), - LocalName: withName("example.com/private/moonbase"), - CanonicalName: withName("example.com/private/moonbase"), + RemoteName: "private/moonbase", + LocalName: "example.com/private/moonbase", + CanonicalName: "example.com/private/moonbase", Official: false, }, "example.com/privatebase": { @@ -469,9 +422,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "example.com", Official: false, }, - RemoteName: withName("privatebase"), - LocalName: withName("example.com/privatebase"), - CanonicalName: withName("example.com/privatebase"), + RemoteName: "privatebase", + LocalName: "example.com/privatebase", + CanonicalName: "example.com/privatebase", Official: false, }, "example.com:8000/private/moonbase": { @@ -479,9 +432,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "example.com:8000", Official: false, }, - RemoteName: withName("private/moonbase"), - LocalName: withName("example.com:8000/private/moonbase"), - CanonicalName: withName("example.com:8000/private/moonbase"), + RemoteName: "private/moonbase", + LocalName: "example.com:8000/private/moonbase", + CanonicalName: "example.com:8000/private/moonbase", Official: false, }, "example.com:8000/privatebase": { @@ -489,9 +442,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "example.com:8000", Official: false, }, - RemoteName: withName("privatebase"), - LocalName: withName("example.com:8000/privatebase"), - CanonicalName: withName("example.com:8000/privatebase"), + RemoteName: "privatebase", + LocalName: "example.com:8000/privatebase", + CanonicalName: "example.com:8000/privatebase", Official: false, }, "localhost/private/moonbase": { @@ -499,9 +452,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "localhost", Official: false, }, - RemoteName: withName("private/moonbase"), - LocalName: withName("localhost/private/moonbase"), - CanonicalName: withName("localhost/private/moonbase"), + RemoteName: "private/moonbase", + LocalName: "localhost/private/moonbase", + CanonicalName: "localhost/private/moonbase", Official: false, }, "localhost/privatebase": { @@ -509,9 +462,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: "localhost", Official: false, }, - RemoteName: withName("privatebase"), - LocalName: withName("localhost/privatebase"), - CanonicalName: withName("localhost/privatebase"), + RemoteName: "privatebase", + LocalName: "localhost/privatebase", + CanonicalName: "localhost/privatebase", Official: false, }, IndexName + "/public/moonbase": { @@ -519,9 +472,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("public/moonbase"), - LocalName: withName("public/moonbase"), - CanonicalName: withName("docker.io/public/moonbase"), + RemoteName: "public/moonbase", + LocalName: "public/moonbase", + CanonicalName: "docker.io/public/moonbase", Official: false, }, "index." + IndexName + "/public/moonbase": { @@ -529,9 +482,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("public/moonbase"), - LocalName: withName("public/moonbase"), - CanonicalName: withName("docker.io/public/moonbase"), + RemoteName: "public/moonbase", + LocalName: "public/moonbase", + CanonicalName: "docker.io/public/moonbase", Official: false, }, "ubuntu-12.04-base": { @@ -539,9 +492,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("library/ubuntu-12.04-base"), - LocalName: withName("ubuntu-12.04-base"), - CanonicalName: withName("docker.io/library/ubuntu-12.04-base"), + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", Official: true, }, IndexName + "/ubuntu-12.04-base": { @@ -549,9 +502,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("library/ubuntu-12.04-base"), - LocalName: withName("ubuntu-12.04-base"), - CanonicalName: withName("docker.io/library/ubuntu-12.04-base"), + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", Official: true, }, "index." + IndexName + "/ubuntu-12.04-base": { @@ -559,9 +512,9 @@ func TestParseRepositoryInfo(t *testing.T) { Name: IndexName, Official: true, }, - RemoteName: withName("library/ubuntu-12.04-base"), - LocalName: withName("ubuntu-12.04-base"), - CanonicalName: withName("docker.io/library/ubuntu-12.04-base"), + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", Official: true, }, } @@ -577,9 +530,9 @@ func TestParseRepositoryInfo(t *testing.T) { t.Error(err) } else { checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName) - checkEqual(t, repoInfo.RemoteName.String(), expectedRepoInfo.RemoteName.String(), reposName) - checkEqual(t, repoInfo.LocalName.String(), expectedRepoInfo.LocalName.String(), reposName) - checkEqual(t, repoInfo.CanonicalName.String(), expectedRepoInfo.CanonicalName.String(), reposName) + checkEqual(t, repoInfo.RemoteName(), expectedRepoInfo.RemoteName, reposName) + checkEqual(t, repoInfo.Name(), expectedRepoInfo.LocalName, reposName) + checkEqual(t, repoInfo.FullName(), expectedRepoInfo.CanonicalName, reposName) checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName) checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName) } @@ -806,82 +759,6 @@ func TestSearchRepositories(t *testing.T) { assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") } -func TestValidRemoteName(t *testing.T) { - validRepositoryNames := []string{ - // Sanity check. - "docker/docker", - - // Allow 64-character non-hexadecimal names (hexadecimal names are forbidden). - "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", - - // Allow embedded hyphens. - "docker-rules/docker", - - // Allow multiple hyphens as well. - "docker---rules/docker", - - //Username doc and image name docker being tested. - "doc/docker", - - // single character names are now allowed. - "d/docker", - "jess/t", - - // Consecutive underscores. - "dock__er/docker", - } - for _, repositoryName := range validRepositoryNames { - 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) - } - } - - invalidRepositoryNames := []string{ - // Disallow capital letters. - "docker/Docker", - - // Only allow one slash. - "docker///docker", - - // Disallow 64-character hexadecimal. - "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - - // Disallow leading and trailing hyphens in namespace. - "-docker/docker", - "docker-/docker", - "-docker-/docker", - - // Don't allow underscores everywhere (as opposed to hyphens). - "____/____", - - "_docker/_docker", - - // Disallow consecutive periods. - "dock..er/docker", - "dock_.er/docker", - "dock-.er/docker", - - // No repository. - "docker/", - - //namespace too long - "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", - } - for _, repositoryName := range invalidRepositoryNames { - repositoryRef, err := reference.ParseNamed(repositoryName) - if err != nil { - continue - } - if err := validateRemoteName(repositoryRef); err == nil { - t.Errorf("Repository name should be invalid: %v", repositoryName) - } - } -} - func TestTrustedLocation(t *testing.T) { for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} { req, _ := http.NewRequest("GET", url, nil) diff --git a/registry/service_v1.go b/registry/service_v1.go index 3b3cc780f3..cd565bc43c 100644 --- a/registry/service_v1.go +++ b/registry/service_v1.go @@ -11,7 +11,7 @@ import ( func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg - nameString := repoName.Name() + nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { endpoints = append(endpoints, APIEndpoint{ URL: DefaultV1Registry, diff --git a/registry/service_v2.go b/registry/service_v2.go index 3a2c32a5d9..8a8cd2600d 100644 --- a/registry/service_v2.go +++ b/registry/service_v2.go @@ -12,7 +12,7 @@ import ( func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg - nameString := repoName.Name() + nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { // v2 mirrors for _, mirror := range s.Config.Mirrors { diff --git a/registry/session.go b/registry/session.go index a1206206f6..494b84bf5a 100644 --- a/registry/session.go +++ b/registry/session.go @@ -312,7 +312,7 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io // argument, and returns data from the first one that answers the query // successfully. func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) { - repository := repositoryRef.Name() + repository := repositoryRef.RemoteName() if strings.Count(repository, "/") == 0 { // This will be removed once the registry supports auto-resolution on @@ -350,7 +350,7 @@ func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Name // the first one that answers the query successfully. It returns a map with // tag names as the keys and image IDs as the values. func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) { - repository := repositoryRef.Name() + repository := repositoryRef.RemoteName() if strings.Count(repository, "/") == 0 { // This will be removed once the registry supports auto-resolution on @@ -403,8 +403,8 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) { } // GetRepositoryData returns lists of images and endpoints for the repository -func (r *Session) GetRepositoryData(remote reference.Named) (*RepositoryData, error) { - repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote.Name()) +func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, error) { + repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), name.RemoteName()) logrus.Debugf("[registry] Calling GET %s", repositoryTarget) @@ -438,7 +438,7 @@ func (r *Session) GetRepositoryData(remote reference.Named) (*RepositoryData, er if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote.Name(), errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, name.RemoteName(), errBody), res) } var endpoints []string @@ -593,7 +593,7 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error { // "jsonify" the string revision = "\"" + revision + "\"" - path := fmt.Sprintf("repositories/%s/tags/%s", remote.Name(), tag) + path := fmt.Sprintf("repositories/%s/tags/%s", remote.RemoteName(), tag) req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision)) if err != nil { @@ -607,7 +607,7 @@ func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registr } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { - return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.Name()), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.RemoteName()), res) } return nil } @@ -633,7 +633,7 @@ func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, if validate { suffix = "images" } - u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote.Name(), suffix) + u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote.RemoteName(), suffix) logrus.Debugf("[registry] PUT %s", u) logrus.Debugf("Image list pushed to index:\n%s", imgListJSON) headers := map[string][]string{ @@ -671,7 +671,7 @@ func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote.Name(), errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote.RemoteName(), errBody), res) } tokens = res.Header["X-Docker-Token"] logrus.Debugf("Auth token: %v", tokens) @@ -689,7 +689,7 @@ func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote.Name(), errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote.RemoteName(), errBody), res) } } diff --git a/registry/types.go b/registry/types.go index 939f44b14c..da3eaacb3f 100644 --- a/registry/types.go +++ b/registry/types.go @@ -60,17 +60,9 @@ const ( // RepositoryInfo describes a repository type RepositoryInfo struct { + reference.Named // Index points to registry information Index *registrytypes.IndexInfo - // RemoteName is the remote name of the repository, such as - // "library/ubuntu-12.04-base" - RemoteName reference.Named - // LocalName is the local name of the repository, such as - // "ubuntu-12.04-base" - LocalName reference.Named - // CanonicalName is the canonical name of the repository, such as - // "docker.io/library/ubuntu-12.04-base" - CanonicalName reference.Named // Official indicates whether the repository is considered official. // If the registry is official, and the normalized name does not // contain a '/' (e.g. "foo"), then it is considered an official repo.