github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/reference/reference.go (about) 1 // Package reference provides a general type to represent any way of referencing images within the registry. 2 // Its main purpose is to abstract tags and digests (content-addressable hash). 3 // 4 // Grammar 5 // 6 // reference := repository [ ":" tag ] [ "@" digest ] 7 // name := [hostname '/'] component ['/' component]* 8 // hostname := hostcomponent ['.' hostcomponent]* [':' port-number] 9 // hostcomponent := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/ 10 // port-number := /[0-9]+/ 11 // component := alpha-numeric [separator alpha-numeric]* 12 // alpha-numeric := /[a-z0-9]+/ 13 // separator := /[_.]|__|[-]*/ 14 // 15 // tag := /[\w][\w.-]{0,127}/ 16 // 17 // digest := digest-algorithm ":" digest-hex 18 // digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ] 19 // digest-algorithm-separator := /[+.-_]/ 20 // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ 21 // digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value 22 package reference 23 24 import ( 25 "errors" 26 "fmt" 27 28 "github.com/docker/distribution/digest" 29 ) 30 31 const ( 32 // NameTotalLengthMax is the maximum total number of characters in a repository name. 33 NameTotalLengthMax = 255 34 ) 35 36 var ( 37 // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. 38 ErrReferenceInvalidFormat = errors.New("invalid reference format") 39 40 // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. 41 ErrTagInvalidFormat = errors.New("invalid tag format") 42 43 // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. 44 ErrDigestInvalidFormat = errors.New("invalid digest format") 45 46 // ErrNameEmpty is returned for empty, invalid repository names. 47 ErrNameEmpty = errors.New("repository name must have at least one component") 48 49 // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. 50 ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) 51 ) 52 53 // Reference is an opaque object reference identifier that may include 54 // modifiers such as a hostname, name, tag, and digest. 55 type Reference interface { 56 // String returns the full reference 57 String() string 58 } 59 60 // Field provides a wrapper type for resolving correct reference types when 61 // working with encoding. 62 type Field struct { 63 reference Reference 64 } 65 66 // AsField wraps a reference in a Field for encoding. 67 func AsField(reference Reference) Field { 68 return Field{reference} 69 } 70 71 // Reference unwraps the reference type from the field to 72 // return the Reference object. This object should be 73 // of the appropriate type to further check for different 74 // reference types. 75 func (f Field) Reference() Reference { 76 return f.reference 77 } 78 79 // MarshalText serializes the field to byte text which 80 // is the string of the reference. 81 func (f Field) MarshalText() (p []byte, err error) { 82 return []byte(f.reference.String()), nil 83 } 84 85 // UnmarshalText parses text bytes by invoking the 86 // reference parser to ensure the appropriately 87 // typed reference object is wrapped by field. 88 func (f *Field) UnmarshalText(p []byte) error { 89 r, err := Parse(string(p)) 90 if err != nil { 91 return err 92 } 93 94 f.reference = r 95 return nil 96 } 97 98 // Named is an object with a full name 99 type Named interface { 100 Reference 101 Name() string 102 } 103 104 // Tagged is an object which has a tag 105 type Tagged interface { 106 Reference 107 Tag() string 108 } 109 110 // NamedTagged is an object including a name and tag. 111 type NamedTagged interface { 112 Named 113 Tag() string 114 } 115 116 // Digested is an object which has a digest 117 // in which it can be referenced by 118 type Digested interface { 119 Reference 120 Digest() digest.Digest 121 } 122 123 // Canonical reference is an object with a fully unique 124 // name including a name with hostname and digest 125 type Canonical interface { 126 Named 127 Digest() digest.Digest 128 } 129 130 // SplitHostname splits a named reference into a 131 // hostname and name string. If no valid hostname is 132 // found, the hostname is empty and the full value 133 // is returned as name 134 func SplitHostname(named Named) (string, string) { 135 name := named.Name() 136 match := anchoredNameRegexp.FindStringSubmatch(name) 137 if match == nil || len(match) != 3 { 138 return "", name 139 } 140 return match[1], match[2] 141 } 142 143 // Parse parses s and returns a syntactically valid Reference. 144 // If an error was encountered it is returned, along with a nil Reference. 145 // NOTE: Parse will not handle short digests. 146 func Parse(s string) (Reference, error) { 147 matches := ReferenceRegexp.FindStringSubmatch(s) 148 if matches == nil { 149 if s == "" { 150 return nil, ErrNameEmpty 151 } 152 // TODO(dmcgowan): Provide more specific and helpful error 153 return nil, ErrReferenceInvalidFormat 154 } 155 156 if len(matches[1]) > NameTotalLengthMax { 157 return nil, ErrNameTooLong 158 } 159 160 ref := reference{ 161 name: matches[1], 162 tag: matches[2], 163 } 164 if matches[3] != "" { 165 var err error 166 ref.digest, err = digest.ParseDigest(matches[3]) 167 if err != nil { 168 return nil, err 169 } 170 } 171 172 r := getBestReferenceType(ref) 173 if r == nil { 174 return nil, ErrNameEmpty 175 } 176 177 return r, nil 178 } 179 180 // ParseNamed parses s and returns a syntactically valid reference implementing 181 // the Named interface. The reference must have a name, otherwise an error is 182 // returned. 183 // If an error was encountered it is returned, along with a nil Reference. 184 // NOTE: ParseNamed will not handle short digests. 185 func ParseNamed(s string) (Named, error) { 186 ref, err := Parse(s) 187 if err != nil { 188 return nil, err 189 } 190 named, isNamed := ref.(Named) 191 if !isNamed { 192 return nil, fmt.Errorf("reference %s has no name", ref.String()) 193 } 194 return named, nil 195 } 196 197 // WithName returns a named object representing the given string. If the input 198 // is invalid ErrReferenceInvalidFormat will be returned. 199 func WithName(name string) (Named, error) { 200 if len(name) > NameTotalLengthMax { 201 return nil, ErrNameTooLong 202 } 203 if !anchoredNameRegexp.MatchString(name) { 204 return nil, ErrReferenceInvalidFormat 205 } 206 return repository(name), nil 207 } 208 209 // WithTag combines the name from "name" and the tag from "tag" to form a 210 // reference incorporating both the name and the tag. 211 func WithTag(name Named, tag string) (NamedTagged, error) { 212 if !anchoredTagRegexp.MatchString(tag) { 213 return nil, ErrTagInvalidFormat 214 } 215 return taggedReference{ 216 name: name.Name(), 217 tag: tag, 218 }, nil 219 } 220 221 // WithDigest combines the name from "name" and the digest from "digest" to form 222 // a reference incorporating both the name and the digest. 223 func WithDigest(name Named, digest digest.Digest) (Canonical, error) { 224 if !anchoredDigestRegexp.MatchString(digest.String()) { 225 return nil, ErrDigestInvalidFormat 226 } 227 return canonicalReference{ 228 name: name.Name(), 229 digest: digest, 230 }, nil 231 } 232 233 func getBestReferenceType(ref reference) Reference { 234 if ref.name == "" { 235 // Allow digest only references 236 if ref.digest != "" { 237 return digestReference(ref.digest) 238 } 239 return nil 240 } 241 if ref.tag == "" { 242 if ref.digest != "" { 243 return canonicalReference{ 244 name: ref.name, 245 digest: ref.digest, 246 } 247 } 248 return repository(ref.name) 249 } 250 if ref.digest == "" { 251 return taggedReference{ 252 name: ref.name, 253 tag: ref.tag, 254 } 255 } 256 257 return ref 258 } 259 260 type reference struct { 261 name string 262 tag string 263 digest digest.Digest 264 } 265 266 func (r reference) String() string { 267 return r.name + ":" + r.tag + "@" + r.digest.String() 268 } 269 270 func (r reference) Name() string { 271 return r.name 272 } 273 274 func (r reference) Tag() string { 275 return r.tag 276 } 277 278 func (r reference) Digest() digest.Digest { 279 return r.digest 280 } 281 282 type repository string 283 284 func (r repository) String() string { 285 return string(r) 286 } 287 288 func (r repository) Name() string { 289 return string(r) 290 } 291 292 type digestReference digest.Digest 293 294 func (d digestReference) String() string { 295 return d.String() 296 } 297 298 func (d digestReference) Digest() digest.Digest { 299 return digest.Digest(d) 300 } 301 302 type taggedReference struct { 303 name string 304 tag string 305 } 306 307 func (t taggedReference) String() string { 308 return t.name + ":" + t.tag 309 } 310 311 func (t taggedReference) Name() string { 312 return t.name 313 } 314 315 func (t taggedReference) Tag() string { 316 return t.tag 317 } 318 319 type canonicalReference struct { 320 name string 321 digest digest.Digest 322 } 323 324 func (c canonicalReference) String() string { 325 return c.name + "@" + c.digest.String() 326 } 327 328 func (c canonicalReference) Name() string { 329 return c.name 330 } 331 332 func (c canonicalReference) Digest() digest.Digest { 333 return c.digest 334 }