Skip to content

Commit 19ce25a

Browse files
committed
Allow falling back from partial pulls if the metadata is too large
... but not if the fallback would be convert_images, again creating too large metadata. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
1 parent c2f9a31 commit 19ce25a

File tree

2 files changed

+51
-16
lines changed

2 files changed

+51
-16
lines changed

pkg/chunked/compression_linux.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func typeToTarType(t string) (byte, error) {
4545
}
4646

4747
// readEstargzChunkedManifest reads the estargz manifest from the seekable stream blobStream.
48-
// It may return an error matching ErrFallbackToOrdinaryLayerDownload.
48+
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
4949
func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, tocDigest digest.Digest) ([]byte, int64, error) {
5050
// information on the format here https://github.com/containerd/stargz-snapshotter/blob/main/docs/stargz-estargz.md
5151
footerSize := int64(51)
@@ -58,7 +58,7 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64,
5858
if err != nil {
5959
var badRequestErr ErrBadRequest
6060
if errors.As(err, &badRequestErr) {
61-
err = newErrFallbackToOrdinaryLayerDownload(err)
61+
err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)}
6262
}
6363
return nil, 0, err
6464
}
@@ -90,14 +90,15 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64,
9090
size := int64(blobSize - footerSize - tocOffset)
9191
// set a reasonable limit
9292
if size > maxTocSize {
93-
return nil, 0, errors.New("manifest too big")
93+
// Not errFallbackCanConvert: we would still use too much memory.
94+
return nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("estargz manifest too big to process in memory (%d bytes)", size))
9495
}
9596

9697
streamsOrErrors, err = getBlobAt(blobStream, ImageSourceChunk{Offset: uint64(tocOffset), Length: uint64(size)})
9798
if err != nil {
9899
var badRequestErr ErrBadRequest
99100
if errors.As(err, &badRequestErr) {
100-
err = newErrFallbackToOrdinaryLayerDownload(err)
101+
err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)}
101102
}
102103
return nil, 0, err
103104
}
@@ -158,7 +159,7 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64,
158159

159160
// readZstdChunkedManifest reads the zstd:chunked manifest from the seekable stream blobStream.
160161
// Returns (manifest blob, parsed manifest, tar-split blob or nil, manifest offset).
161-
// It may return an error matching ErrFallbackToOrdinaryLayerDownload.
162+
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
162163
func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) (_ []byte, _ *minimal.TOC, _ []byte, _ int64, retErr error) {
163164
offsetMetadata := annotations[minimal.ManifestInfoKey]
164165
if offsetMetadata == "" {
@@ -184,10 +185,12 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di
184185

185186
// set a reasonable limit
186187
if manifestChunk.Length > maxTocSize {
187-
return nil, nil, nil, 0, errors.New("manifest too big")
188+
// Not errFallbackCanConvert: we would still use too much memory.
189+
return nil, nil, nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked manifest too big to process in memory (%d bytes compressed)", manifestChunk.Length))
188190
}
189191
if manifestLengthUncompressed > maxTocSize {
190-
return nil, nil, nil, 0, errors.New("manifest too big")
192+
// Not errFallbackCanConvert: we would still use too much memory.
193+
return nil, nil, nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked manifest too big to process in memory (%d bytes uncompressed)", manifestLengthUncompressed))
191194
}
192195

193196
chunks := []ImageSourceChunk{manifestChunk}
@@ -199,7 +202,7 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di
199202
if err != nil {
200203
var badRequestErr ErrBadRequest
201204
if errors.As(err, &badRequestErr) {
202-
err = newErrFallbackToOrdinaryLayerDownload(err)
205+
err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)}
203206
}
204207
return nil, nil, nil, 0, err
205208
}

pkg/chunked/storage_linux.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,38 @@ func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Diges
225225
if !pullOptions.convertImages {
226226
return nil, err
227227
}
228+
var canConvertErr errFallbackCanConvert
229+
if !errors.As(err, &canConvertErr) {
230+
// We are supposed to use makeConvertFromRawDiffer, but that would not work.
231+
// Fail, and make sure the error does _not_ match ErrFallbackToOrdinaryLayerDownload: use only the error text,
232+
// discard all type information.
233+
return nil, fmt.Errorf("neither a partial pull nor convert_images is possible: %s", err.Error())
234+
}
228235
logrus.Debugf("Created differ to convert blob %q", blobDigest)
229236
return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions)
230237
}
231238

