GetBlobAt returns a sequential channel of readers that contain data for the requested blob chunks, and a channel that might get a single error value. The specified chunks must be not overlapping and sorted by their offset. The readers must be fully consumed, in the order they are returned, before bl
(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk)
| 388 | // If the Length for the last chunk is set to math.MaxUint64, then it |
| 389 | // fully fetches the remaining data from the offset to the end of the blob. |
| 390 | func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) { |
| 391 | headers := make(map[string][]string) |
| 392 | |
| 393 | rangeVals := make([]string, 0, len(chunks)) |
| 394 | lastFound := false |
| 395 | for _, c := range chunks { |
| 396 | if lastFound { |
| 397 | return nil, nil, fmt.Errorf("internal error: another chunk requested after an util-EOF chunk") |
| 398 | } |
| 399 | // If the Length is set to -1, then request anything after the specified offset. |
| 400 | if c.Length == math.MaxUint64 { |
| 401 | lastFound = true |
| 402 | rangeVals = append(rangeVals, fmt.Sprintf("%d-", c.Offset)) |
| 403 | } else { |
| 404 | rangeVals = append(rangeVals, fmt.Sprintf("%d-%d", c.Offset, c.Offset+c.Length-1)) |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | headers["Range"] = []string{fmt.Sprintf("bytes=%s", strings.Join(rangeVals, ","))} |
| 409 | |
| 410 | if len(info.URLs) != 0 { |
| 411 | return nil, nil, fmt.Errorf("external URLs not supported with GetBlobAt") |
| 412 | } |
| 413 | |
| 414 | if err := info.Digest.Validate(); err != nil { // Make sure info.Digest.String() does not contain any unexpected characters |
| 415 | return nil, nil, err |
| 416 | } |
| 417 | path := fmt.Sprintf(blobsPath, reference.Path(s.physicalRef.ref), info.Digest.String()) |
| 418 | logrus.Debugf("Downloading %s", path) |
| 419 | res, err := s.c.makeRequest(ctx, http.MethodGet, path, headers, nil, v2Auth, nil) |
| 420 | if err != nil { |
| 421 | return nil, nil, err |
| 422 | } |
| 423 | |
| 424 | switch res.StatusCode { |
| 425 | case http.StatusOK: |
| 426 | // if the server replied with a 200 status code, convert the full body response to a series of |
| 427 | // streams as it would have been done with 206. |
| 428 | streams := make(chan io.ReadCloser) |
| 429 | errs := make(chan error) |
| 430 | go splitHTTP200ResponseToPartial(streams, errs, res.Body, chunks) |
| 431 | return streams, errs, nil |
| 432 | case http.StatusPartialContent: |
| 433 | mediaType, params, err := parseMediaType(res.Header.Get("Content-Type")) |
| 434 | if err != nil { |
| 435 | return nil, nil, err |
| 436 | } |
| 437 | |
| 438 | streams := make(chan io.ReadCloser) |
| 439 | errs := make(chan error) |
| 440 | |
| 441 | go handle206Response(streams, errs, res.Body, chunks, mediaType, params) |
| 442 | return streams, errs, nil |
| 443 | case http.StatusBadRequest: |
| 444 | res.Body.Close() |
| 445 | return nil, nil, private.BadPartialRequestError{Status: res.Status} |
| 446 | default: |
| 447 | err := registryHTTPResponseToError(res) |
nothing calls this directly
no test coverage detected