@@ -12,6 +12,7 @@ import (
12
12
"github.com/Sirupsen/logrus"
13
13
"github.com/docker/distribution"
14
14
"github.com/docker/distribution/digest"
15
+ "github.com/docker/distribution/manifest/manifestlist"
15
16
"github.com/docker/distribution/manifest/schema1"
16
17
"github.com/docker/distribution/manifest/schema2"
17
18
"github.com/docker/distribution/registry/api/errcode"
@@ -254,6 +255,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
254
255
if err != nil {
255
256
return false , err
256
257
}
258
+ case * manifestlist.DeserializedManifestList :
259
+ imageID , manifestDigest , err = p .pullManifestList (ctx , ref , v )
260
+ if err != nil {
261
+ return false , err
262
+ }
257
263
default :
258
264
return false , errors .New ("unsupported manifest format" )
259
265
}
@@ -357,30 +363,11 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverif
357
363
}
358
364
359
365
func (p * v2Puller ) pullSchema2 (ctx context.Context , ref reference.Named , mfst * schema2.DeserializedManifest ) (imageID image.ID , manifestDigest digest.Digest , err error ) {
360
- _ , canonical , err := mfst . Payload ( )
366
+ manifestDigest , err = schema2ManifestDigest ( ref , mfst )
361
367
if err != nil {
362
368
return "" , "" , err
363
369
}
364
370
365
- // If pull by digest, then verify the manifest digest.
366
- if digested , isDigested := ref .(reference.Canonical ); isDigested {
367
- verifier , err := digest .NewDigestVerifier (digested .Digest ())
368
- if err != nil {
369
- return "" , "" , err
370
- }
371
- if _ , err := verifier .Write (canonical ); err != nil {
372
- return "" , "" , err
373
- }
374
- if ! verifier .Verified () {
375
- err := fmt .Errorf ("manifest verification failed for digest %s" , digested .Digest ())
376
- logrus .Error (err )
377
- return "" , "" , err
378
- }
379
- manifestDigest = digested .Digest ()
380
- } else {
381
- manifestDigest = digest .FromBytes (canonical )
382
- }
383
-
384
371
target := mfst .Target ()
385
372
imageID = image .ID (target .Digest )
386
373
if _ , err := p .config .ImageStore .Get (imageID ); err == nil {
@@ -470,6 +457,62 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
470
457
return imageID , manifestDigest , nil
471
458
}
472
459
460
+ // pullManifestList handles "manifest lists" which point to various
461
+ // platform-specifc manifests.
462
+ func (p * v2Puller ) pullManifestList (ctx context.Context , ref reference.Named , mfstList * manifestlist.DeserializedManifestList ) (imageID image.ID , manifestListDigest digest.Digest , err error ) {
463
+ manifestListDigest , err = schema2ManifestDigest (ref , mfstList )
464
+ if err != nil {
465
+ return "" , "" , err
466
+ }
467
+
468
+ var manifestDigest digest.Digest
469
+ for _ , manifestDescriptor := range mfstList .Manifests {
470
+ // TODO(aaronl): The manifest list spec supports optional
471
+ // "features" and "variant" fields. These are not yet used.
472
+ // Once they are, their values should be interpreted here.
473
+ if manifestDescriptor .Platform .Architecture == runtime .GOARCH && manifestDescriptor .Platform .OS == runtime .GOOS {
474
+ manifestDigest = manifestDescriptor .Digest
475
+ break
476
+ }
477
+ }
478
+
479
+ if manifestDigest == "" {
480
+ return "" , "" , errors .New ("no supported platform found in manifest list" )
481
+ }
482
+
483
+ manSvc , err := p .repo .Manifests (ctx )
484
+ if err != nil {
485
+ return "" , "" , err
486
+ }
487
+
488
+ manifest , err := manSvc .Get (ctx , manifestDigest )
489
+ if err != nil {
490
+ return "" , "" , err
491
+ }
492
+
493
+ manifestRef , err := reference .WithDigest (ref , manifestDigest )
494
+ if err != nil {
495
+ return "" , "" , err
496
+ }
497
+
498
+ switch v := manifest .(type ) {
499
+ case * schema1.SignedManifest :
500
+ imageID , _ , err = p .pullSchema1 (ctx , manifestRef , v )
501
+ if err != nil {
502
+ return "" , "" , err
503
+ }
504
+ case * schema2.DeserializedManifest :
505
+ imageID , _ , err = p .pullSchema2 (ctx , manifestRef , v )
506
+ if err != nil {
507
+ return "" , "" , err
508
+ }
509
+ default :
510
+ return "" , "" , errors .New ("unsupported manifest format" )
511
+ }
512
+
513
+ return imageID , manifestListDigest , err
514
+ }
515
+
473
516
func (p * v2Puller ) pullSchema2ImageConfig (ctx context.Context , dgst digest.Digest ) (configJSON []byte , err error ) {
474
517
blobs := p .repo .Blobs (ctx )
475
518
configJSON , err = blobs .Get (ctx , dgst )
@@ -494,6 +537,34 @@ func (p *v2Puller) pullSchema2ImageConfig(ctx context.Context, dgst digest.Diges
494
537
return configJSON , nil
495
538
}
496
539
540
+ // schema2ManifestDigest computes the manifest digest, and, if pulling by
541
+ // digest, ensures that it matches the requested digest.
542
+ func schema2ManifestDigest (ref reference.Named , mfst distribution.Manifest ) (digest.Digest , error ) {
543
+ _ , canonical , err := mfst .Payload ()
544
+ if err != nil {
545
+ return "" , err
546
+ }
547
+
548
+ // If pull by digest, then verify the manifest digest.
549
+ if digested , isDigested := ref .(reference.Canonical ); isDigested {
550
+ verifier , err := digest .NewDigestVerifier (digested .Digest ())
551
+ if err != nil {
552
+ return "" , err
553
+ }
554
+ if _ , err := verifier .Write (canonical ); err != nil {
555
+ return "" , err
556
+ }
557
+ if ! verifier .Verified () {
558
+ err := fmt .Errorf ("manifest verification failed for digest %s" , digested .Digest ())
559
+ logrus .Error (err )
560
+ return "" , err
561
+ }
562
+ return digested .Digest (), nil
563
+ }
564
+
565
+ return digest .FromBytes (canonical ), nil
566
+ }
567
+
497
568
// allowV1Fallback checks if the error is a possible reason to fallback to v1
498
569
// (even if confirmedV2 has been set already), and if so, wraps the error in
499
570
// a fallbackError with confirmedV2 set to false. Otherwise, it returns the
0 commit comments