github.com/olljanat/moby@v1.13.1/reference/reference.go (about)

     1  package reference
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/docker/distribution/digest"
     9  	distreference "github.com/docker/distribution/reference"
    10  	"github.com/docker/docker/image/v1"
    11  )
    12  
    13  const (
    14  	// DefaultTag defines the default tag used when performing images related actions and no tag or digest is specified
    15  	DefaultTag = "latest"
    16  	// DefaultHostname is the default built-in hostname
    17  	DefaultHostname = "docker.io"
    18  	// LegacyDefaultHostname is automatically converted to DefaultHostname
    19  	LegacyDefaultHostname = "index.docker.io"
    20  	// DefaultRepoPrefix is the prefix used for default repositories in default host
    21  	DefaultRepoPrefix = "library/"
    22  )
    23  
    24  // Named is an object with a full name
    25  type Named interface {
    26  	// Name returns normalized repository name, like "ubuntu".
    27  	Name() string
    28  	// String returns full reference, like "ubuntu@sha256:abcdef..."
    29  	String() string
    30  	// FullName returns full repository name with hostname, like "docker.io/library/ubuntu"
    31  	FullName() string
    32  	// Hostname returns hostname for the reference, like "docker.io"
    33  	Hostname() string
    34  	// RemoteName returns the repository component of the full name, like "library/ubuntu"
    35  	RemoteName() string
    36  }
    37  
    38  // NamedTagged is an object including a name and tag.
    39  type NamedTagged interface {
    40  	Named
    41  	Tag() string
    42  }
    43  
    44  // Canonical reference is an object with a fully unique
    45  // name including a name with hostname and digest
    46  type Canonical interface {
    47  	Named
    48  	Digest() digest.Digest
    49  }
    50  
    51  // ParseNamed parses s and returns a syntactically valid reference implementing
    52  // the Named interface. The reference must have a name, otherwise an error is
    53  // returned.
    54  // If an error was encountered it is returned, along with a nil Reference.
    55  func ParseNamed(s string) (Named, error) {
    56  	named, err := distreference.ParseNamed(s)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("Error parsing reference: %q is not a valid repository/tag: %s", s, err)
    59  	}
    60  	r, err := WithName(named.Name())
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	if canonical, isCanonical := named.(distreference.Canonical); isCanonical {
    65  		return WithDigest(r, canonical.Digest())
    66  	}
    67  	if tagged, isTagged := named.(distreference.NamedTagged); isTagged {
    68  		return WithTag(r, tagged.Tag())
    69  	}
    70  	return r, nil
    71  }
    72  
    73  // TrimNamed removes any tag or digest from the named reference
    74  func TrimNamed(ref Named) Named {
    75  	return &namedRef{distreference.TrimNamed(ref)}
    76  }
    77  
    78  // WithName returns a named object representing the given string. If the input
    79  // is invalid ErrReferenceInvalidFormat will be returned.
    80  func WithName(name string) (Named, error) {
    81  	name, err := normalize(name)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	if err := validateName(name); err != nil {
    86  		return nil, err
    87  	}
    88  	r, err := distreference.WithName(name)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return &namedRef{r}, nil
    93  }
    94  
    95  // WithTag combines the name from "name" and the tag from "tag" to form a
    96  // reference incorporating both the name and the tag.
    97  func WithTag(name Named, tag string) (NamedTagged, error) {
    98  	r, err := distreference.WithTag(name, tag)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return &taggedRef{namedRef{r}}, nil
   103  }
   104  
   105  // WithDigest combines the name from "name" and the digest from "digest" to form
   106  // a reference incorporating both the name and the digest.
   107  func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
   108  	r, err := distreference.WithDigest(name, digest)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	return &canonicalRef{namedRef{r}}, nil
   113  }
   114  
   115  type namedRef struct {
   116  	distreference.Named
   117  }
   118  type taggedRef struct {
   119  	namedRef
   120  }
   121  type canonicalRef struct {
   122  	namedRef
   123  }
   124  
   125  func (r *namedRef) FullName() string {
   126  	hostname, remoteName := splitHostname(r.Name())
   127  	return hostname + "/" + remoteName
   128  }
   129  func (r *namedRef) Hostname() string {
   130  	hostname, _ := splitHostname(r.Name())
   131  	return hostname
   132  }
   133  func (r *namedRef) RemoteName() string {
   134  	_, remoteName := splitHostname(r.Name())
   135  	return remoteName
   136  }
   137  func (r *taggedRef) Tag() string {
   138  	return r.namedRef.Named.(distreference.NamedTagged).Tag()
   139  }
   140  func (r *canonicalRef) Digest() digest.Digest {
   141  	return r.namedRef.Named.(distreference.Canonical).Digest()
   142  }
   143  
   144  // WithDefaultTag adds a default tag to a reference if it only has a repo name.
   145  func WithDefaultTag(ref Named) Named {
   146  	if IsNameOnly(ref) {
   147  		ref, _ = WithTag(ref, DefaultTag)
   148  	}
   149  	return ref
   150  }
   151  
   152  // IsNameOnly returns true if reference only contains a repo name.
   153  func IsNameOnly(ref Named) bool {
   154  	if _, ok := ref.(NamedTagged); ok {
   155  		return false
   156  	}
   157  	if _, ok := ref.(Canonical); ok {
   158  		return false
   159  	}
   160  	return true
   161  }
   162  
   163  // ParseIDOrReference parses string for an image ID or a reference. ID can be
   164  // without a default prefix.
   165  func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) {
   166  	if err := v1.ValidateID(idOrRef); err == nil {
   167  		idOrRef = "sha256:" + idOrRef
   168  	}
   169  	if dgst, err := digest.ParseDigest(idOrRef); err == nil {
   170  		return dgst, nil, nil
   171  	}
   172  	ref, err := ParseNamed(idOrRef)
   173  	return "", ref, err
   174  }
   175  
   176  // splitHostname splits a repository name to hostname and remotename string.
   177  // If no valid hostname is found, the default hostname is used. Repository name
   178  // needs to be already validated before.
   179  func splitHostname(name string) (hostname, remoteName string) {
   180  	i := strings.IndexRune(name, '/')
   181  	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
   182  		hostname, remoteName = DefaultHostname, name
   183  	} else {
   184  		hostname, remoteName = name[:i], name[i+1:]
   185  	}
   186  	if hostname == LegacyDefaultHostname {
   187  		hostname = DefaultHostname
   188  	}
   189  	if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') {
   190  		remoteName = DefaultRepoPrefix + remoteName
   191  	}
   192  	return
   193  }
   194  
   195  // normalize returns a repository name in its normalized form, meaning it
   196  // will not contain default hostname nor library/ prefix for official images.
   197  func normalize(name string) (string, error) {
   198  	host, remoteName := splitHostname(name)
   199  	if strings.ToLower(remoteName) != remoteName {
   200  		return "", errors.New("invalid reference format: repository name must be lowercase")
   201  	}
   202  	if host == DefaultHostname {
   203  		if strings.HasPrefix(remoteName, DefaultRepoPrefix) {
   204  			return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil
   205  		}
   206  		return remoteName, nil
   207  	}
   208  	return name, nil
   209  }
   210  
   211  func validateName(name string) error {
   212  	if err := v1.ValidateID(name); err == nil {
   213  		return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
   214  	}
   215  	return nil
   216  }