github.com/containerd/Containerd@v1.4.13/reference/docker/reference.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  // Package docker provides a general type to represent any way of referencing images within the registry.
    18  // Its main purpose is to abstract tags and digests (content-addressable hash).
    19  //
    20  // Grammar
    21  //
    22  // 	reference                       := name [ ":" tag ] [ "@" digest ]
    23  //	name                            := [domain '/'] path-component ['/' path-component]*
    24  //	domain                          := domain-component ['.' domain-component]* [':' port-number]
    25  //	domain-component                := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
    26  //	port-number                     := /[0-9]+/
    27  //	path-component                  := alpha-numeric [separator alpha-numeric]*
    28  // 	alpha-numeric                   := /[a-z0-9]+/
    29  //	separator                       := /[_.]|__|[-]*/
    30  //
    31  //	tag                             := /[\w][\w.-]{0,127}/
    32  //
    33  //	digest                          := digest-algorithm ":" digest-hex
    34  //	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
    35  //	digest-algorithm-separator      := /[+.-_]/
    36  //	digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
    37  //	digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
    38  //
    39  //	identifier                      := /[a-f0-9]{64}/
    40  //	short-identifier                := /[a-f0-9]{6,64}/
    41  package docker
    42  
    43  import (
    44  	"errors"
    45  	"fmt"
    46  	"path"
    47  	"regexp"
    48  	"strings"
    49  
    50  	"github.com/opencontainers/go-digest"
    51  )
    52  
    53  const (
    54  	// NameTotalLengthMax is the maximum total number of characters in a repository name.
    55  	NameTotalLengthMax = 255
    56  )
    57  
    58  var (
    59  	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
    60  	ErrReferenceInvalidFormat = errors.New("invalid reference format")
    61  
    62  	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
    63  	ErrTagInvalidFormat = errors.New("invalid tag format")
    64  
    65  	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
    66  	ErrDigestInvalidFormat = errors.New("invalid digest format")
    67  
    68  	// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
    69  	ErrNameContainsUppercase = errors.New("repository name must be lowercase")
    70  
    71  	// ErrNameEmpty is returned for empty, invalid repository names.
    72  	ErrNameEmpty = errors.New("repository name must have at least one component")
    73  
    74  	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
    75  	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
    76  
    77  	// ErrNameNotCanonical is returned when a name is not canonical.
    78  	ErrNameNotCanonical = errors.New("repository name must be canonical")
    79  )
    80  
    81  // Reference is an opaque object reference identifier that may include
    82  // modifiers such as a hostname, name, tag, and digest.
    83  type Reference interface {
    84  	// String returns the full reference
    85  	String() string
    86  }
    87  
    88  // Field provides a wrapper type for resolving correct reference types when
    89  // working with encoding.
    90  type Field struct {
    91  	reference Reference
    92  }
    93  
    94  // AsField wraps a reference in a Field for encoding.
    95  func AsField(reference Reference) Field {
    96  	return Field{reference}
    97  }
    98  
    99  // Reference unwraps the reference type from the field to
   100  // return the Reference object. This object should be
   101  // of the appropriate type to further check for different
   102  // reference types.
   103  func (f Field) Reference() Reference {
   104  	return f.reference
   105  }
   106  
   107  // MarshalText serializes the field to byte text which
   108  // is the string of the reference.
   109  func (f Field) MarshalText() (p []byte, err error) {
   110  	return []byte(f.reference.String()), nil
   111  }
   112  
   113  // UnmarshalText parses text bytes by invoking the
   114  // reference parser to ensure the appropriately
   115  // typed reference object is wrapped by field.
   116  func (f *Field) UnmarshalText(p []byte) error {
   117  	r, err := Parse(string(p))
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	f.reference = r
   123  	return nil
   124  }
   125  
   126  // Named is an object with a full name
   127  type Named interface {
   128  	Reference
   129  	Name() string
   130  }
   131  
   132  // Tagged is an object which has a tag
   133  type Tagged interface {
   134  	Reference
   135  	Tag() string
   136  }
   137  
   138  // NamedTagged is an object including a name and tag.
   139  type NamedTagged interface {
   140  	Named
   141  	Tag() string
   142  }
   143  
   144  // Digested is an object which has a digest
   145  // in which it can be referenced by
   146  type Digested interface {
   147  	Reference
   148  	Digest() digest.Digest
   149  }
   150  
   151  // Canonical reference is an object with a fully unique
   152  // name including a name with domain and digest
   153  type Canonical interface {
   154  	Named
   155  	Digest() digest.Digest
   156  }
   157  
   158  // namedRepository is a reference to a repository with a name.
   159  // A namedRepository has both domain and path components.
   160  type namedRepository interface {
   161  	Named
   162  	Domain() string
   163  	Path() string
   164  }
   165  
   166  // Domain returns the domain part of the Named reference
   167  func Domain(named Named) string {
   168  	if r, ok := named.(namedRepository); ok {
   169  		return r.Domain()
   170  	}
   171  	domain, _ := splitDomain(named.Name())
   172  	return domain
   173  }
   174  
   175  // Path returns the name without the domain part of the Named reference
   176  func Path(named Named) (name string) {
   177  	if r, ok := named.(namedRepository); ok {
   178  		return r.Path()
   179  	}
   180  	_, path := splitDomain(named.Name())
   181  	return path
   182  }
   183  
   184  func splitDomain(name string) (string, string) {
   185  	match := anchoredNameRegexp.FindStringSubmatch(name)
   186  	if len(match) != 3 {
   187  		return "", name
   188  	}
   189  	return match[1], match[2]
   190  }
   191  
   192  // SplitHostname splits a named reference into a
   193  // hostname and name string. If no valid hostname is
   194  // found, the hostname is empty and the full value
   195  // is returned as name
   196  // DEPRECATED: Use Domain or Path
   197  func SplitHostname(named Named) (string, string) {
   198  	if r, ok := named.(namedRepository); ok {
   199  		return r.Domain(), r.Path()
   200  	}
   201  	return splitDomain(named.Name())
   202  }
   203  
   204  // Parse parses s and returns a syntactically valid Reference.
   205  // If an error was encountered it is returned, along with a nil Reference.
   206  // NOTE: Parse will not handle short digests.
   207  func Parse(s string) (Reference, error) {
   208  	matches := ReferenceRegexp.FindStringSubmatch(s)
   209  	if matches == nil {
   210  		if s == "" {
   211  			return nil, ErrNameEmpty
   212  		}
   213  		if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
   214  			return nil, ErrNameContainsUppercase
   215  		}
   216  		return nil, ErrReferenceInvalidFormat
   217  	}
   218  
   219  	if len(matches[1]) > NameTotalLengthMax {
   220  		return nil, ErrNameTooLong
   221  	}
   222  
   223  	var repo repository
   224  
   225  	nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
   226  	if len(nameMatch) == 3 {
   227  		repo.domain = nameMatch[1]
   228  		repo.path = nameMatch[2]
   229  	} else {
   230  		repo.domain = ""
   231  		repo.path = matches[1]
   232  	}
   233  
   234  	ref := reference{
   235  		namedRepository: repo,
   236  		tag:             matches[2],
   237  	}
   238  	if matches[3] != "" {
   239  		var err error
   240  		ref.digest, err = digest.Parse(matches[3])
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  	}
   245  
   246  	r := getBestReferenceType(ref)
   247  	if r == nil {
   248  		return nil, ErrNameEmpty
   249  	}
   250  
   251  	return r, nil
   252  }
   253  
   254  // ParseNamed parses s and returns a syntactically valid reference implementing
   255  // the Named interface. The reference must have a name and be in the canonical
   256  // form, otherwise an error is returned.
   257  // If an error was encountered it is returned, along with a nil Reference.
   258  // NOTE: ParseNamed will not handle short digests.
   259  func ParseNamed(s string) (Named, error) {
   260  	named, err := ParseNormalizedNamed(s)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	if named.String() != s {
   265  		return nil, ErrNameNotCanonical
   266  	}
   267  	return named, nil
   268  }
   269  
   270  // WithName returns a named object representing the given string. If the input
   271  // is invalid ErrReferenceInvalidFormat will be returned.
   272  func WithName(name string) (Named, error) {
   273  	if len(name) > NameTotalLengthMax {
   274  		return nil, ErrNameTooLong
   275  	}
   276  
   277  	match := anchoredNameRegexp.FindStringSubmatch(name)
   278  	if match == nil || len(match) != 3 {
   279  		return nil, ErrReferenceInvalidFormat
   280  	}
   281  	return repository{
   282  		domain: match[1],
   283  		path:   match[2],
   284  	}, nil
   285  }
   286  
   287  // WithTag combines the name from "name" and the tag from "tag" to form a
   288  // reference incorporating both the name and the tag.
   289  func WithTag(name Named, tag string) (NamedTagged, error) {
   290  	if !anchoredTagRegexp.MatchString(tag) {
   291  		return nil, ErrTagInvalidFormat
   292  	}
   293  	var repo repository
   294  	if r, ok := name.(namedRepository); ok {
   295  		repo.domain = r.Domain()
   296  		repo.path = r.Path()
   297  	} else {
   298  		repo.path = name.Name()
   299  	}
   300  	if canonical, ok := name.(Canonical); ok {
   301  		return reference{
   302  			namedRepository: repo,
   303  			tag:             tag,
   304  			digest:          canonical.Digest(),
   305  		}, nil
   306  	}
   307  	return taggedReference{
   308  		namedRepository: repo,
   309  		tag:             tag,
   310  	}, nil
   311  }
   312  
   313  // WithDigest combines the name from "name" and the digest from "digest" to form
   314  // a reference incorporating both the name and the digest.
   315  func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
   316  	if !anchoredDigestRegexp.MatchString(digest.String()) {
   317  		return nil, ErrDigestInvalidFormat
   318  	}
   319  	var repo repository
   320  	if r, ok := name.(namedRepository); ok {
   321  		repo.domain = r.Domain()
   322  		repo.path = r.Path()
   323  	} else {
   324  		repo.path = name.Name()
   325  	}
   326  	if tagged, ok := name.(Tagged); ok {
   327  		return reference{
   328  			namedRepository: repo,
   329  			tag:             tagged.Tag(),
   330  			digest:          digest,
   331  		}, nil
   332  	}
   333  	return canonicalReference{
   334  		namedRepository: repo,
   335  		digest:          digest,
   336  	}, nil
   337  }
   338  
   339  // TrimNamed removes any tag or digest from the named reference.
   340  func TrimNamed(ref Named) Named {
   341  	domain, path := SplitHostname(ref)
   342  	return repository{
   343  		domain: domain,
   344  		path:   path,
   345  	}
   346  }
   347  
   348  func getBestReferenceType(ref reference) Reference {
   349  	if ref.Name() == "" {
   350  		// Allow digest only references
   351  		if ref.digest != "" {
   352  			return digestReference(ref.digest)
   353  		}
   354  		return nil
   355  	}
   356  	if ref.tag == "" {
   357  		if ref.digest != "" {
   358  			return canonicalReference{
   359  				namedRepository: ref.namedRepository,
   360  				digest:          ref.digest,
   361  			}
   362  		}
   363  		return ref.namedRepository
   364  	}
   365  	if ref.digest == "" {
   366  		return taggedReference{
   367  			namedRepository: ref.namedRepository,
   368  			tag:             ref.tag,
   369  		}
   370  	}
   371  
   372  	return ref
   373  }
   374  
   375  type reference struct {
   376  	namedRepository
   377  	tag    string
   378  	digest digest.Digest
   379  }
   380  
   381  func (r reference) String() string {
   382  	return r.Name() + ":" + r.tag + "@" + r.digest.String()
   383  }
   384  
   385  func (r reference) Tag() string {
   386  	return r.tag
   387  }
   388  
   389  func (r reference) Digest() digest.Digest {
   390  	return r.digest
   391  }
   392  
   393  type repository struct {
   394  	domain string
   395  	path   string
   396  }
   397  
   398  func (r repository) String() string {
   399  	return r.Name()
   400  }
   401  
   402  func (r repository) Name() string {
   403  	if r.domain == "" {
   404  		return r.path
   405  	}
   406  	return r.domain + "/" + r.path
   407  }
   408  
   409  func (r repository) Domain() string {
   410  	return r.domain
   411  }
   412  
   413  func (r repository) Path() string {
   414  	return r.path
   415  }
   416  
   417  type digestReference digest.Digest
   418  
   419  func (d digestReference) String() string {
   420  	return digest.Digest(d).String()
   421  }
   422  
   423  func (d digestReference) Digest() digest.Digest {
   424  	return digest.Digest(d)
   425  }
   426  
   427  type taggedReference struct {
   428  	namedRepository
   429  	tag string
   430  }
   431  
   432  func (t taggedReference) String() string {
   433  	return t.Name() + ":" + t.tag
   434  }
   435  
   436  func (t taggedReference) Tag() string {
   437  	return t.tag
   438  }
   439  
   440  type canonicalReference struct {
   441  	namedRepository
   442  	digest digest.Digest
   443  }
   444  
   445  func (c canonicalReference) String() string {
   446  	return c.Name() + "@" + c.digest.String()
   447  }
   448  
   449  func (c canonicalReference) Digest() digest.Digest {
   450  	return c.digest
   451  }
   452  
   453  var (
   454  	// alphaNumericRegexp defines the alpha numeric atom, typically a
   455  	// component of names. This only allows lower case characters and digits.
   456  	alphaNumericRegexp = match(`[a-z0-9]+`)
   457  
   458  	// separatorRegexp defines the separators allowed to be embedded in name
   459  	// components. This allow one period, one or two underscore and multiple
   460  	// dashes.
   461  	separatorRegexp = match(`(?:[._]|__|[-]*)`)
   462  
   463  	// nameComponentRegexp restricts registry path component names to start
   464  	// with at least one letter or number, with following parts able to be
   465  	// separated by one period, one or two underscore and multiple dashes.
   466  	nameComponentRegexp = expression(
   467  		alphaNumericRegexp,
   468  		optional(repeated(separatorRegexp, alphaNumericRegexp)))
   469  
   470  	// domainComponentRegexp restricts the registry domain component of a
   471  	// repository name to start with a component as defined by DomainRegexp
   472  	// and followed by an optional port.
   473  	domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
   474  
   475  	// DomainRegexp defines the structure of potential domain components
   476  	// that may be part of image names. This is purposely a subset of what is
   477  	// allowed by DNS to ensure backwards compatibility with Docker image
   478  	// names.
   479  	DomainRegexp = expression(
   480  		domainComponentRegexp,
   481  		optional(repeated(literal(`.`), domainComponentRegexp)),
   482  		optional(literal(`:`), match(`[0-9]+`)))
   483  
   484  	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
   485  	TagRegexp = match(`[\w][\w.-]{0,127}`)
   486  
   487  	// anchoredTagRegexp matches valid tag names, anchored at the start and
   488  	// end of the matched string.
   489  	anchoredTagRegexp = anchored(TagRegexp)
   490  
   491  	// DigestRegexp matches valid digests.
   492  	DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
   493  
   494  	// anchoredDigestRegexp matches valid digests, anchored at the start and
   495  	// end of the matched string.
   496  	anchoredDigestRegexp = anchored(DigestRegexp)
   497  
   498  	// NameRegexp is the format for the name component of references. The
   499  	// regexp has capturing groups for the domain and name part omitting
   500  	// the separating forward slash from either.
   501  	NameRegexp = expression(
   502  		optional(DomainRegexp, literal(`/`)),
   503  		nameComponentRegexp,
   504  		optional(repeated(literal(`/`), nameComponentRegexp)))
   505  
   506  	// anchoredNameRegexp is used to parse a name value, capturing the
   507  	// domain and trailing components.
   508  	anchoredNameRegexp = anchored(
   509  		optional(capture(DomainRegexp), literal(`/`)),
   510  		capture(nameComponentRegexp,
   511  			optional(repeated(literal(`/`), nameComponentRegexp))))
   512  
   513  	// ReferenceRegexp is the full supported format of a reference. The regexp
   514  	// is anchored and has capturing groups for name, tag, and digest
   515  	// components.
   516  	ReferenceRegexp = anchored(capture(NameRegexp),
   517  		optional(literal(":"), capture(TagRegexp)),
   518  		optional(literal("@"), capture(DigestRegexp)))
   519  
   520  	// IdentifierRegexp is the format for string identifier used as a
   521  	// content addressable identifier using sha256. These identifiers
   522  	// are like digests without the algorithm, since sha256 is used.
   523  	IdentifierRegexp = match(`([a-f0-9]{64})`)
   524  
   525  	// ShortIdentifierRegexp is the format used to represent a prefix
   526  	// of an identifier. A prefix may be used to match a sha256 identifier
   527  	// within a list of trusted identifiers.
   528  	ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
   529  
   530  	// anchoredIdentifierRegexp is used to check or match an
   531  	// identifier value, anchored at start and end of string.
   532  	anchoredIdentifierRegexp = anchored(IdentifierRegexp)
   533  )
   534  
   535  // match compiles the string to a regular expression.
   536  var match = regexp.MustCompile
   537  
   538  // literal compiles s into a literal regular expression, escaping any regexp
   539  // reserved characters.
   540  func literal(s string) *regexp.Regexp {
   541  	re := match(regexp.QuoteMeta(s))
   542  
   543  	if _, complete := re.LiteralPrefix(); !complete {
   544  		panic("must be a literal")
   545  	}
   546  
   547  	return re
   548  }
   549  
   550  // expression defines a full expression, where each regular expression must
   551  // follow the previous.
   552  func expression(res ...*regexp.Regexp) *regexp.Regexp {
   553  	var s string
   554  	for _, re := range res {
   555  		s += re.String()
   556  	}
   557  
   558  	return match(s)
   559  }
   560  
   561  // optional wraps the expression in a non-capturing group and makes the
   562  // production optional.
   563  func optional(res ...*regexp.Regexp) *regexp.Regexp {
   564  	return match(group(expression(res...)).String() + `?`)
   565  }
   566  
   567  // repeated wraps the regexp in a non-capturing group to get one or more
   568  // matches.
   569  func repeated(res ...*regexp.Regexp) *regexp.Regexp {
   570  	return match(group(expression(res...)).String() + `+`)
   571  }
   572  
   573  // group wraps the regexp in a non-capturing group.
   574  func group(res ...*regexp.Regexp) *regexp.Regexp {
   575  	return match(`(?:` + expression(res...).String() + `)`)
   576  }
   577  
   578  // capture wraps the expression in a capturing group.
   579  func capture(res ...*regexp.Regexp) *regexp.Regexp {
   580  	return match(`(` + expression(res...).String() + `)`)
   581  }
   582  
   583  // anchored anchors the regular expression by adding start and end delimiters.
   584  func anchored(res ...*regexp.Regexp) *regexp.Regexp {
   585  	return match(`^` + expression(res...).String() + `$`)
   586  }
   587  
   588  var (
   589  	legacyDefaultDomain = "index.docker.io"
   590  	defaultDomain       = "docker.io"
   591  	officialRepoName    = "library"
   592  	defaultTag          = "latest"
   593  )
   594  
   595  // normalizedNamed represents a name which has been
   596  // normalized and has a familiar form. A familiar name
   597  // is what is used in Docker UI. An example normalized
   598  // name is "docker.io/library/ubuntu" and corresponding
   599  // familiar name of "ubuntu".
   600  type normalizedNamed interface {
   601  	Named
   602  	Familiar() Named
   603  }
   604  
   605  // ParseNormalizedNamed parses a string into a named reference
   606  // transforming a familiar name from Docker UI to a fully
   607  // qualified reference. If the value may be an identifier
   608  // use ParseAnyReference.
   609  func ParseNormalizedNamed(s string) (Named, error) {
   610  	if ok := anchoredIdentifierRegexp.MatchString(s); ok {
   611  		return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
   612  	}
   613  	domain, remainder := splitDockerDomain(s)
   614  	var remoteName string
   615  	if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
   616  		remoteName = remainder[:tagSep]
   617  	} else {
   618  		remoteName = remainder
   619  	}
   620  	if strings.ToLower(remoteName) != remoteName {
   621  		return nil, errors.New("invalid reference format: repository name must be lowercase")
   622  	}
   623  
   624  	ref, err := Parse(domain + "/" + remainder)
   625  	if err != nil {
   626  		return nil, err
   627  	}
   628  	named, isNamed := ref.(Named)
   629  	if !isNamed {
   630  		return nil, fmt.Errorf("reference %s has no name", ref.String())
   631  	}
   632  	return named, nil
   633  }
   634  
   635  // ParseDockerRef normalizes the image reference following the docker convention. This is added
   636  // mainly for backward compatibility.
   637  // The reference returned can only be either tagged or digested. For reference contains both tag
   638  // and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
   639  // sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
   640  // docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
   641  func ParseDockerRef(ref string) (Named, error) {
   642  	named, err := ParseNormalizedNamed(ref)
   643  	if err != nil {
   644  		return nil, err
   645  	}
   646  	if _, ok := named.(NamedTagged); ok {
   647  		if canonical, ok := named.(Canonical); ok {
   648  			// The reference is both tagged and digested, only
   649  			// return digested.
   650  			newNamed, err := WithName(canonical.Name())
   651  			if err != nil {
   652  				return nil, err
   653  			}
   654  			newCanonical, err := WithDigest(newNamed, canonical.Digest())
   655  			if err != nil {
   656  				return nil, err
   657  			}
   658  			return newCanonical, nil
   659  		}
   660  	}
   661  	return TagNameOnly(named), nil
   662  }
   663  
   664  // splitDockerDomain splits a repository name to domain and remotename string.
   665  // If no valid domain is found, the default domain is used. Repository name
   666  // needs to be already validated before.
   667  func splitDockerDomain(name string) (domain, remainder string) {
   668  	i := strings.IndexRune(name, '/')
   669  	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
   670  		domain, remainder = defaultDomain, name
   671  	} else {
   672  		domain, remainder = name[:i], name[i+1:]
   673  	}
   674  	if domain == legacyDefaultDomain {
   675  		domain = defaultDomain
   676  	}
   677  	if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
   678  		remainder = officialRepoName + "/" + remainder
   679  	}
   680  	return
   681  }
   682  
   683  // familiarizeName returns a shortened version of the name familiar
   684  // to to the Docker UI. Familiar names have the default domain
   685  // "docker.io" and "library/" repository prefix removed.
   686  // For example, "docker.io/library/redis" will have the familiar
   687  // name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
   688  // Returns a familiarized named only reference.
   689  func familiarizeName(named namedRepository) repository {
   690  	repo := repository{
   691  		domain: named.Domain(),
   692  		path:   named.Path(),
   693  	}
   694  
   695  	if repo.domain == defaultDomain {
   696  		repo.domain = ""
   697  		// Handle official repositories which have the pattern "library/<official repo name>"
   698  		if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
   699  			repo.path = split[1]
   700  		}
   701  	}
   702  	return repo
   703  }
   704  
   705  func (r reference) Familiar() Named {
   706  	return reference{
   707  		namedRepository: familiarizeName(r.namedRepository),
   708  		tag:             r.tag,
   709  		digest:          r.digest,
   710  	}
   711  }
   712  
   713  func (r repository) Familiar() Named {
   714  	return familiarizeName(r)
   715  }
   716  
   717  func (t taggedReference) Familiar() Named {
   718  	return taggedReference{
   719  		namedRepository: familiarizeName(t.namedRepository),
   720  		tag:             t.tag,
   721  	}
   722  }
   723  
   724  func (c canonicalReference) Familiar() Named {
   725  	return canonicalReference{
   726  		namedRepository: familiarizeName(c.namedRepository),
   727  		digest:          c.digest,
   728  	}
   729  }
   730  
   731  // TagNameOnly adds the default tag "latest" to a reference if it only has
   732  // a repo name.
   733  func TagNameOnly(ref Named) Named {
   734  	if IsNameOnly(ref) {
   735  		namedTagged, err := WithTag(ref, defaultTag)
   736  		if err != nil {
   737  			// Default tag must be valid, to create a NamedTagged
   738  			// type with non-validated input the WithTag function
   739  			// should be used instead
   740  			panic(err)
   741  		}
   742  		return namedTagged
   743  	}
   744  	return ref
   745  }
   746  
   747  // ParseAnyReference parses a reference string as a possible identifier,
   748  // full digest, or familiar name.
   749  func ParseAnyReference(ref string) (Reference, error) {
   750  	if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
   751  		return digestReference("sha256:" + ref), nil
   752  	}
   753  	if dgst, err := digest.Parse(ref); err == nil {
   754  		return digestReference(dgst), nil
   755  	}
   756  
   757  	return ParseNormalizedNamed(ref)
   758  }
   759  
   760  // IsNameOnly returns true if reference only contains a repo name.
   761  func IsNameOnly(ref Named) bool {
   762  	if _, ok := ref.(NamedTagged); ok {
   763  		return false
   764  	}
   765  	if _, ok := ref.(Canonical); ok {
   766  		return false
   767  	}
   768  	return true
   769  }
   770  
   771  // FamiliarName returns the familiar name string
   772  // for the given named, familiarizing if needed.
   773  func FamiliarName(ref Named) string {
   774  	if nn, ok := ref.(normalizedNamed); ok {
   775  		return nn.Familiar().Name()
   776  	}
   777  	return ref.Name()
   778  }
   779  
   780  // FamiliarString returns the familiar string representation
   781  // for the given reference, familiarizing if needed.
   782  func FamiliarString(ref Reference) string {
   783  	if nn, ok := ref.(normalizedNamed); ok {
   784  		return nn.Familiar().String()
   785  	}
   786  	return ref.String()
   787  }
   788  
   789  // FamiliarMatch reports whether ref matches the specified pattern.
   790  // See https://godoc.org/path#Match for supported patterns.
   791  func FamiliarMatch(pattern string, ref Reference) (bool, error) {
   792  	matched, err := path.Match(pattern, FamiliarString(ref))
   793  	if namedRef, isNamed := ref.(Named); isNamed && !matched {
   794  		matched, _ = path.Match(pattern, FamiliarName(namedRef))
   795  	}
   796  	return matched, err
   797  }