github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/reference/reference.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package reference 18 19 import ( 20 "errors" 21 "fmt" 22 "net/url" 23 "path" 24 "regexp" 25 "strings" 26 27 digest "github.com/opencontainers/go-digest" 28 ) 29 30 var ( 31 // ErrInvalid is returned when there is an invalid reference 32 ErrInvalid = errors.New("invalid reference") 33 // ErrObjectRequired is returned when the object is required 34 ErrObjectRequired = errors.New("object required") 35 // ErrHostnameRequired is returned when the hostname is required 36 ErrHostnameRequired = errors.New("hostname required") 37 ) 38 39 // Spec defines the main components of a reference specification. 40 // 41 // A reference specification is a schema-less URI parsed into common 42 // components. The two main components, locator and object, are required to be 43 // supported by remotes. It represents a superset of the naming define in 44 // docker's reference schema. It aims to be compatible but not prescriptive. 45 // 46 // While the interpretation of the components, locator and object, are up to 47 // the remote, we define a few common parts, accessible via helper methods. 48 // 49 // The first is the hostname, which is part of the locator. This doesn't need 50 // to map to a physical resource, but it must parse as a hostname. We refer to 51 // this as the namespace. 52 // 53 // The other component made accessible by helper method is the digest. This is 54 // part of the object identifier, always prefixed with an '@'. If present, the 55 // remote may use the digest portion directly or resolve it against a prefix. 56 // If the object does not include the `@` symbol, the return value for `Digest` 57 // will be empty. 58 type Spec struct { 59 // Locator is the host and path portion of the specification. The host 60 // portion may refer to an actual host or just a namespace of related 61 // images. 62 // 63 // Typically, the locator may used to resolve the remote to fetch specific 64 // resources. 65 Locator string 66 67 // Object contains the identifier for the remote resource. Classically, 68 // this is a tag but can refer to anything in a remote. By convention, any 69 // portion that may be a partial or whole digest will be preceded by an 70 // `@`. Anything preceding the `@` will be referred to as the "tag". 71 // 72 // In practice, we will see this broken down into the following formats: 73 // 74 // 1. <tag> 75 // 2. <tag>@<digest spec> 76 // 3. @<digest spec> 77 // 78 // We define the tag to be anything except '@' and ':'. <digest spec> may 79 // be a full valid digest or shortened version, possibly with elided 80 // algorithm. 81 Object string 82 } 83 84 var splitRe = regexp.MustCompile(`[:@]`) 85 86 // Parse parses the string into a structured ref. 87 func Parse(s string) (Spec, error) { 88 u, err := url.Parse("dummy://" + s) 89 if err != nil { 90 return Spec{}, err 91 } 92 93 if u.Scheme != "dummy" { 94 return Spec{}, ErrInvalid 95 } 96 97 if u.Host == "" { 98 return Spec{}, ErrHostnameRequired 99 } 100 101 var object string 102 103 if idx := splitRe.FindStringIndex(u.Path); idx != nil { 104 // This allows us to retain the @ to signify digests or shortened digests in 105 // the object. 106 object = u.Path[idx[0]:] 107 if object[:1] == ":" { 108 object = object[1:] 109 } 110 u.Path = u.Path[:idx[0]] 111 } 112 113 return Spec{ 114 Locator: path.Join(u.Host, u.Path), 115 Object: object, 116 }, nil 117 } 118 119 // Hostname returns the hostname portion of the locator. 120 // 121 // Remotes are not required to directly access the resources at this host. This 122 // method is provided for convenience. 123 func (r Spec) Hostname() string { 124 i := strings.Index(r.Locator, "/") 125 126 if i < 0 { 127 return r.Locator 128 } 129 return r.Locator[:i] 130 } 131 132 // Digest returns the digest portion of the reference spec. This may be a 133 // partial or invalid digest, which may be used to lookup a complete digest. 134 func (r Spec) Digest() digest.Digest { 135 _, dgst := SplitObject(r.Object) 136 return dgst 137 } 138 139 // String returns the normalized string for the ref. 140 func (r Spec) String() string { 141 if r.Object == "" { 142 return r.Locator 143 } 144 if r.Object[:1] == "@" { 145 return fmt.Sprintf("%v%v", r.Locator, r.Object) 146 } 147 148 return fmt.Sprintf("%v:%v", r.Locator, r.Object) 149 } 150 151 // SplitObject provides two parts of the object spec, delimited by an `@` 152 // symbol. 153 // 154 // Either may be empty and it is the callers job to validate them 155 // appropriately. 156 func SplitObject(obj string) (tag string, dgst digest.Digest) { 157 parts := strings.SplitAfterN(obj, "@", 2) 158 if len(parts) < 2 { 159 return parts[0], "" 160 } 161 return parts[0], digest.Digest(parts[1]) 162 }