@@ -27,6 +27,7 @@ import (
2727 "github.com/opentracing/opentracing-go"
2828 "github.com/pkg/errors"
2929 "golang.org/x/xerrors"
30+ "k8s.io/apimachinery/pkg/util/wait"
3031
3132 "github.com/gitpod-io/gitpod/common-go/log"
3233 "github.com/gitpod-io/gitpod/common-go/tracing"
@@ -96,6 +97,15 @@ func (reg *Registry) handleManifest(ctx context.Context, r *http.Request) http.H
9697 return res
9798}
9899
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+
99109type manifestHandler struct {
100110 Context context.Context
101111
@@ -278,39 +288,51 @@ func DownloadConfig(ctx context.Context, fetch FetcherFunc, ref string, desc oci
278288
279289 return nil , xerrors .Errorf ("unsupported media type: %s" , desc .MediaType )
280290 }
291+ log := log .WithField ("desc" , desc )
281292
282293 var opts manifestDownloadOptions
283294 for _ , o := range options {
284295 o (& opts )
285296 }
286297
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+ }
297311 }
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 ()
303324 }
304- rc , err = fetcher .Fetch (ctx , desc )
325+
326+ buf , err = io .ReadAll (rc )
305327 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
307330 }
308- defer rc .Close ()
309- }
310331
311- buf , err := io .ReadAll (rc )
332+ return true , nil
333+ })
312334 if err != nil {
313- return nil , xerrors .Errorf ("cannot read config: %w" , err )
335+ return nil , xerrors .Errorf ("failed to fetch config: %w" , err )
314336 }
315337
316338 var res ociv1.Image
@@ -387,68 +409,80 @@ func AsFetcherFunc(f remotes.Fetcher) FetcherFunc {
387409// DownloadManifest downloads and unmarshals the manifest of the given desc. If the desc points to manifest list
388410// we choose the first manifest in that list.
389411func 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+
390414 var opts manifestDownloadOptions
391415 for _ , o := range options {
392416 o (& opts )
393417 }
394418
395419 var (
396420 placeInStore bool
397- rc io.ReadCloser
398421 mediaType = desc .MediaType
422+ inpt []byte
399423 )
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 ()
407462 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
414465 }
415466
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 )
421468 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
424471 }
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
438473 }
439474
440- rc , err = fetcher .Fetch (ctx , desc )
475+ inpt , err = io .ReadAll (rc )
476+ rc .Close ()
441477 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
444480 }
445- mediaType = desc .MediaType
446- }
447481
448- inpt , err := io . ReadAll ( rc )
449- rc . Close ( )
482+ return true , nil
483+ } )
450484 if err != nil {
451- err = xerrors .Errorf ("cannot download manifest: %w" , err )
485+ err = xerrors .Errorf ("failed to fetch manifest: %w" , err )
452486 return
453487 }
454488
@@ -457,7 +491,8 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
457491
458492 switch rdesc .MediaType {
459493 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" )
461496
462497 // we received a manifest list which means we'll pick the default platform
463498 // and fetch that manifest
@@ -472,24 +507,34 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
472507 return
473508 }
474509
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+ }
480517
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+ })
491536 if err != nil {
492- err = xerrors .Errorf ("cannot download manifest : %w" , err )
537+ err = xerrors .Errorf ("failed to download config : %w" , err )
493538 return
494539 }
495540 }
0 commit comments