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 }