github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/reference/reference.go (about)

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