github.com/slowteetoe/docker@v1.7.1-rc3/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 "github.com/Sirupsen/logrus" 13 "github.com/docker/distribution/digest" 14 "github.com/docker/distribution/registry/api/v2" 15 "github.com/docker/docker/pkg/httputils" 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, nil) 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, nil) 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 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 logrus.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 72 // GetV2ImageManifest simply fetches the bytes of a manifest and the remote 73 // digest, if available in the request. Note that the application shouldn't 74 // rely on the untrusted remoteDigest, and should also verify against a 75 // locally provided digest, if applicable. 76 func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) (remoteDigest digest.Digest, p []byte, err error) { 77 routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) 78 if err != nil { 79 return "", nil, err 80 } 81 82 method := "GET" 83 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 84 85 req, err := http.NewRequest(method, routeURL, nil) 86 if err != nil { 87 return "", nil, err 88 } 89 90 if err := auth.Authorize(req); err != nil { 91 return "", nil, err 92 } 93 94 res, err := r.client.Do(req) 95 if err != nil { 96 return "", nil, err 97 } 98 defer res.Body.Close() 99 100 if res.StatusCode != 200 { 101 if res.StatusCode == 401 { 102 return "", nil, errLoginRequired 103 } else if res.StatusCode == 404 { 104 return "", nil, ErrDoesNotExist 105 } 106 return "", nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) 107 } 108 109 p, err = ioutil.ReadAll(res.Body) 110 if err != nil { 111 return "", nil, fmt.Errorf("Error while reading the http response: %s", err) 112 } 113 114 dgstHdr := res.Header.Get(DockerDigestHeader) 115 if dgstHdr != "" { 116 remoteDigest, err = digest.ParseDigest(dgstHdr) 117 if err != nil { 118 // NOTE(stevvooe): Including the remote digest is optional. We 119 // don't need to verify against it, but it is good practice. 120 remoteDigest = "" 121 logrus.Debugf("error parsing remote digest when fetching %v: %v", routeURL, err) 122 } 123 } 124 125 return 126 } 127 128 // - Succeeded to head image blob (already exists) 129 // - Failed with no error (continue to Push the Blob) 130 // - Failed with error 131 func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) { 132 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) 133 if err != nil { 134 return false, err 135 } 136 137 method := "HEAD" 138 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 139 140 req, err := http.NewRequest(method, routeURL, nil) 141 if err != nil { 142 return false, err 143 } 144 if err := auth.Authorize(req); err != nil { 145 return false, err 146 } 147 res, err := r.client.Do(req) 148 if err != nil { 149 return false, err 150 } 151 res.Body.Close() // close early, since we're not needing a body on this call .. yet? 152 switch { 153 case res.StatusCode >= 200 && res.StatusCode < 400: 154 // return something indicating no push needed 155 return true, nil 156 case res.StatusCode == 401: 157 return false, errLoginRequired 158 case res.StatusCode == 404: 159 // return something indicating blob push needed 160 return false, nil 161 } 162 163 return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) 164 } 165 166 func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error { 167 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) 168 if err != nil { 169 return err 170 } 171 172 method := "GET" 173 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 174 req, err := http.NewRequest(method, routeURL, nil) 175 if err != nil { 176 return err 177 } 178 if err := auth.Authorize(req); err != nil { 179 return err 180 } 181 res, err := r.client.Do(req) 182 if err != nil { 183 return err 184 } 185 defer res.Body.Close() 186 if res.StatusCode != 200 { 187 if res.StatusCode == 401 { 188 return errLoginRequired 189 } 190 return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) 191 } 192 193 _, err = io.Copy(blobWrtr, res.Body) 194 return err 195 } 196 197 func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) { 198 routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) 199 if err != nil { 200 return nil, 0, err 201 } 202 203 method := "GET" 204 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 205 req, err := http.NewRequest(method, routeURL, nil) 206 if err != nil { 207 return nil, 0, err 208 } 209 if err := auth.Authorize(req); err != nil { 210 return nil, 0, err 211 } 212 res, err := r.client.Do(req) 213 if err != nil { 214 return nil, 0, err 215 } 216 if res.StatusCode != 200 { 217 if res.StatusCode == 401 { 218 return nil, 0, errLoginRequired 219 } 220 return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) 221 } 222 lenStr := res.Header.Get("Content-Length") 223 l, err := strconv.ParseInt(lenStr, 10, 64) 224 if err != nil { 225 return nil, 0, err 226 } 227 228 return res.Body, l, err 229 } 230 231 // Push the image to the server for storage. 232 // 'layer' is an uncompressed reader of the blob to be pushed. 233 // The server will generate it's own checksum calculation. 234 func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error { 235 location, err := r.initiateBlobUpload(ep, imageName, auth) 236 if err != nil { 237 return err 238 } 239 240 method := "PUT" 241 logrus.Debugf("[registry] Calling %q %s", method, location) 242 req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr)) 243 if err != nil { 244 return err 245 } 246 queryParams := req.URL.Query() 247 queryParams.Add("digest", dgst.String()) 248 req.URL.RawQuery = queryParams.Encode() 249 if err := auth.Authorize(req); err != nil { 250 return err 251 } 252 res, err := r.client.Do(req) 253 if err != nil { 254 return err 255 } 256 defer res.Body.Close() 257 258 if res.StatusCode != 201 { 259 if res.StatusCode == 401 { 260 return errLoginRequired 261 } 262 errBody, err := ioutil.ReadAll(res.Body) 263 if err != nil { 264 return err 265 } 266 logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 267 return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) 268 } 269 270 return nil 271 } 272 273 // initiateBlobUpload gets the blob upload location for the given image name. 274 func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *RequestAuthorization) (location string, err error) { 275 routeURL, err := getV2Builder(ep).BuildBlobUploadURL(imageName) 276 if err != nil { 277 return "", err 278 } 279 280 logrus.Debugf("[registry] Calling %q %s", "POST", routeURL) 281 req, err := http.NewRequest("POST", routeURL, nil) 282 if err != nil { 283 return "", err 284 } 285 286 if err := auth.Authorize(req); err != nil { 287 return "", err 288 } 289 res, err := r.client.Do(req) 290 if err != nil { 291 return "", err 292 } 293 294 if res.StatusCode != http.StatusAccepted { 295 if res.StatusCode == http.StatusUnauthorized { 296 return "", errLoginRequired 297 } 298 if res.StatusCode == http.StatusNotFound { 299 return "", ErrDoesNotExist 300 } 301 302 errBody, err := ioutil.ReadAll(res.Body) 303 if err != nil { 304 return "", err 305 } 306 307 logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 308 return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) 309 } 310 311 if location = res.Header.Get("Location"); location == "" { 312 return "", fmt.Errorf("registry did not return a Location header for resumable blob upload for image %s", imageName) 313 } 314 315 return 316 } 317 318 // Finally Push the (signed) manifest of the blobs we've just pushed 319 func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) { 320 routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) 321 if err != nil { 322 return "", err 323 } 324 325 method := "PUT" 326 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 327 req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest)) 328 if err != nil { 329 return "", err 330 } 331 if err := auth.Authorize(req); err != nil { 332 return "", err 333 } 334 res, err := r.client.Do(req) 335 if err != nil { 336 return "", err 337 } 338 defer res.Body.Close() 339 340 // All 2xx and 3xx responses can be accepted for a put. 341 if res.StatusCode >= 400 { 342 if res.StatusCode == 401 { 343 return "", errLoginRequired 344 } 345 errBody, err := ioutil.ReadAll(res.Body) 346 if err != nil { 347 return "", err 348 } 349 logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) 350 return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) 351 } 352 353 hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) 354 if err != nil { 355 return "", fmt.Errorf("invalid manifest digest from registry: %s", err) 356 } 357 358 dgstVerifier, err := digest.NewDigestVerifier(hdrDigest) 359 if err != nil { 360 return "", fmt.Errorf("invalid manifest digest from registry: %s", err) 361 } 362 363 dgstVerifier.Write(rawManifest) 364 365 if !dgstVerifier.Verified() { 366 computedDigest, _ := digest.FromBytes(rawManifest) 367 return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest) 368 } 369 370 return hdrDigest, nil 371 } 372 373 type remoteTags struct { 374 Name string `json:"name"` 375 Tags []string `json:"tags"` 376 } 377 378 // Given a repository name, returns a json array of string tags 379 func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestAuthorization) ([]string, error) { 380 routeURL, err := getV2Builder(ep).BuildTagsURL(imageName) 381 if err != nil { 382 return nil, err 383 } 384 385 method := "GET" 386 logrus.Debugf("[registry] Calling %q %s", method, routeURL) 387 388 req, err := http.NewRequest(method, routeURL, nil) 389 if err != nil { 390 return nil, err 391 } 392 if err := auth.Authorize(req); err != nil { 393 return nil, err 394 } 395 res, err := r.client.Do(req) 396 if err != nil { 397 return nil, err 398 } 399 defer res.Body.Close() 400 if res.StatusCode != 200 { 401 if res.StatusCode == 401 { 402 return nil, errLoginRequired 403 } else if res.StatusCode == 404 { 404 return nil, ErrDoesNotExist 405 } 406 return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) 407 } 408 409 var remote remoteTags 410 if err := json.NewDecoder(res.Body).Decode(&remote); err != nil { 411 return nil, fmt.Errorf("Error while decoding the http response: %s", err) 412 } 413 return remote.Tags, nil 414 }