github.com/eljojo/docker@v1.6.0-rc4/registry/session_v2.go (about) 1 package registry 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "strconv" 11 12 log "github.com/Sirupsen/logrus" 13 "github.com/docker/distribution/digest" 14 "github.com/docker/docker/registry/v2" 15 "github.com/docker/docker/utils" 16 ) 17 18 const DockerDigestHeader = "Docker-Content-Digest" 19 20 func getV2Builder(e *Endpoint) *v2.URLBuilder { 21 if e.URLBuilder == nil { 22 e.URLBuilder = v2.NewURLBuilder(e.URL) 23 } 24 return e.URLBuilder 25 } 26 27 func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) { 28 // TODO check if should use Mirror 29 if index.Official { 30 ep, err = newEndpoint(REGISTRYSERVER, true) 31 if err != nil { 32 return 33 } 34 err = validateEndpoint(ep) 35 if err != nil { 36 return 37 } 38 } else if r.indexEndpoint.String() == index.GetAuthConfigKey() { 39 ep = r.indexEndpoint 40 } else { 41 ep, err = NewEndpoint(index) 42 if err != nil { 43 return 44 } 45 } 46 47 ep.URLBuilder = v2.NewURLBuilder(ep.URL) 48 return 49 } 50 51 // GetV2Authorization gets the authorization needed to the given image 52 // If readonly access is requested, then only the authorization may 53 // only be used for Get operations. 54 func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bool) (auth *RequestAuthorization, err error) { 55 scopes := []string{"pull"} 56 if !readOnly { 57 scopes = append(scopes, "push") 58 } 59 60 log.Debugf("Getting authorization for %s %s", imageName, scopes) 61 return NewRequestAuthorization(r.GetAuthConfig(true), ep, "repository", imageName, scopes), nil 62 } 63 64 // 65 // 1) Check if TarSum of each layer exists /v2/ 66 // 1.a) if 200, continue 67 // 1.b) if 300, then push the 68 // 1.c) if anything else, err 69 // 2) PUT the created/signed manifest 70 // 71 func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, string, error) { 72 routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) 73 if err != nil { 74 return nil, "", err 75 } 76 77 method := "GET" 78 log.Debugf("[registry] Calling %q %s", method, routeURL) 79 80 req, err := r.reqFactory.NewRequest(method, routeURL, nil) 81 if err != nil { 82 return nil, "", err 83 } 84 if err := auth.Authorize(req); err != nil { 85 return nil, "", err 86 } 87 res, _, err := r.doRequest(req) 88 if err != nil { 89 return nil, "", err 90 } 91 defer res.Body.Close() 92 if res.StatusCode != 200 { 93 if res.StatusCode == 401 { 94 return nil, "", errLoginRequired 95 } else if res.StatusCode == 404 { 96 return nil, "", ErrDoesNotExist 97 } 98 return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) 99 } 100 101 manifestBytes, err := ioutil.ReadAll(res.Body) 102 if err != nil { 103 return nil, "", fmt.Errorf("Error while reading the http response: %s", err) 104 } 105 106 return manifestBytes, res.Header.Get(DockerDigestHeader), nil 107 } 108 109 // - Succeeded to head image blob (already exists) 110 // - Failed with no error (continue to Push the Blob) 111 // - Failed with error 112 func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (bool, error) { 113 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) 114 if err != nil { 115 return false, err 116 } 117 118 method := "HEAD" 119 log.Debugf("[registry] Calling %q %s", method, routeURL) 120 121 req, err := r.reqFactory.NewRequest(method, routeURL, nil) 122 if err != nil { 123 return false, err 124 } 125 if err := auth.Authorize(req); err != nil { 126 return false, err 127 } 128 res, _, err := r.doRequest(req) 129 if err != nil { 130 return false, err 131 } 132 res.Body.Close() // close early, since we're not needing a body on this call .. yet? 133 switch { 134 case res.StatusCode >= 200 && res.StatusCode < 400: 135 // return something indicating no push needed 136 return true, nil 137 case res.StatusCode == 401: 138 return false, errLoginRequired 139 case res.StatusCode == 404: 140 // return something indicating blob push needed 141 return false, nil 142 } 143 144 return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s:%s", res.StatusCode, imageName, sumType, sum), res) 145 } 146 147 func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, blobWrtr io.Writer, auth *RequestAuthorization) error { 148 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) 149 if err != nil { 150 return err 151 } 152 153 method := "GET" 154 log.Debugf("[registry] Calling %q %s", method, routeURL) 155 req, err := r.reqFactory.NewRequest(method, routeURL, nil) 156 if err != nil { 157 return err 158 } 159 if err := auth.Authorize(req); err != nil { 160 return err 161 } 162 res, _, err := r.doRequest(req) 163 if err != nil { 164 return err 165 } 166 defer res.Body.Close() 167 if res.StatusCode != 200 { 168 if res.StatusCode == 401 { 169 return errLoginRequired 170 } 171 return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) 172 } 173 174 _, err = io.Copy(blobWrtr, res.Body) 175 return err 176 } 177 178 func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (io.ReadCloser, int64, error) { 179 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) 180 if err != nil { 181 return nil, 0, err 182 } 183 184 method := "GET" 185 log.Debugf("[registry] Calling %q %s", method, routeURL) 186 req, err := r.reqFactory.NewRequest(method, routeURL, nil) 187 if err != nil { 188 return nil, 0, err 189 } 190 if err := auth.Authorize(req); err != nil { 191 return nil, 0, err 192 } 193 res, _, err := r.doRequest(req) 194 if err != nil { 195 return nil, 0, err 196 } 197 if res.StatusCode != 200 { 198 if res.StatusCode == 401 { 199 return nil, 0, errLoginRequired 200 } 201 return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s:%s", res.StatusCode, imageName, sumType, sum), res) 202 } 203 lenStr := res.Header.Get("Content-Length") 204 l, err := strconv.ParseInt(lenStr, 10, 64) 205 if err != nil { 206 return nil, 0, err 207 } 208 209 return res.Body, l, err 210 } 211 212 // Push the image to the server for storage. 213 // 'layer' is an uncompressed reader of the blob to be pushed. 214 // The server will generate it's own checksum calculation. 215 func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string, blobRdr io.Reader, auth *RequestAuthorization) error { 216 location, err := r.initiateBlobUpload(ep, imageName, auth) 217 if err != nil { 218 return err 219 } 220 221 method := "PUT" 222 log.Debugf("[registry] Calling %q %s", method, location) 223 req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr)) 224 if err != nil { 225 return err 226 } 227 queryParams := req.URL.Query() 228 queryParams.Add("digest", sumType+":"+sumStr) 229 req.URL.RawQuery = queryParams.Encode() 230 if err := auth.Authorize(req); err != nil { 231 return err 232 } 233 res, _, err := r.doRequest(req) 234 if err != nil { 235 return err 236 } 237 defer res.Body.Close() 238 239 if res.StatusCode != 201 { 240 if res.StatusCode == 401 { 241 return errLoginRequired 242 } 243 errBody, err := ioutil.ReadAll(res.Body) 244 if err != nil { 245 return err 246 } 247 log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 248 return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res) 249 } 250 251 return nil 252 } 253 254 // initiateBlobUpload gets the blob upload location for the given image name. 255 func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *RequestAuthorization) (location string, err error) { 256 routeURL, err := getV2Builder(ep).BuildBlobUploadURL(imageName) 257 if err != nil { 258 return "", err 259 } 260 261 log.Debugf("[registry] Calling %q %s", "POST", routeURL) 262 req, err := r.reqFactory.NewRequest("POST", routeURL, nil) 263 if err != nil { 264 return "", err 265 } 266 267 if err := auth.Authorize(req); err != nil { 268 return "", err 269 } 270 res, _, err := r.doRequest(req) 271 if err != nil { 272 return "", err 273 } 274 275 if res.StatusCode != http.StatusAccepted { 276 if res.StatusCode == http.StatusUnauthorized { 277 return "", errLoginRequired 278 } 279 if res.StatusCode == http.StatusNotFound { 280 return "", ErrDoesNotExist 281 } 282 283 errBody, err := ioutil.ReadAll(res.Body) 284 if err != nil { 285 return "", err 286 } 287 288 log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 289 return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) 290 } 291 292 if location = res.Header.Get("Location"); location == "" { 293 return "", fmt.Errorf("registry did not return a Location header for resumable blob upload for image %s", imageName) 294 } 295 296 return 297 } 298 299 // Finally Push the (signed) manifest of the blobs we've just pushed 300 func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) { 301 routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) 302 if err != nil { 303 return "", err 304 } 305 306 method := "PUT" 307 log.Debugf("[registry] Calling %q %s", method, routeURL) 308 req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest)) 309 if err != nil { 310 return "", err 311 } 312 if err := auth.Authorize(req); err != nil { 313 return "", err 314 } 315 res, _, err := r.doRequest(req) 316 if err != nil { 317 return "", err 318 } 319 defer res.Body.Close() 320 321 // All 2xx and 3xx responses can be accepted for a put. 322 if res.StatusCode >= 400 { 323 if res.StatusCode == 401 { 324 return "", errLoginRequired 325 } 326 errBody, err := ioutil.ReadAll(res.Body) 327 if err != nil { 328 return "", err 329 } 330 log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 331 return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) 332 } 333 334 hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) 335 if err != nil { 336 return "", fmt.Errorf("invalid manifest digest from registry: %s", err) 337 } 338 339 dgstVerifier, err := digest.NewDigestVerifier(hdrDigest) 340 if err != nil { 341 return "", fmt.Errorf("invalid manifest digest from registry: %s", err) 342 } 343 344 dgstVerifier.Write(rawManifest) 345 346 if !dgstVerifier.Verified() { 347 computedDigest, _ := digest.FromBytes(rawManifest) 348 return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest) 349 } 350 351 return hdrDigest, nil 352 } 353 354 type remoteTags struct { 355 name string 356 tags []string 357 } 358 359 // Given a repository name, returns a json array of string tags 360 func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestAuthorization) ([]string, error) { 361 routeURL, err := getV2Builder(ep).BuildTagsURL(imageName) 362 if err != nil { 363 return nil, err 364 } 365 366 method := "GET" 367 log.Debugf("[registry] Calling %q %s", method, routeURL) 368 369 req, err := r.reqFactory.NewRequest(method, routeURL, nil) 370 if err != nil { 371 return nil, err 372 } 373 if err := auth.Authorize(req); err != nil { 374 return nil, err 375 } 376 res, _, err := r.doRequest(req) 377 if err != nil { 378 return nil, err 379 } 380 defer res.Body.Close() 381 if res.StatusCode != 200 { 382 if res.StatusCode == 401 { 383 return nil, errLoginRequired 384 } else if res.StatusCode == 404 { 385 return nil, ErrDoesNotExist 386 } 387 return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) 388 } 389 390 decoder := json.NewDecoder(res.Body) 391 var remote remoteTags 392 err = decoder.Decode(&remote) 393 if err != nil { 394 return nil, fmt.Errorf("Error while decoding the http response: %s", err) 395 } 396 return remote.tags, nil 397 }