github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/reference/reference.go (about)

     1  // Package reference provides a general type to represent any way of referencing images within the registry.
     2  // Its main purpose is to abstract tags and digests (content-addressable hash).
     3  //
     4  // Grammar
     5  //
     6  // 	reference                       := repository [ ":" tag ] [ "@" digest ]
     7  //	name                            := [hostname '/'] component ['/' component]*
     8  //	hostname                        := hostcomponent ['.' hostcomponent]* [':' port-number]
     9  //	hostcomponent                   := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/
    10  //	port-number                     := /[0-9]+/
    11  //	component                       := alpha-numeric [separator alpha-numeric]*
    12  // 	alpha-numeric                   := /[a-z0-9]+/
    13  //	separator                       := /[_.]|__|[-]*/
    14  //
    15  //	tag                             := /[\w][\w.-]{0,127}/
    16  //
    17  //	digest                          := digest-algorithm ":" digest-hex
    18  //	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
    19  //	digest-algorithm-separator      := /[+.-_]/
    20  //	digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
    21  //	digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
    22  package reference
    23  
    24  import (
    25  	"errors"
    26  	"fmt"
    27  
    28  	"github.com/docker/distribution/digest"
    29  )
    30  
    31  const (
    32  	// NameTotalLengthMax is the maximum total number of characters in a repository name.
    33  	NameTotalLengthMax = 255
    34  )
    35  
    36  var (
    37  	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
    38  	ErrReferenceInvalidFormat = errors.New("invalid reference format")
    39  
    40  	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
    41  	ErrTagInvalidFormat = errors.New("invalid tag format")
    42  
    43  	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
    44  	ErrDigestInvalidFormat = errors.New("invalid digest format")
    45  
    46  	// ErrNameEmpty is returned for empty, invalid repository names.
    47  	ErrNameEmpty = errors.New("repository name must have at least one component")
    48  
    49  	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
    50  	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
    51  )
    52  
    53  // Reference is an opaque object reference identifier that may include
    54  // modifiers such as a hostname, name, tag, and digest.
    55  type Reference interface {
    56  	// String returns the full reference
    57  	String() string
    58  }
    59  
    60  // Field provides a wrapper type for resolving correct reference types when
    61  // working with encoding.
    62  type Field struct {
    63  	reference Reference
    64  }
    65  
    66  // AsField wraps a reference in a Field for encoding.
    67  func AsField(reference Reference) Field {
    68  	return Field{reference}
    69  }
    70  
    71  // Reference unwraps the reference type from the field to
    72  // return the Reference object. This object should be
    73  // of the appropriate type to further check for different
    74  // reference types.
    75  func (f Field) Reference() Reference {
    76  	return f.reference
    77  }
    78  
    79  // MarshalText serializes the field to byte text which
    80  // is the string of the reference.
    81  func (f Field) MarshalText() (p []byte, err error) {
    82  	return []byte(f.reference.String()), nil
    83  }
    84  
    85  // UnmarshalText parses text bytes by invoking the
    86  // reference parser to ensure the appropriately
    87  // typed reference object is wrapped by field.
    88  func (f *Field) UnmarshalText(p []byte) error {
    89  	r, err := Parse(string(p))
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	f.reference = r
    95  	return nil
    96  }
    97  
    98  // Named is an object with a full name
    99  type Named interface {
   100  	Reference
   101  	Name() string
   102  }
   103  
   104  // Tagged is an object which has a tag
   105  type Tagged interface {
   106  	Reference
   107  	Tag() string
   108  }
   109  
   110  // NamedTagged is an object including a name and tag.
   111  type NamedTagged interface {
   112  	Named
   113  	Tag() string
   114  }
   115  
   116  // Digested is an object which has a digest
   117  // in which it can be referenced by
   118  type Digested interface {
   119  	Reference
   120  	Digest() digest.Digest
   121  }
   122  
   123  // Canonical reference is an object with a fully unique
   124  // name including a name with hostname and digest
   125  type Canonical interface {
   126  	Named
   127  	Digest() digest.Digest
   128  }
   129  
   130  // SplitHostname splits a named reference into a
   131  // hostname and name string. If no valid hostname is
   132  // found, the hostname is empty and the full value
   133  // is returned as name
   134  func SplitHostname(named Named) (string, string) {
   135  	name := named.Name()
   136  	match := anchoredNameRegexp.FindStringSubmatch(name)
   137  	if match == nil || len(match) != 3 {
   138  		return "", name
   139  	}
   140  	return match[1], match[2]
   141  }
   142  
   143  // Parse parses s and returns a syntactically valid Reference.
   144  // If an error was encountered it is returned, along with a nil Reference.
   145  // NOTE: Parse will not handle short digests.
   146  func Parse(s string) (Reference, error) {
   147  	matches := ReferenceRegexp.FindStringSubmatch(s)
   148  	if matches == nil {
   149  		if s == "" {
   150  			return nil, ErrNameEmpty
   151  		}
   152  		// TODO(dmcgowan): Provide more specific and helpful error
   153  		return nil, ErrReferenceInvalidFormat
   154  	}
   155  
   156  	if len(matches[1]) > NameTotalLengthMax {
   157  		return nil, ErrNameTooLong
   158  	}
   159  
   160  	ref := reference{
   161  		name: matches[1],
   162  		tag:  matches[2],
   163  	}
   164  	if matches[3] != "" {
   165  		var err error
   166  		ref.digest, err = digest.ParseDigest(matches[3])
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  	}
   171  
   172  	r := getBestReferenceType(ref)
   173  	if r == nil {
   174  		return nil, ErrNameEmpty
   175  	}
   176  
   177  	return r, nil
   178  }
   179  
   180  // ParseNamed parses s and returns a syntactically valid reference implementing
   181  // the Named interface. The reference must have a name, otherwise an error is
   182  // returned.
   183  // If an error was encountered it is returned, along with a nil Reference.
   184  // NOTE: ParseNamed will not handle short digests.
   185  func ParseNamed(s string) (Named, error) {
   186  	ref, err := Parse(s)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	named, isNamed := ref.(Named)
   191  	if !isNamed {
   192  		return nil, fmt.Errorf("reference %s has no name", ref.String())
   193  	}
   194  	return named, nil
   195  }
   196  
   197  // WithName returns a named object representing the given string. If the input
   198  // is invalid ErrReferenceInvalidFormat will be returned.
   199  func WithName(name string) (Named, error) {
   200  	if len(name) > NameTotalLengthMax {
   201  		return nil, ErrNameTooLong
   202  	}
   203  	if !anchoredNameRegexp.MatchString(name) {
   204  		return nil, ErrReferenceInvalidFormat
   205  	}
   206  	return repository(name), nil
   207  }
   208  
   209  // WithTag combines the name from "name" and the tag from "tag" to form a
   210  // reference incorporating both the name and the tag.
   211  func WithTag(name Named, tag string) (NamedTagged, error) {
   212  	if !anchoredTagRegexp.MatchString(tag) {
   213  		return nil, ErrTagInvalidFormat
   214  	}
   215  	return taggedReference{
   216  		name: name.Name(),
   217  		tag:  tag,
   218  	}, nil
   219  }
   220  
   221  // WithDigest combines the name from "name" and the digest from "digest" to form
   222  // a reference incorporating both the name and the digest.
   223  func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
   224  	if !anchoredDigestRegexp.MatchString(digest.String()) {
   225  		return nil, ErrDigestInvalidFormat
   226  	}
   227  	return canonicalReference{
   228  		name:   name.Name(),
   229  		digest: digest,
   230  	}, nil
   231  }
   232  
   233  func getBestReferenceType(ref reference) Reference {
   234  	if ref.name == "" {
   235  		// Allow digest only references
   236  		if ref.digest != "" {
   237  			return digestReference(ref.digest)
   238  		}
   239  		return nil
   240  	}
   241  	if ref.tag == "" {
   242  		if ref.digest != "" {
   243  			return canonicalReference{
   244  				name:   ref.name,
   245  				digest: ref.digest,
   246  			}
   247  		}
   248  		return repository(ref.name)
   249  	}
   250  	if ref.digest == "" {
   251  		return taggedReference{
   252  			name: ref.name,
   253  			tag:  ref.tag,
   254  		}
   255  	}
   256  
   257  	return ref
   258  }
   259  
   260  type reference struct {
   261  	name   string
   262  	tag    string
   263  	digest digest.Digest
   264  }
   265  
   266  func (r reference) String() string {
   267  	return r.name + ":" + r.tag + "@" + r.digest.String()
   268  }
   269  
   270  func (r reference) Name() string {
   271  	return r.name
   272  }
   273  
   274  func (r reference) Tag() string {
   275  	return r.tag
   276  }
   277  
   278  func (r reference) Digest() digest.Digest {
   279  	return r.digest
   280  }
   281  
   282  type repository string
   283  
   284  func (r repository) String() string {
   285  	return string(r)
   286  }
   287  
   288  func (r repository) Name() string {
   289  	return string(r)
   290  }
   291  
   292  type digestReference digest.Digest
   293  
   294  func (d digestReference) String() string {
   295  	return d.String()
   296  }
   297  
   298  func (d digestReference) Digest() digest.Digest {
   299  	return digest.Digest(d)
   300  }
   301  
   302  type taggedReference struct {
   303  	name string
   304  	tag  string
   305  }
   306  
   307  func (t taggedReference) String() string {
   308  	return t.name + ":" + t.tag
   309  }
   310  
   311  func (t taggedReference) Name() string {
   312  	return t.name
   313  }
   314  
   315  func (t taggedReference) Tag() string {
   316  	return t.tag
   317  }
   318  
   319  type canonicalReference struct {
   320  	name   string
   321  	digest digest.Digest
   322  }
   323  
   324  func (c canonicalReference) String() string {
   325  	return c.name + "@" + c.digest.String()
   326  }
   327  
   328  func (c canonicalReference) Name() string {
   329  	return c.name
   330  }
   331  
   332  func (c canonicalReference) Digest() digest.Digest {
   333  	return c.digest
   334  }