232239
return differ, nil
233240
}
234241

242+
// errFallbackCanConvert is an an error type _accompanying_ ErrFallbackToOrdinaryLayerDownload
243+
// within getProperDiffer, to mark that using makeConvertFromRawDiffer makes sense.
244+
// This is used to distinguish between cases where the environment does not support partial pulls
245+
// (e.g. a registry does not support range requests) and convert_images is still possible,
246+
// from cases where the image content is unacceptable for partial pulls (e.g. exceeds memory limits)
247+
// and convert_images would not help.
248+
type errFallbackCanConvert struct {
249+
err error
250+
}
251+
252+
func (e errFallbackCanConvert) Error() string {
253+
return e.err.Error()
254+
}
255+
256+
func (e errFallbackCanConvert) Unwrap() error {
257+
return e.err
258+
}
259+
235260
// getProperDiffer is an implementation detail of GetDiffer.
236261
// It returns a “proper” differ (not a convert_images one) if possible.
237262
// May return an error matching ErrFallbackToOrdinaryLayerDownload if a fallback to an alternative
@@ -271,10 +296,13 @@ func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int
271296
return differ, nil
272297

273298
default: // no TOC
299+
message := "no TOC found"
274300
if !pullOptions.convertImages {
275-
return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("no TOC found and convert_images is not configured"))
301+
message = "no TOC found and convert_images is not configured"
302+
}
303+
return nil, errFallbackCanConvert{
304+
newErrFallbackToOrdinaryLayerDownload(errors.New(message)),
276305
}
277-
return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("no TOC found"))
278306
}
279307
}
280308

@@ -301,10 +329,10 @@ func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blo
301329
}
302330

303331
// makeZstdChunkedDiffer sets up a chunkedDiffer for a zstd:chunked layer.
304-
// It may return an error matching ErrFallbackToOrdinaryLayerDownload.
332+
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
305333
func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, error) {
306334
manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(iss, tocDigest, annotations)
307-
if err != nil { // May be ErrFallbackToOrdinaryLayerDownload
335+
if err != nil { // May be ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert
308336
return nil, fmt.Errorf("read zstd:chunked manifest: %w", err)
309337
}
310338

@@ -315,7 +343,9 @@ func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest
315343
return nil, fmt.Errorf("computing size from tar-split: %w", err)
316344
}
317345
} else if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest.
318-
return nil, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked layers without tar-split data don't support partial pulls with guaranteed consistency with non-partial pulls"))
346+
return nil, errFallbackCanConvert{
347+
newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked layers without tar-split data don't support partial pulls with guaranteed consistency with non-partial pulls")),
348+
}
319349
}
320350

321351
layersCache, err := getLayersCache(store)
@@ -344,14 +374,16 @@ func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest
344374
}
345375

346376
// makeEstargzChunkedDiffer sets up a chunkedDiffer for an estargz layer.
347-
// It may return an error matching ErrFallbackToOrdinaryLayerDownload.
377+
// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert.
348378
func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, error) {
349379
if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest.
350-
return nil, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("estargz layers don't support partial pulls with guaranteed consistency with non-partial pulls"))
380+
return nil, errFallbackCanConvert{
381+
newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("estargz layers don't support partial pulls with guaranteed consistency with non-partial pulls")),
382+
}
351383
}
352384

353385
manifest, tocOffset, err := readEstargzChunkedManifest(iss, blobSize, tocDigest)
354-
if err != nil { // May be ErrFallbackToOrdinaryLayerDownload
386+
if err != nil { // May be ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert
355387
return nil, fmt.Errorf("read zstd:chunked manifest: %w", err)
356388
}
357389
layersCache, err := getLayersCache(store)

0 commit comments

Comments
 (0)