github.com/dougm/docker@v1.5.0/registry/v2/urls.go (about) 1 package v2 2 3 import ( 4 "net/http" 5 "net/url" 6 7 "github.com/gorilla/mux" 8 ) 9 10 // URLBuilder creates registry API urls from a single base endpoint. It can be 11 // used to create urls for use in a registry client or server. 12 // 13 // All urls will be created from the given base, including the api version. 14 // For example, if a root of "/foo/" is provided, urls generated will be fall 15 // under "/foo/v2/...". Most application will only provide a schema, host and 16 // port, such as "https://localhost:5000/". 17 type URLBuilder struct { 18 root *url.URL // url root (ie http://localhost/) 19 router *mux.Router 20 } 21 22 // NewURLBuilder creates a URLBuilder with provided root url object. 23 func NewURLBuilder(root *url.URL) *URLBuilder { 24 return &URLBuilder{ 25 root: root, 26 router: Router(), 27 } 28 } 29 30 // NewURLBuilderFromString workes identically to NewURLBuilder except it takes 31 // a string argument for the root, returning an error if it is not a valid 32 // url. 33 func NewURLBuilderFromString(root string) (*URLBuilder, error) { 34 u, err := url.Parse(root) 35 if err != nil { 36 return nil, err 37 } 38 39 return NewURLBuilder(u), nil 40 } 41 42 // NewURLBuilderFromRequest uses information from an *http.Request to 43 // construct the root url. 44 func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { 45 u := &url.URL{ 46 Scheme: r.URL.Scheme, 47 Host: r.Host, 48 } 49 50 return NewURLBuilder(u) 51 } 52 53 // BuildBaseURL constructs a base url for the API, typically just "/v2/". 54 func (ub *URLBuilder) BuildBaseURL() (string, error) { 55 route := ub.cloneRoute(RouteNameBase) 56 57 baseURL, err := route.URL() 58 if err != nil { 59 return "", err 60 } 61 62 return baseURL.String(), nil 63 } 64 65 // BuildTagsURL constructs a url to list the tags in the named repository. 66 func (ub *URLBuilder) BuildTagsURL(name string) (string, error) { 67 route := ub.cloneRoute(RouteNameTags) 68 69 tagsURL, err := route.URL("name", name) 70 if err != nil { 71 return "", err 72 } 73 74 return tagsURL.String(), nil 75 } 76 77 // BuildManifestURL constructs a url for the manifest identified by name and tag. 78 func (ub *URLBuilder) BuildManifestURL(name, tag string) (string, error) { 79 route := ub.cloneRoute(RouteNameManifest) 80 81 manifestURL, err := route.URL("name", name, "tag", tag) 82 if err != nil { 83 return "", err 84 } 85 86 return manifestURL.String(), nil 87 } 88 89 // BuildBlobURL constructs the url for the blob identified by name and dgst. 90 func (ub *URLBuilder) BuildBlobURL(name string, dgst string) (string, error) { 91 route := ub.cloneRoute(RouteNameBlob) 92 93 layerURL, err := route.URL("name", name, "digest", dgst) 94 if err != nil { 95 return "", err 96 } 97 98 return layerURL.String(), nil 99 } 100 101 // BuildBlobUploadURL constructs a url to begin a blob upload in the 102 // repository identified by name. 103 func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) { 104 route := ub.cloneRoute(RouteNameBlobUpload) 105 106 uploadURL, err := route.URL("name", name) 107 if err != nil { 108 return "", err 109 } 110 111 return appendValuesURL(uploadURL, values...).String(), nil 112 } 113 114 // BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, 115 // including any url values. This should generally not be used by clients, as 116 // this url is provided by server implementations during the blob upload 117 // process. 118 func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) { 119 route := ub.cloneRoute(RouteNameBlobUploadChunk) 120 121 uploadURL, err := route.URL("name", name, "uuid", uuid) 122 if err != nil { 123 return "", err 124 } 125 126 return appendValuesURL(uploadURL, values...).String(), nil 127 } 128 129 // clondedRoute returns a clone of the named route from the router. Routes 130 // must be cloned to avoid modifying them during url generation. 131 func (ub *URLBuilder) cloneRoute(name string) clonedRoute { 132 route := new(mux.Route) 133 root := new(url.URL) 134 135 *route = *ub.router.GetRoute(name) // clone the route 136 *root = *ub.root 137 138 return clonedRoute{Route: route, root: root} 139 } 140 141 type clonedRoute struct { 142 *mux.Route 143 root *url.URL 144 } 145 146 func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { 147 routeURL, err := cr.Route.URL(pairs...) 148 if err != nil { 149 return nil, err 150 } 151 152 return cr.root.ResolveReference(routeURL), nil 153 } 154 155 // appendValuesURL appends the parameters to the url. 156 func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { 157 merged := u.Query() 158 159 for _, v := range values { 160 for k, vv := range v { 161 merged[k] = append(merged[k], vv...) 162 } 163 } 164 165 u.RawQuery = merged.Encode() 166 return u 167 } 168 169 // appendValues appends the parameters to the url. Panics if the string is not 170 // a url. 171 func appendValues(u string, values ...url.Values) string { 172 up, err := url.Parse(u) 173 174 if err != nil { 175 panic(err) // should never happen 176 } 177 178 return appendValuesURL(up, values...).String() 179 }