d325c8b1d5
Signed-off-by: Stephen J Day <stephen.day@docker.com>
141 lines
4 KiB
Go
141 lines
4 KiB
Go
package reference
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
|
|
digest "github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
var (
|
|
ErrInvalid = errors.New("invalid reference")
|
|
ErrObjectRequired = errors.New("object required")
|
|
ErrHostnameRequired = errors.New("hostname required")
|
|
)
|
|
|
|
// Spec defines the main components of a reference specification.
|
|
//
|
|
// A reference specification is a schema-less URI parsed into common
|
|
// components. The two main components, locator and object, are required to be
|
|
// supported by remotes. It represents a superset of the naming define in
|
|
// docker's reference schema. It aims to be compatible but not prescriptive.
|
|
//
|
|
// While the interpretation of the components, locator and object, are up to
|
|
// the remote, we define a few common parts, accessible via helper methods.
|
|
//
|
|
// The first is the hostname, which is part of the locator. This doesn't need
|
|
// to map to a physical resource, but it must parse as a hostname. We refer to
|
|
// this as the namespace.
|
|
//
|
|
// The other component made accessible by helper method is the digest. This is
|
|
// part of the object identifier, always prefixed with an '@'. If present, the
|
|
// remote may use the digest portion directly or resolve it against a prefix.
|
|
// If the object does not include the `@` symbol, the return value for `Digest`
|
|
// will be empty.
|
|
type Spec struct {
|
|
// Locator is the host and path portion of the specification. The host
|
|
// portion may refer to an actual host or just a namespace of related
|
|
// images.
|
|
//
|
|
// Typically, the locator may used to resolve the remote to fetch specific
|
|
// resources.
|
|
Locator string
|
|
|
|
// Object contains the identifier for the remote resource. Classically,
|
|
// this is a tag but can refer to anything in a remote. By convention, any
|
|
// portion that may be a partial or whole digest will be preceeded by an
|
|
// `@`. Anything preceeding the `@` will be referred to as the "tag".
|
|
//
|
|
// In practice, we will see this broken down into the following formats:
|
|
//
|
|
// 1. <tag>
|
|
// 2. <tag>@<digest spec>
|
|
// 3. @<digest spec>
|
|
//
|
|
// We define the tag to be anything except '@' and ':'. <digest spec> may
|
|
// be a full valid digest or shortened version, possibly with elided
|
|
// algorithm.
|
|
Object string
|
|
}
|
|
|
|
var splitRe = regexp.MustCompile(`[:@]`)
|
|
|
|
// Parse parses the string into a structured ref.
|
|
func Parse(s string) (Spec, error) {
|
|
u, err := url.Parse("dummy://" + s)
|
|
if err != nil {
|
|
return Spec{}, err
|
|
}
|
|
|
|
if u.Scheme != "dummy" {
|
|
return Spec{}, ErrInvalid
|
|
}
|
|
|
|
if u.Host == "" {
|
|
return Spec{}, ErrHostnameRequired
|
|
}
|
|
|
|
parts := splitRe.Split(u.Path, 2)
|
|
if len(parts) < 2 {
|
|
return Spec{}, ErrObjectRequired
|
|
}
|
|
|
|
// This allows us to retain the @ to signify digests or shortend digests in
|
|
// the object.
|
|
object := u.Path[len(parts[0]):]
|
|
if object[:1] == ":" {
|
|
object = object[1:]
|
|
}
|
|
|
|
return Spec{
|
|
Locator: path.Join(u.Host, parts[0]),
|
|
Object: object,
|
|
}, nil
|
|
}
|
|
|
|
// Hostname returns the hostname portion of the locator.
|
|
//
|
|
// Remotes are not required to directly access the resources at this host. This
|
|
// method is provided for convenience.
|
|
func (r Spec) Hostname() string {
|
|
i := strings.Index(r.Locator, "/")
|
|
|
|
if i < 0 {
|
|
i = len(r.Locator) + 1
|
|
}
|
|
return r.Locator[:i]
|
|
}
|
|
|
|
// Digest returns the digest portion of the reference spec. This may be a
|
|
// partial or invalid digest, which may be used to lookup a complete digest.
|
|
func (r Spec) Digest() digest.Digest {
|
|
_, dgst := SplitObject(r.Object)
|
|
return dgst
|
|
}
|
|
|
|
// String returns the normalized string for the ref.
|
|
func (r Spec) String() string {
|
|
if r.Object[:1] == "@" {
|
|
return fmt.Sprintf("%v%v", r.Locator, r.Object)
|
|
}
|
|
|
|
return fmt.Sprintf("%v:%v", r.Locator, r.Object)
|
|
}
|
|
|
|
// SplitObject provides two parts of the object spec, delimiited by an `@`
|
|
// symbol.
|
|
//
|
|
// Either may be empty and it is the callers job to validate them
|
|
// appropriately.
|
|
func SplitObject(obj string) (tag string, dgst digest.Digest) {
|
|
parts := strings.SplitAfterN(obj, "@", 2)
|
|
if len(parts) < 2 {
|
|
return parts[0], ""
|
|
} else {
|
|
return parts[0], digest.Digest(parts[1])
|
|
}
|
|
}
|