A manifest list refers to platform-specific manifests. This allows
for images that target more than one architecture to share the same tag.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
| ... | ... |
@@ -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 | 254 |
if err != nil {
|
| 255 | 255 |
return false, err |
| 256 | 256 |
} |
| 257 |
+ case *manifestlist.DeserializedManifestList: |
|
| 258 |
+ imageID, manifestDigest, err = p.pullManifestList(ctx, ref, v) |
|
| 259 |
+ if err != nil {
|
|
| 260 |
+ return false, err |
|
| 261 |
+ } |
|
| 257 | 262 |
default: |
| 258 | 263 |
return false, errors.New("unsupported manifest format")
|
| 259 | 264 |
} |
| ... | ... |
@@ -357,30 +363,11 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverif |
| 357 | 357 |
} |
| 358 | 358 |
|
| 359 | 359 |
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() |
|
| 360 |
+ manifestDigest, err = schema2ManifestDigest(ref, mfst) |
|
| 361 | 361 |
if err != nil {
|
| 362 | 362 |
return "", "", err |
| 363 | 363 |
} |
| 364 | 364 |
|
| 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 | 365 |
target := mfst.Target() |
| 385 | 366 |
imageID = image.ID(target.Digest) |
| 386 | 367 |
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 | 470 |
return imageID, manifestDigest, nil |
| 471 | 471 |
} |
| 472 | 472 |
|
| 473 |
+// pullManifestList handles "manifest lists" which point to various |
|
| 474 |
+// platform-specifc manifests. |
|
| 475 |
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (imageID image.ID, manifestListDigest digest.Digest, err error) {
|
|
| 476 |
+ manifestListDigest, err = schema2ManifestDigest(ref, mfstList) |
|
| 477 |
+ if err != nil {
|
|
| 478 |
+ return "", "", err |
|
| 479 |
+ } |
|
| 480 |
+ |
|
| 481 |
+ var manifestDigest digest.Digest |
|
| 482 |
+ for _, manifestDescriptor := range mfstList.Manifests {
|
|
| 483 |
+ // TODO(aaronl): The manifest list spec supports optional |
|
| 484 |
+ // "features" and "variant" fields. These are not yet used. |
|
| 485 |
+ // Once they are, their values should be interpreted here. |
|
| 486 |
+ if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
|
|
| 487 |
+ manifestDigest = manifestDescriptor.Digest |
|
| 488 |
+ break |
|
| 489 |
+ } |
|
| 490 |
+ } |
|
| 491 |
+ |
|
| 492 |
+ if manifestDigest == "" {
|
|
| 493 |
+ return "", "", errors.New("no supported platform found in manifest list")
|
|
| 494 |
+ } |
|
| 495 |
+ |
|
| 496 |
+ manSvc, err := p.repo.Manifests(ctx) |
|
| 497 |
+ if err != nil {
|
|
| 498 |
+ return "", "", err |
|
| 499 |
+ } |
|
| 500 |
+ |
|
| 501 |
+ manifest, err := manSvc.Get(ctx, manifestDigest) |
|
| 502 |
+ if err != nil {
|
|
| 503 |
+ return "", "", err |
|
| 504 |
+ } |
|
| 505 |
+ |
|
| 506 |
+ manifestRef, err := reference.WithDigest(ref, manifestDigest) |
|
| 507 |
+ if err != nil {
|
|
| 508 |
+ return "", "", err |
|
| 509 |
+ } |
|
| 510 |
+ |
|
| 511 |
+ switch v := manifest.(type) {
|
|
| 512 |
+ case *schema1.SignedManifest: |
|
| 513 |
+ imageID, _, err = p.pullSchema1(ctx, manifestRef, v) |
|
| 514 |
+ if err != nil {
|
|
| 515 |
+ return "", "", err |
|
| 516 |
+ } |
|
| 517 |
+ case *schema2.DeserializedManifest: |
|
| 518 |
+ imageID, _, err = p.pullSchema2(ctx, manifestRef, v) |
|
| 519 |
+ if err != nil {
|
|
| 520 |
+ return "", "", err |
|
| 521 |
+ } |
|
| 522 |
+ default: |
|
| 523 |
+ return "", "", errors.New("unsupported manifest format")
|
|
| 524 |
+ } |
|
| 525 |
+ |
|
| 526 |
+ return imageID, manifestListDigest, err |
|
| 527 |
+} |
|
| 528 |
+ |
|
| 473 | 529 |
func (p *v2Puller) pullSchema2ImageConfig(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) {
|
| 474 | 530 |
blobs := p.repo.Blobs(ctx) |
| 475 | 531 |
configJSON, err = blobs.Get(ctx, dgst) |
| ... | ... |
@@ -494,6 +537,34 @@ func (p *v2Puller) pullSchema2ImageConfig(ctx context.Context, dgst digest.Diges |
| 494 | 494 |
return configJSON, nil |
| 495 | 495 |
} |
| 496 | 496 |
|
| 497 |
+// schema2ManifestDigest computes the manifest digest, and, if pulling by |
|
| 498 |
+// digest, ensures that it matches the requested digest. |
|
| 499 |
+func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (digest.Digest, error) {
|
|
| 500 |
+ _, canonical, err := mfst.Payload() |
|
| 501 |
+ if err != nil {
|
|
| 502 |
+ return "", err |
|
| 503 |
+ } |
|
| 504 |
+ |
|
| 505 |
+ // If pull by digest, then verify the manifest digest. |
|
| 506 |
+ if digested, isDigested := ref.(reference.Canonical); isDigested {
|
|
| 507 |
+ verifier, err := digest.NewDigestVerifier(digested.Digest()) |
|
| 508 |
+ if err != nil {
|
|
| 509 |
+ return "", err |
|
| 510 |
+ } |
|
| 511 |
+ if _, err := verifier.Write(canonical); err != nil {
|
|
| 512 |
+ return "", err |
|
| 513 |
+ } |
|
| 514 |
+ if !verifier.Verified() {
|
|
| 515 |
+ err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
|
|
| 516 |
+ logrus.Error(err) |
|
| 517 |
+ return "", err |
|
| 518 |
+ } |
|
| 519 |
+ return digested.Digest(), nil |
|
| 520 |
+ } |
|
| 521 |
+ |
|
| 522 |
+ return digest.FromBytes(canonical), nil |
|
| 523 |
+} |
|
| 524 |
+ |
|
| 497 | 525 |
// allowV1Fallback checks if the error is a possible reason to fallback to v1 |
| 498 | 526 |
// (even if confirmedV2 has been set already), and if so, wraps the error in |
| 499 | 527 |
// a fallbackError with confirmedV2 set to false. Otherwise, it returns the |