package artifacts import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" ) // blobBound is the most GetBlob reads: a templates archive is a few kilobytes; anything near this is no archive a // take-in reads text from. const blobBound = 64 << 20 // GetBlob is the archive a recorded reference names (`artifact-store:///blobs/sha256:`), read whole // and checked against the digest it names: bytes that hash to another digest are refused, never read (R4e: the // controller reads a templates archive by the reference of a builder's done key). Gone when the store does not hold // it; ErrNotOurs for a reference outside the store or one that is not an archive. func (s Store) GetBlob(ctx context.Context, reference string) ([]byte, error) { repository, digest, archive, err := s.archive(reference) if err != nil { return nil, err } if !archive { return nil, fmt.Errorf("%w: %s is not an archive", ErrNotOurs, reference) } request, err := http.NewRequestWithContext(ctx, http.MethodGet, s.url(repository, "blobs", digest), nil) if err != nil { return nil, err } response, err := s.client().Do(request) if err != nil { return nil, fmt.Errorf("cannot reach the artifact store at %s: %w", s.Address, err) } defer response.Body.Close() switch response.StatusCode { case http.StatusOK: case http.StatusNotFound: return nil, Gone default: return nil, fmt.Errorf("the artifact store answered %s for %s/blobs/%s", response.Status, repository, digest) } body, err := io.ReadAll(io.LimitReader(response.Body, blobBound+1)) if err != nil { return nil, fmt.Errorf("reading %s/blobs/%s: %w", repository, digest, err) } if len(body) > blobBound { return nil, fmt.Errorf("%s/blobs/%s is larger than %d bytes, which no archive read here is", repository, digest, blobBound) } sum := sha256.Sum256(body) if got := "sha256:" + hex.EncodeToString(sum[:]); got != digest { return nil, fmt.Errorf("the artifact store answered %s/blobs/%s with bytes of the digest %s: refused", repository, digest, got) } return body, nil }