github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/distribution/pull_v2.go (about) 1 package distribution // import "github.com/docker/docker/distribution" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/url" 10 "os" 11 "runtime" 12 "strings" 13 14 "github.com/containerd/containerd/platforms" 15 "github.com/docker/distribution" 16 "github.com/docker/distribution/manifest/manifestlist" 17 "github.com/docker/distribution/manifest/schema1" 18 "github.com/docker/distribution/manifest/schema2" 19 "github.com/docker/distribution/reference" 20 "github.com/docker/distribution/registry/api/errcode" 21 "github.com/docker/distribution/registry/client/auth" 22 "github.com/docker/distribution/registry/client/transport" 23 "github.com/docker/docker/distribution/metadata" 24 "github.com/docker/docker/distribution/xfer" 25 "github.com/docker/docker/image" 26 "github.com/docker/docker/image/v1" 27 "github.com/docker/docker/layer" 28 "github.com/docker/docker/pkg/ioutils" 29 "github.com/docker/docker/pkg/progress" 30 "github.com/docker/docker/pkg/stringid" 31 "github.com/docker/docker/pkg/system" 32 refstore "github.com/docker/docker/reference" 33 "github.com/docker/docker/registry" 34 "github.com/opencontainers/go-digest" 35 specs "github.com/opencontainers/image-spec/specs-go/v1" 36 "github.com/pkg/errors" 37 "github.com/sirupsen/logrus" 38 ) 39 40 var ( 41 errRootFSMismatch = errors.New("layers from manifest don't match image configuration") 42 errRootFSInvalid = errors.New("invalid rootfs in image configuration") 43 ) 44 45 // ImageConfigPullError is an error pulling the image config blob 46 // (only applies to schema2). 47 type ImageConfigPullError struct { 48 Err error 49 } 50 51 // Error returns the error string for ImageConfigPullError. 52 func (e ImageConfigPullError) Error() string { 53 return "error pulling image configuration: " + e.Err.Error() 54 } 55 56 type v2Puller struct { 57 V2MetadataService metadata.V2MetadataService 58 endpoint registry.APIEndpoint 59 config *ImagePullConfig 60 repoInfo *registry.RepositoryInfo 61 repo distribution.Repository 62 // confirmedV2 is set to true if we confirm we're talking to a v2 63 // registry. This is used to limit fallbacks to the v1 protocol. 64 confirmedV2 bool 65 } 66 67 func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) { 68 // TODO(tiborvass): was ReceiveTimeout 69 p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") 70 if err != nil { 71 logrus.Warnf("Error getting v2 registry: %v", err) 72 return err 73 } 74 75 if err = p.pullV2Repository(ctx, ref, platform); err != nil { 76 if _, ok := err.(fallbackError); ok { 77 return err 78 } 79 if continueOnError(err, p.endpoint.Mirror) { 80 return fallbackError{ 81 err: err, 82 confirmedV2: p.confirmedV2, 83 transportOK: true, 84 } 85 } 86 } 87 return err 88 } 89 90 func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) { 91 var layersDownloaded bool 92 if !reference.IsNameOnly(ref) { 93 layersDownloaded, err = p.pullV2Tag(ctx, ref, platform) 94 if err != nil { 95 return err 96 } 97 } else { 98 tags, err := p.repo.Tags(ctx).All(ctx) 99 if err != nil { 100 // If this repository doesn't exist on V2, we should 101 // permit a fallback to V1. 102 return allowV1Fallback(err) 103 } 104 105 // The v2 registry knows about this repository, so we will not 106 // allow fallback to the v1 protocol even if we encounter an 107 // error later on. 108 p.confirmedV2 = true 109 110 for _, tag := range tags { 111 tagRef, err := reference.WithTag(ref, tag) 112 if err != nil { 113 return err 114 } 115 pulledNew, err := p.pullV2Tag(ctx, tagRef, platform) 116 if err != nil { 117 // Since this is the pull-all-tags case, don't 118 // allow an error pulling a particular tag to 119 // make the whole pull fall back to v1. 120 if fallbackErr, ok := err.(fallbackError); ok { 121 return fallbackErr.err 122 } 123 return err 124 } 125 // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged 126 // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? 127 layersDownloaded = layersDownloaded || pulledNew 128 } 129 } 130 131 writeStatus(reference.FamiliarString(ref), p.config.ProgressOutput, layersDownloaded) 132 133 return nil 134 } 135 136 type v2LayerDescriptor struct { 137 digest digest.Digest 138 diffID layer.DiffID 139 repoInfo *registry.RepositoryInfo 140 repo distribution.Repository 141 V2MetadataService metadata.V2MetadataService 142 tmpFile *os.File 143 verifier digest.Verifier 144 src distribution.Descriptor 145 } 146 147 func (ld *v2LayerDescriptor) Key() string { 148 return "v2:" + ld.digest.String() 149 } 150 151 func (ld *v2LayerDescriptor) ID() string { 152 return stringid.TruncateID(ld.digest.String()) 153 } 154 155 func (ld *v2LayerDescriptor) DiffID() (layer.DiffID, error) { 156 if ld.diffID != "" { 157 return ld.diffID, nil 158 } 159 return ld.V2MetadataService.GetDiffID(ld.digest) 160 } 161 162 func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) { 163 logrus.Debugf("pulling blob %q", ld.digest) 164 165 var ( 166 err error 167 offset int64 168 ) 169 170 if ld.tmpFile == nil { 171 ld.tmpFile, err = createDownloadFile() 172 if err != nil { 173 return nil, 0, xfer.DoNotRetry{Err: err} 174 } 175 } else { 176 offset, err = ld.tmpFile.Seek(0, os.SEEK_END) 177 if err != nil { 178 logrus.Debugf("error seeking to end of download file: %v", err) 179 offset = 0 180 181 ld.tmpFile.Close() 182 if err := os.Remove(ld.tmpFile.Name()); err != nil { 183 logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name()) 184 } 185 ld.tmpFile, err = createDownloadFile() 186 if err != nil { 187 return nil, 0, xfer.DoNotRetry{Err: err} 188 } 189 } else if offset != 0 { 190 logrus.Debugf("attempting to resume download of %q from %d bytes", ld.digest, offset) 191 } 192 } 193 194 tmpFile := ld.tmpFile 195 196 layerDownload, err := ld.open(ctx) 197 if err != nil { 198 logrus.Errorf("Error initiating layer download: %v", err) 199 return nil, 0, retryOnError(err) 200 } 201 202 if offset != 0 { 203 _, err := layerDownload.Seek(offset, os.SEEK_SET) 204 if err != nil { 205 if err := ld.truncateDownloadFile(); err != nil { 206 return nil, 0, xfer.DoNotRetry{Err: err} 207 } 208 return nil, 0, err 209 } 210 } 211 size, err := layerDownload.Seek(0, os.SEEK_END) 212 if err != nil { 213 // Seek failed, perhaps because there was no Content-Length 214 // header. This shouldn't fail the download, because we can 215 // still continue without a progress bar. 216 size = 0 217 } else { 218 if size != 0 && offset > size { 219 logrus.Debug("Partial download is larger than full blob. Starting over") 220 offset = 0 221 if err := ld.truncateDownloadFile(); err != nil { 222 return nil, 0, xfer.DoNotRetry{Err: err} 223 } 224 } 225 226 // Restore the seek offset either at the beginning of the 227 // stream, or just after the last byte we have from previous 228 // attempts. 229 _, err = layerDownload.Seek(offset, os.SEEK_SET) 230 if err != nil { 231 return nil, 0, err 232 } 233 } 234 235 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerDownload), progressOutput, size-offset, ld.ID(), "Downloading") 236 defer reader.Close() 237 238 if ld.verifier == nil { 239 ld.verifier = ld.digest.Verifier() 240 } 241 242 _, err = io.Copy(tmpFile, io.TeeReader(reader, ld.verifier)) 243 if err != nil { 244 if err == transport.ErrWrongCodeForByteRange { 245 if err := ld.truncateDownloadFile(); err != nil { 246 return nil, 0, xfer.DoNotRetry{Err: err} 247 } 248 return nil, 0, err 249 } 250 return nil, 0, retryOnError(err) 251 } 252 253 progress.Update(progressOutput, ld.ID(), "Verifying Checksum") 254 255 if !ld.verifier.Verified() { 256 err = fmt.Errorf("filesystem layer verification failed for digest %s", ld.digest) 257 logrus.Error(err) 258 259 // Allow a retry if this digest verification error happened 260 // after a resumed download. 261 if offset != 0 { 262 if err := ld.truncateDownloadFile(); err != nil { 263 return nil, 0, xfer.DoNotRetry{Err: err} 264 } 265 266 return nil, 0, err 267 } 268 return nil, 0, xfer.DoNotRetry{Err: err} 269 } 270 271 progress.Update(progressOutput, ld.ID(), "Download complete") 272 273 logrus.Debugf("Downloaded %s to tempfile %s", ld.ID(), tmpFile.Name()) 274 275 _, err = tmpFile.Seek(0, os.SEEK_SET) 276 if err != nil { 277 tmpFile.Close() 278 if err := os.Remove(tmpFile.Name()); err != nil { 279 logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name()) 280 } 281 ld.tmpFile = nil 282 ld.verifier = nil 283 return nil, 0, xfer.DoNotRetry{Err: err} 284 } 285 286 // hand off the temporary file to the download manager, so it will only 287 // be closed once 288 ld.tmpFile = nil 289 290 return ioutils.NewReadCloserWrapper(tmpFile, func() error { 291 tmpFile.Close() 292 err := os.RemoveAll(tmpFile.Name()) 293 if err != nil { 294 logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name()) 295 } 296 return err 297 }), size, nil 298 } 299 300 func (ld *v2LayerDescriptor) Close() { 301 if ld.tmpFile != nil { 302 ld.tmpFile.Close() 303 if err := os.RemoveAll(ld.tmpFile.Name()); err != nil { 304 logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name()) 305 } 306 } 307 } 308 309 func (ld *v2LayerDescriptor) truncateDownloadFile() error { 310 // Need a new hash context since we will be redoing the download 311 ld.verifier = nil 312 313 if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil { 314 logrus.Errorf("error seeking to beginning of download file: %v", err) 315 return err 316 } 317 318 if err := ld.tmpFile.Truncate(0); err != nil { 319 logrus.Errorf("error truncating download file: %v", err) 320 return err 321 } 322 323 return nil 324 } 325 326 func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) { 327 // Cache mapping from this layer's DiffID to the blobsum 328 ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()}) 329 } 330 331 func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform *specs.Platform) (tagUpdated bool, err error) { 332 manSvc, err := p.repo.Manifests(ctx) 333 if err != nil { 334 return false, err 335 } 336 337 var ( 338 manifest distribution.Manifest 339 tagOrDigest string // Used for logging/progress only 340 ) 341 if digested, isDigested := ref.(reference.Canonical); isDigested { 342 manifest, err = manSvc.Get(ctx, digested.Digest()) 343 if err != nil { 344 return false, err 345 } 346 tagOrDigest = digested.Digest().String() 347 } else if tagged, isTagged := ref.(reference.NamedTagged); isTagged { 348 manifest, err = manSvc.Get(ctx, "", distribution.WithTag(tagged.Tag())) 349 if err != nil { 350 return false, allowV1Fallback(err) 351 } 352 tagOrDigest = tagged.Tag() 353 } else { 354 return false, fmt.Errorf("internal error: reference has neither a tag nor a digest: %s", reference.FamiliarString(ref)) 355 } 356 357 if manifest == nil { 358 return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest) 359 } 360 361 if m, ok := manifest.(*schema2.DeserializedManifest); ok { 362 var allowedMediatype bool 363 for _, t := range p.config.Schema2Types { 364 if m.Manifest.Config.MediaType == t { 365 allowedMediatype = true 366 break 367 } 368 } 369 if !allowedMediatype { 370 configClass := mediaTypeClasses[m.Manifest.Config.MediaType] 371 if configClass == "" { 372 configClass = "unknown" 373 } 374 return false, invalidManifestClassError{m.Manifest.Config.MediaType, configClass} 375 } 376 } 377 378 // If manSvc.Get succeeded, we can be confident that the registry on 379 // the other side speaks the v2 protocol. 380 p.confirmedV2 = true 381 382 logrus.Debugf("Pulling ref from V2 registry: %s", reference.FamiliarString(ref)) 383 progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named())) 384 385 var ( 386 id digest.Digest 387 manifestDigest digest.Digest 388 ) 389 390 switch v := manifest.(type) { 391 case *schema1.SignedManifest: 392 if p.config.RequireSchema2 { 393 return false, fmt.Errorf("invalid manifest: not schema2") 394 } 395 id, manifestDigest, err = p.pullSchema1(ctx, ref, v, platform) 396 if err != nil { 397 return false, err 398 } 399 case *schema2.DeserializedManifest: 400 id, manifestDigest, err = p.pullSchema2(ctx, ref, v, platform) 401 if err != nil { 402 return false, err 403 } 404 case *manifestlist.DeserializedManifestList: 405 id, manifestDigest, err = p.pullManifestList(ctx, ref, v, platform) 406 if err != nil { 407 return false, err 408 } 409 default: 410 return false, invalidManifestFormatError{} 411 } 412 413 progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String()) 414 415 if p.config.ReferenceStore != nil { 416 oldTagID, err := p.config.ReferenceStore.Get(ref) 417 if err == nil { 418 if oldTagID == id { 419 return false, addDigestReference(p.config.ReferenceStore, ref, manifestDigest, id) 420 } 421 } else if err != refstore.ErrDoesNotExist { 422 return false, err 423 } 424 425 if canonical, ok := ref.(reference.Canonical); ok { 426 if err = p.config.ReferenceStore.AddDigest(canonical, id, true); err != nil { 427 return false, err 428 } 429 } else { 430 if err = addDigestReference(p.config.ReferenceStore, ref, manifestDigest, id); err != nil { 431 return false, err 432 } 433 if err = p.config.ReferenceStore.AddTag(ref, id, true); err != nil { 434 return false, err 435 } 436 } 437 } 438 return true, nil 439 } 440 441 func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) { 442 var verifiedManifest *schema1.Manifest 443 verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref) 444 if err != nil { 445 return "", "", err 446 } 447 448 rootFS := image.NewRootFS() 449 450 // remove duplicate layers and check parent chain validity 451 err = fixManifestLayers(verifiedManifest) 452 if err != nil { 453 return "", "", err 454 } 455 456 var descriptors []xfer.DownloadDescriptor 457 458 // Image history converted to the new format 459 var history []image.History 460 461 // Note that the order of this loop is in the direction of bottom-most 462 // to top-most, so that the downloads slice gets ordered correctly. 463 for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- { 464 blobSum := verifiedManifest.FSLayers[i].BlobSum 465 466 var throwAway struct { 467 ThrowAway bool `json:"throwaway,omitempty"` 468 } 469 if err := json.Unmarshal([]byte(verifiedManifest.History[i].V1Compatibility), &throwAway); err != nil { 470 return "", "", err 471 } 472 473 h, err := v1.HistoryFromConfig([]byte(verifiedManifest.History[i].V1Compatibility), throwAway.ThrowAway) 474 if err != nil { 475 return "", "", err 476 } 477 history = append(history, h) 478 479 if throwAway.ThrowAway { 480 continue 481 } 482 483 layerDescriptor := &v2LayerDescriptor{ 484 digest: blobSum, 485 repoInfo: p.repoInfo, 486 repo: p.repo, 487 V2MetadataService: p.V2MetadataService, 488 } 489 490 descriptors = append(descriptors, layerDescriptor) 491 } 492 493 // The v1 manifest itself doesn't directly contain an OS. However, 494 // the history does, but unfortunately that's a string, so search through 495 // all the history until hopefully we find one which indicates the OS. 496 // supertest2014/nyan is an example of a registry image with schemav1. 497 configOS := runtime.GOOS 498 if system.LCOWSupported() { 499 type config struct { 500 Os string `json:"os,omitempty"` 501 } 502 for _, v := range verifiedManifest.History { 503 var c config 504 if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil { 505 if c.Os != "" { 506 configOS = c.Os 507 break 508 } 509 } 510 } 511 } 512 513 // In the situation that the API call didn't specify an OS explicitly, but 514 // we support the operating system, switch to that operating system. 515 // eg FROM supertest2014/nyan with no platform specifier, and docker build 516 // with no --platform= flag under LCOW. 517 requestedOS := "" 518 if platform != nil { 519 requestedOS = platform.OS 520 } else if system.IsOSSupported(configOS) { 521 requestedOS = configOS 522 } 523 524 // Early bath if the requested OS doesn't match that of the configuration. 525 // This avoids doing the download, only to potentially fail later. 526 if !strings.EqualFold(configOS, requestedOS) { 527 return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS) 528 } 529 530 resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, configOS, descriptors, p.config.ProgressOutput) 531 if err != nil { 532 return "", "", err 533 } 534 defer release() 535 536 config, err := v1.MakeConfigFromV1Config([]byte(verifiedManifest.History[0].V1Compatibility), &resultRootFS, history) 537 if err != nil { 538 return "", "", err 539 } 540 541 imageID, err := p.config.ImageStore.Put(config) 542 if err != nil { 543 return "", "", err 544 } 545 546 manifestDigest = digest.FromBytes(unverifiedManifest.Canonical) 547 548 return imageID, manifestDigest, nil 549 } 550 551 func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) { 552 manifestDigest, err = schema2ManifestDigest(ref, mfst) 553 if err != nil { 554 return "", "", err 555 } 556 557 target := mfst.Target() 558 if _, err := p.config.ImageStore.Get(target.Digest); err == nil { 559 // If the image already exists locally, no need to pull 560 // anything. 561 return target.Digest, manifestDigest, nil 562 } 563 564 var descriptors []xfer.DownloadDescriptor 565 566 // Note that the order of this loop is in the direction of bottom-most 567 // to top-most, so that the downloads slice gets ordered correctly. 568 for _, d := range mfst.Layers { 569 layerDescriptor := &v2LayerDescriptor{ 570 digest: d.Digest, 571 repo: p.repo, 572 repoInfo: p.repoInfo, 573 V2MetadataService: p.V2MetadataService, 574 src: d, 575 } 576 577 descriptors = append(descriptors, layerDescriptor) 578 } 579 580 configChan := make(chan []byte, 1) 581 configErrChan := make(chan error, 1) 582 layerErrChan := make(chan error, 1) 583 downloadsDone := make(chan struct{}) 584 var cancel func() 585 ctx, cancel = context.WithCancel(ctx) 586 defer cancel() 587 588 // Pull the image config 589 go func() { 590 configJSON, err := p.pullSchema2Config(ctx, target.Digest) 591 if err != nil { 592 configErrChan <- ImageConfigPullError{Err: err} 593 cancel() 594 return 595 } 596 configChan <- configJSON 597 }() 598 599 var ( 600 configJSON []byte // raw serialized image config 601 downloadedRootFS *image.RootFS // rootFS from registered layers 602 configRootFS *image.RootFS // rootFS from configuration 603 release func() // release resources from rootFS download 604 configPlatform *specs.Platform // for LCOW when registering downloaded layers 605 ) 606 607 layerStoreOS := runtime.GOOS 608 if platform != nil { 609 layerStoreOS = platform.OS 610 } 611 612 // https://github.com/docker/docker/issues/24766 - Err on the side of caution, 613 // explicitly blocking images intended for linux from the Windows daemon. On 614 // Windows, we do this before the attempt to download, effectively serialising 615 // the download slightly slowing it down. We have to do it this way, as 616 // chances are the download of layers itself would fail due to file names 617 // which aren't suitable for NTFS. At some point in the future, if a similar 618 // check to block Windows images being pulled on Linux is implemented, it 619 // may be necessary to perform the same type of serialisation. 620 if runtime.GOOS == "windows" { 621 configJSON, configRootFS, configPlatform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan) 622 if err != nil { 623 return "", "", err 624 } 625 if configRootFS == nil { 626 return "", "", errRootFSInvalid 627 } 628 if err := checkImageCompatibility(configPlatform.OS, configPlatform.OSVersion); err != nil { 629 return "", "", err 630 } 631 632 if len(descriptors) != len(configRootFS.DiffIDs) { 633 return "", "", errRootFSMismatch 634 } 635 if platform == nil { 636 // Early bath if the requested OS doesn't match that of the configuration. 637 // This avoids doing the download, only to potentially fail later. 638 if !system.IsOSSupported(configPlatform.OS) { 639 return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, layerStoreOS) 640 } 641 layerStoreOS = configPlatform.OS 642 } 643 644 // Populate diff ids in descriptors to avoid downloading foreign layers 645 // which have been side loaded 646 for i := range descriptors { 647 descriptors[i].(*v2LayerDescriptor).diffID = configRootFS.DiffIDs[i] 648 } 649 } 650 651 if p.config.DownloadManager != nil { 652 go func() { 653 var ( 654 err error 655 rootFS image.RootFS 656 ) 657 downloadRootFS := *image.NewRootFS() 658 rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, layerStoreOS, descriptors, p.config.ProgressOutput) 659 if err != nil { 660 // Intentionally do not cancel the config download here 661 // as the error from config download (if there is one) 662 // is more interesting than the layer download error 663 layerErrChan <- err 664 return 665 } 666 667 downloadedRootFS = &rootFS 668 close(downloadsDone) 669 }() 670 } else { 671 // We have nothing to download 672 close(downloadsDone) 673 } 674 675 if configJSON == nil { 676 configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan) 677 if err == nil && configRootFS == nil { 678 err = errRootFSInvalid 679 } 680 if err != nil { 681 cancel() 682 select { 683 case <-downloadsDone: 684 case <-layerErrChan: 685 } 686 return "", "", err 687 } 688 } 689 690 select { 691 case <-downloadsDone: 692 case err = <-layerErrChan: 693 return "", "", err 694 } 695 696 if release != nil { 697 defer release() 698 } 699 700 if downloadedRootFS != nil { 701 // The DiffIDs returned in rootFS MUST match those in the config. 702 // Otherwise the image config could be referencing layers that aren't 703 // included in the manifest. 704 if len(downloadedRootFS.DiffIDs) != len(configRootFS.DiffIDs) { 705 return "", "", errRootFSMismatch 706 } 707 708 for i := range downloadedRootFS.DiffIDs { 709 if downloadedRootFS.DiffIDs[i] != configRootFS.DiffIDs[i] { 710 return "", "", errRootFSMismatch 711 } 712 } 713 } 714 715 imageID, err := p.config.ImageStore.Put(configJSON) 716 if err != nil { 717 return "", "", err 718 } 719 720 return imageID, manifestDigest, nil 721 } 722 723 func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, *specs.Platform, error) { 724 select { 725 case configJSON := <-configChan: 726 rootfs, err := s.RootFSFromConfig(configJSON) 727 if err != nil { 728 return nil, nil, nil, err 729 } 730 platform, err := s.PlatformFromConfig(configJSON) 731 if err != nil { 732 return nil, nil, nil, err 733 } 734 return configJSON, rootfs, platform, nil 735 case err := <-errChan: 736 return nil, nil, nil, err 737 // Don't need a case for ctx.Done in the select because cancellation 738 // will trigger an error in p.pullSchema2ImageConfig. 739 } 740 } 741 742 // pullManifestList handles "manifest lists" which point to various 743 // platform-specific manifests. 744 func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, pp *specs.Platform) (id digest.Digest, manifestListDigest digest.Digest, err error) { 745 manifestListDigest, err = schema2ManifestDigest(ref, mfstList) 746 if err != nil { 747 return "", "", err 748 } 749 750 var platform specs.Platform 751 if pp != nil { 752 platform = *pp 753 } 754 logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), platforms.Format(platform), runtime.GOARCH) 755 756 manifestMatches := filterManifests(mfstList.Manifests, platform) 757 758 if len(manifestMatches) == 0 { 759 errMsg := fmt.Sprintf("no matching manifest for %s in the manifest list entries", formatPlatform(platform)) 760 logrus.Debugf(errMsg) 761 return "", "", errors.New(errMsg) 762 } 763 764 if len(manifestMatches) > 1 { 765 logrus.Debugf("found multiple matches in manifest list, choosing best match %s", manifestMatches[0].Digest.String()) 766 } 767 manifestDigest := manifestMatches[0].Digest 768 769 if err := checkImageCompatibility(manifestMatches[0].Platform.OS, manifestMatches[0].Platform.OSVersion); err != nil { 770 return "", "", err 771 } 772 773 manSvc, err := p.repo.Manifests(ctx) 774 if err != nil { 775 return "", "", err 776 } 777 778 manifest, err := manSvc.Get(ctx, manifestDigest) 779 if err != nil { 780 return "", "", err 781 } 782 783 manifestRef, err := reference.WithDigest(reference.TrimNamed(ref), manifestDigest) 784 if err != nil { 785 return "", "", err 786 } 787 788 switch v := manifest.(type) { 789 case *schema1.SignedManifest: 790 platform := toOCIPlatform(manifestMatches[0].Platform) 791 id, _, err = p.pullSchema1(ctx, manifestRef, v, &platform) 792 if err != nil { 793 return "", "", err 794 } 795 case *schema2.DeserializedManifest: 796 platform := toOCIPlatform(manifestMatches[0].Platform) 797 id, _, err = p.pullSchema2(ctx, manifestRef, v, &platform) 798 if err != nil { 799 return "", "", err 800 } 801 default: 802 return "", "", errors.New("unsupported manifest format") 803 } 804 805 return id, manifestListDigest, err 806 } 807 808 func (p *v2Puller) pullSchema2Config(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) { 809 blobs := p.repo.Blobs(ctx) 810 configJSON, err = blobs.Get(ctx, dgst) 811 if err != nil { 812 return nil, err 813 } 814 815 // Verify image config digest 816 verifier := dgst.Verifier() 817 if _, err := verifier.Write(configJSON); err != nil { 818 return nil, err 819 } 820 if !verifier.Verified() { 821 err := fmt.Errorf("image config verification failed for digest %s", dgst) 822 logrus.Error(err) 823 return nil, err 824 } 825 826 return configJSON, nil 827 } 828 829 // schema2ManifestDigest computes the manifest digest, and, if pulling by 830 // digest, ensures that it matches the requested digest. 831 func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (digest.Digest, error) { 832 _, canonical, err := mfst.Payload() 833 if err != nil { 834 return "", err 835 } 836 837 // If pull by digest, then verify the manifest digest. 838 if digested, isDigested := ref.(reference.Canonical); isDigested { 839 verifier := digested.Digest().Verifier() 840 if _, err := verifier.Write(canonical); err != nil { 841 return "", err 842 } 843 if !verifier.Verified() { 844 err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest()) 845 logrus.Error(err) 846 return "", err 847 } 848 return digested.Digest(), nil 849 } 850 851 return digest.FromBytes(canonical), nil 852 } 853 854 // allowV1Fallback checks if the error is a possible reason to fallback to v1 855 // (even if confirmedV2 has been set already), and if so, wraps the error in 856 // a fallbackError with confirmedV2 set to false. Otherwise, it returns the 857 // error unmodified. 858 func allowV1Fallback(err error) error { 859 switch v := err.(type) { 860 case errcode.Errors: 861 if len(v) != 0 { 862 if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) { 863 return fallbackError{ 864 err: err, 865 confirmedV2: false, 866 transportOK: true, 867 } 868 } 869 } 870 case errcode.Error: 871 if shouldV2Fallback(v) { 872 return fallbackError{ 873 err: err, 874 confirmedV2: false, 875 transportOK: true, 876 } 877 } 878 case *url.Error: 879 if v.Err == auth.ErrNoBasicAuthCredentials { 880 return fallbackError{err: err, confirmedV2: false} 881 } 882 } 883 884 return err 885 } 886 887 func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Reference) (m *schema1.Manifest, err error) { 888 // If pull by digest, then verify the manifest digest. NOTE: It is 889 // important to do this first, before any other content validation. If the 890 // digest cannot be verified, don't even bother with those other things. 891 if digested, isCanonical := ref.(reference.Canonical); isCanonical { 892 verifier := digested.Digest().Verifier() 893 if _, err := verifier.Write(signedManifest.Canonical); err != nil { 894 return nil, err 895 } 896 if !verifier.Verified() { 897 err := fmt.Errorf("image verification failed for digest %s", digested.Digest()) 898 logrus.Error(err) 899 return nil, err 900 } 901 } 902 m = &signedManifest.Manifest 903 904 if m.SchemaVersion != 1 { 905 return nil, fmt.Errorf("unsupported schema version %d for %q", m.SchemaVersion, reference.FamiliarString(ref)) 906 } 907 if len(m.FSLayers) != len(m.History) { 908 return nil, fmt.Errorf("length of history not equal to number of layers for %q", reference.FamiliarString(ref)) 909 } 910 if len(m.FSLayers) == 0 { 911 return nil, fmt.Errorf("no FSLayers in manifest for %q", reference.FamiliarString(ref)) 912 } 913 return m, nil 914 } 915 916 // fixManifestLayers removes repeated layers from the manifest and checks the 917 // correctness of the parent chain. 918 func fixManifestLayers(m *schema1.Manifest) error { 919 imgs := make([]*image.V1Image, len(m.FSLayers)) 920 for i := range m.FSLayers { 921 img := &image.V1Image{} 922 923 if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), img); err != nil { 924 return err 925 } 926 927 imgs[i] = img 928 if err := v1.ValidateID(img.ID); err != nil { 929 return err 930 } 931 } 932 933 if imgs[len(imgs)-1].Parent != "" && runtime.GOOS != "windows" { 934 // Windows base layer can point to a base layer parent that is not in manifest. 935 return errors.New("invalid parent ID in the base layer of the image") 936 } 937 938 // check general duplicates to error instead of a deadlock 939 idmap := make(map[string]struct{}) 940 941 var lastID string 942 for _, img := range imgs { 943 // skip IDs that appear after each other, we handle those later 944 if _, exists := idmap[img.ID]; img.ID != lastID && exists { 945 return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID) 946 } 947 lastID = img.ID 948 idmap[lastID] = struct{}{} 949 } 950 951 // backwards loop so that we keep the remaining indexes after removing items 952 for i := len(imgs) - 2; i >= 0; i-- { 953 if imgs[i].ID == imgs[i+1].ID { // repeated ID. remove and continue 954 m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...) 955 m.History = append(m.History[:i], m.History[i+1:]...) 956 } else if imgs[i].Parent != imgs[i+1].ID { 957 return fmt.Errorf("invalid parent ID. Expected %v, got %v", imgs[i+1].ID, imgs[i].Parent) 958 } 959 } 960 961 return nil 962 } 963 964 func createDownloadFile() (*os.File, error) { 965 return ioutil.TempFile("", "GetImageBlob") 966 } 967 968 func toOCIPlatform(p manifestlist.PlatformSpec) specs.Platform { 969 return specs.Platform{ 970 OS: p.OS, 971 Architecture: p.Architecture, 972 Variant: p.Variant, 973 OSFeatures: p.OSFeatures, 974 OSVersion: p.OSVersion, 975 } 976 }