gitee.com/bomy/docker.git@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 }