@@ -27,6 +27,7 @@ import (
27
27
"github.com/opentracing/opentracing-go"
28
28
"github.com/pkg/errors"
29
29
"golang.org/x/xerrors"
30
+ "k8s.io/apimachinery/pkg/util/wait"
30
31
31
32
"github.com/gitpod-io/gitpod/common-go/log"
32
33
"github.com/gitpod-io/gitpod/common-go/tracing"
@@ -96,6 +97,15 @@ func (reg *Registry) handleManifest(ctx context.Context, r *http.Request) http.H
96
97
return res
97
98
}
98
99
100
+ // fetcherBackoffParams defines the backoff parameters for blob retrieval.
101
+ // Aiming at ~10 seconds total time for retries
102
+ var fetcherBackoffParams = wait.Backoff {
103
+ Duration : 1 * time .Second ,
104
+ Factor : 1.2 ,
105
+ Jitter : 0.2 ,
106
+ Steps : 5 ,
107
+ }
108
+
99
109
type manifestHandler struct {
100
110
Context context.Context
101
111
@@ -278,39 +288,51 @@ func DownloadConfig(ctx context.Context, fetch FetcherFunc, ref string, desc oci
278
288
279
289
return nil , xerrors .Errorf ("unsupported media type: %s" , desc .MediaType )
280
290
}
291
+ log := log .WithField ("desc" , desc )
281
292
282
293
var opts manifestDownloadOptions
283
294
for _ , o := range options {
284
295
o (& opts )
285
296
}
286
297
287
- var rc io.ReadCloser
288
- if opts .Store != nil {
289
- r , err := opts .Store .ReaderAt (ctx , desc )
290
- if errors .Is (err , errdefs .ErrNotFound ) {
291
- // not cached yet
292
- } else if err != nil {
293
- log .WithError (err ).WithField ("desc" , desc ).Warn ("cannot read config from store - fetching again" )
294
- } else {
295
- defer r .Close ()
296
- rc = io .NopCloser (content .NewReader (r ))
298
+ var buf []byte
299
+ err = wait .ExponentialBackoffWithContext (ctx , fetcherBackoffParams , func (ctx context.Context ) (done bool , err error ) {
300
+ var rc io.ReadCloser
301
+ if opts .Store != nil {
302
+ r , err := opts .Store .ReaderAt (ctx , desc )
303
+ if errors .Is (err , errdefs .ErrNotFound ) {
304
+ // not cached yet
305
+ } else if err != nil {
306
+ log .WithError (err ).Warn ("cannot read config from store - fetching again" )
307
+ } else {
308
+ defer r .Close ()
309
+ rc = io .NopCloser (content .NewReader (r ))
310
+ }
297
311
}
298
- }
299
- if rc == nil {
300
- fetcher , err := fetch ()
301
- if err != nil {
302
- return nil , err
312
+ if rc == nil {
313
+ fetcher , err := fetch ()
314
+ if err != nil {
315
+ log .WithError (err ).Warn ("cannot create fetcher" )
316
+ return false , nil // retry
317
+ }
318
+ rc , err = fetcher .Fetch (ctx , desc )
319
+ if err != nil {
320
+ log .WithError (err ).Warn ("cannot fetch config" )
321
+ return false , nil // retry
322
+ }
323
+ defer rc .Close ()
303
324
}
304
- rc , err = fetcher .Fetch (ctx , desc )
325
+
326
+ buf , err = io .ReadAll (rc )
305
327
if err != nil {
306
- return nil , xerrors .Errorf ("cannot download config: %w" , err )
328
+ log .WithError (err ).Warn ("cannot read config" )
329
+ return false , nil // retry
307
330
}
308
- defer rc .Close ()
309
- }
310
331
311
- buf , err := io .ReadAll (rc )
332
+ return true , nil
333
+ })
312
334
if err != nil {
313
- return nil , xerrors .Errorf ("cannot read config: %w" , err )
335
+ return nil , xerrors .Errorf ("failed to fetch config: %w" , err )
314
336
}
315
337
316
338
var res ociv1.Image
@@ -387,68 +409,80 @@ func AsFetcherFunc(f remotes.Fetcher) FetcherFunc {
387
409
// DownloadManifest downloads and unmarshals the manifest of the given desc. If the desc points to manifest list
388
410
// we choose the first manifest in that list.
389
411
func DownloadManifest (ctx context.Context , fetch FetcherFunc , desc ociv1.Descriptor , options ... ManifestDownloadOption ) (cfg * ociv1.Manifest , rdesc * ociv1.Descriptor , err error ) {
412
+ log := log .WithField ("desc" , desc )
413
+
390
414
var opts manifestDownloadOptions
391
415
for _ , o := range options {
392
416
o (& opts )
393
417
}
394
418
395
419
var (
396
420
placeInStore bool
397
- rc io.ReadCloser
398
421
mediaType = desc .MediaType
422
+ inpt []byte
399
423
)
400
- if opts .Store != nil {
401
- func () {
402
- nfo , err := opts .Store .Info (ctx , desc .Digest )
403
- if errors .Is (err , errdefs .ErrNotFound ) {
404
- // not in store yet
405
- return
406
- }
424
+ err = wait .ExponentialBackoffWithContext (ctx , fetcherBackoffParams , func (ctx context.Context ) (done bool , err error ) {
425
+ var rc io.ReadCloser
426
+ if opts .Store != nil {
427
+ func () {
428
+ nfo , err := opts .Store .Info (ctx , desc .Digest )
429
+ if errors .Is (err , errdefs .ErrNotFound ) {
430
+ // not in store yet
431
+ return
432
+ }
433
+ if err != nil {
434
+ log .WithError (err ).Warn ("cannot get manifest from store" )
435
+ return
436
+ }
437
+ if nfo .Labels ["Content-Type" ] == "" {
438
+ // we have broken data in the store - ignore it and overwrite
439
+ return
440
+ }
441
+
442
+ r , err := opts .Store .ReaderAt (ctx , desc )
443
+ if errors .Is (err , errdefs .ErrNotFound ) {
444
+ // not in store yet
445
+ return
446
+ }
447
+ if err != nil {
448
+ log .WithError (err ).Warn ("cannot get manifest from store" )
449
+ return
450
+ }
451
+
452
+ mediaType , rc = nfo .Labels ["Content-Type" ], & reader {ReaderAt : r }
453
+ }()
454
+ }
455
+ if rc == nil {
456
+ // did not find in store, or there was no store. Either way, let's fetch this
457
+ // thing from the remote.
458
+ placeInStore = true
459
+
460
+ var fetcher remotes.Fetcher
461
+ fetcher , err = fetch ()
407
462
if err != nil {
408
- log .WithError (err ).WithField ("desc" , desc ).Warn ("cannot get manifest from store" )
409
- return
410
- }
411
- if nfo .Labels ["Content-Type" ] == "" {
412
- // we have broken data in the store - ignore it and overwrite
413
- return
463
+ log .WithError (err ).Warn ("cannot create fetcher" )
464
+ return false , nil // retry
414
465
}
415
466
416
- r , err := opts .Store .ReaderAt (ctx , desc )
417
- if errors .Is (err , errdefs .ErrNotFound ) {
418
- // not in store yet
419
- return
420
- }
467
+ rc , err = fetcher .Fetch (ctx , desc )
421
468
if err != nil {
422
- log .WithError (err ).WithField ( "desc" , desc ). Warn ("cannot get manifest from store " )
423
- return
469
+ log .WithError (err ).Warn ("cannot fetch manifest" )
470
+ return false , nil // retry
424
471
}
425
-
426
- mediaType , rc = nfo .Labels ["Content-Type" ], & reader {ReaderAt : r }
427
- }()
428
- }
429
- if rc == nil {
430
- // did not find in store, or there was no store. Either way, let's fetch this
431
- // thing from the remote.
432
- placeInStore = true
433
-
434
- var fetcher remotes.Fetcher
435
- fetcher , err = fetch ()
436
- if err != nil {
437
- return
472
+ mediaType = desc .MediaType
438
473
}
439
474
440
- rc , err = fetcher .Fetch (ctx , desc )
475
+ inpt , err = io .ReadAll (rc )
476
+ rc .Close ()
441
477
if err != nil {
442
- err = xerrors . Errorf ("cannot fetch manifest: %w" , err )
443
- return
478
+ log . WithError ( err ). Warn ("cannot read manifest" )
479
+ return false , nil // retry
444
480
}
445
- mediaType = desc .MediaType
446
- }
447
481
448
- inpt , err := io . ReadAll ( rc )
449
- rc . Close ( )
482
+ return true , nil
483
+ } )
450
484
if err != nil {
451
- err = xerrors .Errorf ("cannot download manifest: %w" , err )
485
+ err = xerrors .Errorf ("failed to fetch manifest: %w" , err )
452
486
return
453
487
}
454
488
@@ -457,7 +491,8 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
457
491
458
492
switch rdesc .MediaType {
459
493
case images .MediaTypeDockerSchema2ManifestList , ociv1 .MediaTypeImageIndex :
460
- log .WithField ("desc" , rdesc ).Debug ("resolving image index" )
494
+ log := log .WithField ("desc" , rdesc )
495
+ log .Debug ("resolving image index" )
461
496
462
497
// we received a manifest list which means we'll pick the default platform
463
498
// and fetch that manifest
@@ -472,24 +507,34 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
472
507
return
473
508
}
474
509
475
- var fetcher remotes.Fetcher
476
- fetcher , err = fetch ()
477
- if err != nil {
478
- return
479
- }
510
+ err = wait .ExponentialBackoffWithContext (ctx , fetcherBackoffParams , func (ctx context.Context ) (done bool , err error ) {
511
+ var fetcher remotes.Fetcher
512
+ fetcher , err = fetch ()
513
+ if err != nil {
514
+ log .WithError (err ).Warn ("cannot create fetcher" )
515
+ return false , nil // retry
516
+ }
480
517
481
- // TODO(cw): choose by platform, not just the first manifest
482
- md := list .Manifests [0 ]
483
- rc , err = fetcher .Fetch (ctx , md )
484
- if err != nil {
485
- err = xerrors .Errorf ("cannot download config: %w" , err )
486
- return
487
- }
488
- rdesc = & md
489
- inpt , err = io .ReadAll (rc )
490
- rc .Close ()
518
+ // TODO(cw): choose by platform, not just the first manifest
519
+ var rc io.ReadCloser
520
+ md := list .Manifests [0 ]
521
+ rc , err = fetcher .Fetch (ctx , md )
522
+ if err != nil {
523
+ log .WithError (err ).Warn ("cannot download config" )
524
+ return false , nil // retry
525
+ }
526
+ rdesc = & md
527
+ inpt , err = io .ReadAll (rc )
528
+ rc .Close ()
529
+ if err != nil {
530
+ log .WithError (err ).Warn ("cannot download manifest" )
531
+ return false , nil // retry
532
+ }
533
+
534
+ return true , nil
535
+ })
491
536
if err != nil {
492
- err = xerrors .Errorf ("cannot download manifest : %w" , err )
537
+ err = xerrors .Errorf ("failed to download config : %w" , err )
493
538
return
494
539
}
495
540
}
0 commit comments