// Package reference provides a general type to represent any way of referencing images within the registry. // Its main purpose is to abstract tags and digests (content-addressable hash). // // Grammar // // reference := repository [ ":" tag ] [ "@" digest ] // // // repository.go // repository := hostname ['/' component]+ // hostname := hostcomponent [':' port-number] // component := alpha-numeric [separator alpha-numeric]* // hostcomponent := [hostpart '.']* hostpart // alpha-numeric := /[a-zA-Z0-9]+/ // separator := /[_-]/ // port-number := /[0-9]+/ // hostpart := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ // // // tag.go // tag := /[\w][\w.-]{0,127}/ // // // from the digest package // digest := digest-algorithm ":" digest-hex // digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ] // digest-algorithm-separator := /[+.-_]/ // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ // digest-hex := /[0-9a-fA-F]{32,}/ ; Atleast 128 bit digest value package reference import ( "errors" "fmt" "github.com/docker/distribution/digest" ) const ( // NameTotalLengthMax is the maximum total number of characters in a repository name. NameTotalLengthMax = 255 ) var ( // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. ErrReferenceInvalidFormat = errors.New("invalid reference format") // ErrNameEmpty is returned for empty, invalid repository names. ErrNameEmpty = errors.New("repository name must have at least one component") // ErrNameTooLong is returned when a repository name is longer than // RepositoryNameTotalLengthMax ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) ) // 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 } // Named is an object with a full name type Named interface { Name() string } // Tagged is an object which has a tag type Tagged interface { Tag() string } // Digested is an object which has a digest // in which it can be referenced by type Digested interface { Digest() digest.Digest } // Canonical reference is an object with a fully unique // name including a name with hostname and digest type Canonical interface { Reference Named Digested } // SplitHostname splits a named reference into a // hostname and name string. If no valid hostname is // found, the hostname is empty and the full value // is returned as name func SplitHostname(named Named) (string, string) { name := named.Name() match := anchoredNameRegexp.FindStringSubmatch(name) if match == nil || len(match) != 3 { return "", name } return match[1], match[2] } // Parse parses s and returns a syntactically valid Reference. // If an error was encountered it is returned, along with a nil Reference. // NOTE: Parse will not handle short digests. func Parse(s string) (Reference, error) { matches := ReferenceRegexp.FindStringSubmatch(s) if matches == nil { if s == "" { return nil, ErrNameEmpty } // TODO(dmcgowan): Provide more specific and helpful error return nil, ErrReferenceInvalidFormat } if len(matches[1]) > NameTotalLengthMax { return nil, ErrNameTooLong } ref := reference{ name: matches[1], tag: matches[2], } if matches[3] != "" { var err error ref.digest, err = digest.ParseDigest(matches[3]) if err != nil { return nil, err } } r := getBestReferenceType(ref) if r == nil { return nil, ErrNameEmpty } return r, nil } // ParseNamed parses the input string and returns a named // object representing the given string. If the input is // invalid ErrReferenceInvalidFormat will be returned. func ParseNamed(name string) (Named, error) { if !anchoredNameRegexp.MatchString(name) { return nil, ErrReferenceInvalidFormat } return repository(name), nil } func getBestReferenceType(ref reference) Reference { if ref.name == "" { // Allow digest only references if ref.digest != "" { return digestReference(ref.digest) } return nil } if ref.tag == "" { if ref.digest != "" { return canonicalReference{ name: ref.name, digest: ref.digest, } } return repository(ref.name) } if ref.digest == "" { return taggedReference{ name: ref.name, tag: ref.tag, } } return ref } type reference struct { name string tag string digest digest.Digest } func (r reference) String() string { return r.name + ":" + r.tag + "@" + r.digest.String() } func (r reference) Name() string { return r.name } func (r reference) Tag() string { return r.tag } func (r reference) Digest() digest.Digest { return r.digest } type repository string func (r repository) String() string { return string(r) } func (r repository) Name() string { return string(r) } type digestReference digest.Digest func (d digestReference) String() string { return d.String() } func (d digestReference) Digest() digest.Digest { return digest.Digest(d) } type taggedReference struct { name string tag string } func (t taggedReference) String() string { return t.name + ":" + t.tag } func (t taggedReference) Name() string { return t.name } func (t taggedReference) Tag() string { return t.tag } type canonicalReference struct { name string digest digest.Digest } func (c canonicalReference) String() string { return c.name + "@" + c.digest.String() } func (c canonicalReference) Name() string { return c.name } func (c canonicalReference) Digest() digest.Digest { return c.digest }