github.com/brahmaroutu/docker@v1.2.1-0.20160809185609-eb28dde01f16/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  // WithName returns a named object representing the given string. If the input
    74  // is invalid ErrReferenceInvalidFormat will be returned.
    75  func WithName(name string) (Named, error) {
    76  	name, err := normalize(name)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	if err := validateName(name); err != nil {
    81  		return nil, err
    82  	}
    83  	r, err := distreference.WithName(name)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return &namedRef{r}, nil
    88  }
    89  
    90  // WithTag combines the name from "name" and the tag from "tag" to form a
    91  // reference incorporating both the name and the tag.
    92  func WithTag(name Named, tag string) (NamedTagged, error) {
    93  	r, err := distreference.WithTag(name, tag)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return &taggedRef{namedRef{r}}, nil
    98  }
    99  
   100  // WithDigest combines the name from "name" and the digest from "digest" to form
   101  // a reference incorporating both the name and the digest.
   102  func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
   103  	r, err := distreference.WithDigest(name, digest)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return &canonicalRef{namedRef{r}}, nil
   108  }
   109  
   110  type namedRef struct {
   111  	distreference.Named
   112  }
   113  type taggedRef struct {
   114  	namedRef
   115  }
   116  type canonicalRef struct {
   117  	namedRef
   118  }
   119  
   120  func (r *namedRef) FullName() string {
   121  	hostname, remoteName := splitHostname(r.Name())
   122  	return hostname + "/" + remoteName
   123  }
   124  func (r *namedRef) Hostname() string {
   125  	hostname, _ := splitHostname(r.Name())
   126  	return hostname
   127  }
   128  func (r *namedRef) RemoteName() string {
   129  	_, remoteName := splitHostname(r.Name())
   130  	return remoteName
   131  }
   132  func (r *taggedRef) Tag() string {
   133  	return r.namedRef.Named.(distreference.NamedTagged).Tag()
   134  }
   135  func (r *canonicalRef) Digest() digest.Digest {
   136  	return r.namedRef.Named.(distreference.Canonical).Digest()
   137  }
   138  
   139  // WithDefaultTag adds a default tag to a reference if it only has a repo name.
   140  func WithDefaultTag(ref Named) Named {
   141  	if IsNameOnly(ref) {
   142  		ref, _ = WithTag(ref, DefaultTag)
   143  	}
   144  	return ref
   145  }
   146  
   147  // IsNameOnly returns true if reference only contains a repo name.
   148  func IsNameOnly(ref Named) bool {
   149  	if _, ok := ref.(NamedTagged); ok {
   150  		return false
   151  	}
   152  	if _, ok := ref.(Canonical); ok {
   153  		return false
   154  	}
   155  	return true
   156  }
   157  
   158  // ParseIDOrReference parses string for an image ID or a reference. ID can be
   159  // without a default prefix.
   160  func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) {
   161  	if err := v1.ValidateID(idOrRef); err == nil {
   162  		idOrRef = "sha256:" + idOrRef
   163  	}
   164  	if dgst, err := digest.ParseDigest(idOrRef); err == nil {
   165  		return dgst, nil, nil
   166  	}
   167  	ref, err := ParseNamed(idOrRef)
   168  	return "", ref, err
   169  }
   170  
   171  // splitHostname splits a repository name to hostname and remotename string.
   172  // If no valid hostname is found, the default hostname is used. Repository name
   173  // needs to be already validated before.
   174  func splitHostname(name string) (hostname, remoteName string) {
   175  	i := strings.IndexRune(name, '/')
   176  	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
   177  		hostname, remoteName = DefaultHostname, name
   178  	} else {
   179  		hostname, remoteName = name[:i], name[i+1:]
   180  	}
   181  	if hostname == LegacyDefaultHostname {
   182  		hostname = DefaultHostname
   183  	}
   184  	if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') {
   185  		remoteName = DefaultRepoPrefix + remoteName
   186  	}
   187  	return
   188  }
   189  
   190  // normalize returns a repository name in its normalized form, meaning it
   191  // will not contain default hostname nor library/ prefix for official images.
   192  func normalize(name string) (string, error) {
   193  	host, remoteName := splitHostname(name)
   194  	if strings.ToLower(remoteName) != remoteName {
   195  		return "", errors.New("invalid reference format: repository name must be lowercase")
   196  	}
   197  	if host == DefaultHostname {
   198  		if strings.HasPrefix(remoteName, DefaultRepoPrefix) {
   199  			return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil
   200  		}
   201  		return remoteName, nil
   202  	}
   203  	return name, nil
   204  }
   205  
   206  func validateName(name string) error {
   207  	if err := v1.ValidateID(name); err == nil {
   208  		return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
   209  	}
   210  	return nil
   211  }