github.com/tompao/docker@v1.9.1/graph/pull_v2.go (about) 1 package graph 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/distribution" 13 "github.com/docker/distribution/digest" 14 "github.com/docker/distribution/manifest" 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/pkg/broadcaster" 17 "github.com/docker/docker/pkg/progressreader" 18 "github.com/docker/docker/pkg/streamformatter" 19 "github.com/docker/docker/pkg/stringid" 20 "github.com/docker/docker/registry" 21 "github.com/docker/docker/utils" 22 "golang.org/x/net/context" 23 ) 24 25 type v2Puller struct { 26 *TagStore 27 endpoint registry.APIEndpoint 28 config *ImagePullConfig 29 sf *streamformatter.StreamFormatter 30 repoInfo *registry.RepositoryInfo 31 repo distribution.Repository 32 sessionID string 33 } 34 35 func (p *v2Puller) Pull(tag string) (fallback bool, err error) { 36 // TODO(tiborvass): was ReceiveTimeout 37 p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") 38 if err != nil { 39 logrus.Warnf("Error getting v2 registry: %v", err) 40 return true, err 41 } 42 43 p.sessionID = stringid.GenerateRandomID() 44 45 if err := p.pullV2Repository(tag); err != nil { 46 if registry.ContinueOnError(err) { 47 logrus.Debugf("Error trying v2 registry: %v", err) 48 return true, err 49 } 50 return false, err 51 } 52 return false, nil 53 } 54 55 func (p *v2Puller) pullV2Repository(tag string) (err error) { 56 var tags []string 57 taggedName := p.repoInfo.LocalName 58 if len(tag) > 0 { 59 tags = []string{tag} 60 taggedName = utils.ImageReference(p.repoInfo.LocalName, tag) 61 } else { 62 var err error 63 64 manSvc, err := p.repo.Manifests(context.Background()) 65 if err != nil { 66 return err 67 } 68 69 tags, err = manSvc.Tags() 70 if err != nil { 71 return err 72 } 73 74 } 75 76 poolKey := "v2:" + taggedName 77 broadcaster, found := p.poolAdd("pull", poolKey) 78 broadcaster.Add(p.config.OutStream) 79 if found { 80 // Another pull of the same repository is already taking place; just wait for it to finish 81 return broadcaster.Wait() 82 } 83 84 // This must use a closure so it captures the value of err when the 85 // function returns, not when the 'defer' is evaluated. 86 defer func() { 87 p.poolRemoveWithError("pull", poolKey, err) 88 }() 89 90 var layersDownloaded bool 91 for _, tag := range tags { 92 // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged 93 // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? 94 pulledNew, err := p.pullV2Tag(broadcaster, tag, taggedName) 95 if err != nil { 96 return err 97 } 98 layersDownloaded = layersDownloaded || pulledNew 99 } 100 101 writeStatus(taggedName, broadcaster, p.sf, layersDownloaded) 102 103 return nil 104 } 105 106 // downloadInfo is used to pass information from download to extractor 107 type downloadInfo struct { 108 img contentAddressableDescriptor 109 imgIndex int 110 tmpFile *os.File 111 digest digest.Digest 112 layer distribution.ReadSeekCloser 113 size int64 114 err chan error 115 poolKey string 116 broadcaster *broadcaster.Buffered 117 } 118 119 // contentAddressableDescriptor is used to pass image data from a manifest to the 120 // graph. 121 type contentAddressableDescriptor struct { 122 id string 123 parent string 124 strongID digest.Digest 125 compatibilityID string 126 config []byte 127 v1Compatibility []byte 128 } 129 130 func newContentAddressableImage(v1Compatibility []byte, blobSum digest.Digest, parent digest.Digest) (contentAddressableDescriptor, error) { 131 img := contentAddressableDescriptor{ 132 v1Compatibility: v1Compatibility, 133 } 134 135 var err error 136 img.config, err = image.MakeImageConfig(v1Compatibility, blobSum, parent) 137 if err != nil { 138 return img, err 139 } 140 img.strongID, err = image.StrongID(img.config) 141 if err != nil { 142 return img, err 143 } 144 145 unmarshalledConfig, err := image.NewImgJSON(v1Compatibility) 146 if err != nil { 147 return img, err 148 } 149 150 img.compatibilityID = unmarshalledConfig.ID 151 img.id = img.strongID.Hex() 152 153 return img, nil 154 } 155 156 // ID returns the actual ID to be used for the downloaded image. This may be 157 // a computed ID. 158 func (img contentAddressableDescriptor) ID() string { 159 return img.id 160 } 161 162 // Parent returns the parent ID to be used for the image. This may be a 163 // computed ID. 164 func (img contentAddressableDescriptor) Parent() string { 165 return img.parent 166 } 167 168 // MarshalConfig renders the image structure into JSON. 169 func (img contentAddressableDescriptor) MarshalConfig() ([]byte, error) { 170 return img.config, nil 171 } 172 173 type errVerification struct{} 174 175 func (errVerification) Error() string { return "verification failed" } 176 177 func (p *v2Puller) download(di *downloadInfo) { 178 logrus.Debugf("pulling blob %q to %s", di.digest, di.img.id) 179 180 blobs := p.repo.Blobs(context.Background()) 181 182 desc, err := blobs.Stat(context.Background(), di.digest) 183 if err != nil { 184 logrus.Debugf("Error statting layer: %v", err) 185 di.err <- err 186 return 187 } 188 di.size = desc.Size 189 190 layerDownload, err := blobs.Open(context.Background(), di.digest) 191 if err != nil { 192 logrus.Debugf("Error fetching layer: %v", err) 193 di.err <- err 194 return 195 } 196 defer layerDownload.Close() 197 198 verifier, err := digest.NewDigestVerifier(di.digest) 199 if err != nil { 200 di.err <- err 201 return 202 } 203 204 reader := progressreader.New(progressreader.Config{ 205 In: ioutil.NopCloser(io.TeeReader(layerDownload, verifier)), 206 Out: di.broadcaster, 207 Formatter: p.sf, 208 Size: di.size, 209 NewLines: false, 210 ID: stringid.TruncateID(di.img.id), 211 Action: "Downloading", 212 }) 213 io.Copy(di.tmpFile, reader) 214 215 di.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Verifying Checksum", nil)) 216 217 if !verifier.Verified() { 218 err = fmt.Errorf("filesystem layer verification failed for digest %s", di.digest) 219 logrus.Error(err) 220 di.err <- err 221 return 222 } 223 224 di.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Download complete", nil)) 225 226 logrus.Debugf("Downloaded %s to tempfile %s", di.img.id, di.tmpFile.Name()) 227 di.layer = layerDownload 228 229 di.err <- nil 230 } 231 232 func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (tagUpdated bool, err error) { 233 logrus.Debugf("Pulling tag from V2 registry: %q", tag) 234 235 manSvc, err := p.repo.Manifests(context.Background()) 236 if err != nil { 237 return false, err 238 } 239 240 unverifiedManifest, err := manSvc.GetByTag(tag) 241 if err != nil { 242 return false, err 243 } 244 if unverifiedManifest == nil { 245 return false, fmt.Errorf("image manifest does not exist for tag %q", tag) 246 } 247 var verifiedManifest *manifest.Manifest 248 verifiedManifest, err = verifyManifest(unverifiedManifest, tag) 249 if err != nil { 250 return false, err 251 } 252 253 // remove duplicate layers and check parent chain validity 254 err = fixManifestLayers(verifiedManifest) 255 if err != nil { 256 return false, err 257 } 258 259 imgs, err := p.getImageInfos(verifiedManifest) 260 if err != nil { 261 return false, err 262 } 263 264 out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name())) 265 266 var downloads []*downloadInfo 267 268 var layerIDs []string 269 defer func() { 270 p.graph.Release(p.sessionID, layerIDs...) 271 272 for _, d := range downloads { 273 p.poolRemoveWithError("pull", d.poolKey, err) 274 if d.tmpFile != nil { 275 d.tmpFile.Close() 276 if err := os.RemoveAll(d.tmpFile.Name()); err != nil { 277 logrus.Errorf("Failed to remove temp file: %s", d.tmpFile.Name()) 278 } 279 } 280 } 281 }() 282 283 for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- { 284 img := imgs[i] 285 286 p.graph.Retain(p.sessionID, img.id) 287 layerIDs = append(layerIDs, img.id) 288 289 p.graph.imageMutex.Lock(img.id) 290 291 // Check if exists 292 if p.graph.Exists(img.id) { 293 if err := p.validateImageInGraph(img.id, imgs, i); err != nil { 294 p.graph.imageMutex.Unlock(img.id) 295 return false, fmt.Errorf("image validation failed: %v", err) 296 } 297 logrus.Debugf("Image already exists: %s", img.id) 298 p.graph.imageMutex.Unlock(img.id) 299 continue 300 } 301 p.graph.imageMutex.Unlock(img.id) 302 303 out.Write(p.sf.FormatProgress(stringid.TruncateID(img.id), "Pulling fs layer", nil)) 304 305 d := &downloadInfo{ 306 img: img, 307 imgIndex: i, 308 poolKey: "v2layer:" + img.id, 309 digest: verifiedManifest.FSLayers[i].BlobSum, 310 // TODO: seems like this chan buffer solved hanging problem in go1.5, 311 // this can indicate some deeper problem that somehow we never take 312 // error from channel in loop below 313 err: make(chan error, 1), 314 } 315 316 tmpFile, err := ioutil.TempFile("", "GetImageBlob") 317 if err != nil { 318 return false, err 319 } 320 d.tmpFile = tmpFile 321 322 downloads = append(downloads, d) 323 324 broadcaster, found := p.poolAdd("pull", d.poolKey) 325 broadcaster.Add(out) 326 d.broadcaster = broadcaster 327 if found { 328 d.err <- nil 329 } else { 330 go p.download(d) 331 } 332 } 333 334 for _, d := range downloads { 335 if err := <-d.err; err != nil { 336 return false, err 337 } 338 339 if d.layer == nil { 340 // Wait for a different pull to download and extract 341 // this layer. 342 err = d.broadcaster.Wait() 343 if err != nil { 344 return false, err 345 } 346 continue 347 } 348 349 d.tmpFile.Seek(0, 0) 350 err := func() error { 351 reader := progressreader.New(progressreader.Config{ 352 In: d.tmpFile, 353 Out: d.broadcaster, 354 Formatter: p.sf, 355 Size: d.size, 356 NewLines: false, 357 ID: stringid.TruncateID(d.img.id), 358 Action: "Extracting", 359 }) 360 361 p.graph.imagesMutex.Lock() 362 defer p.graph.imagesMutex.Unlock() 363 364 p.graph.imageMutex.Lock(d.img.id) 365 defer p.graph.imageMutex.Unlock(d.img.id) 366 367 // Must recheck the data on disk if any exists. 368 // This protects against races where something 369 // else is written to the graph under this ID 370 // after attemptIDReuse. 371 if p.graph.Exists(d.img.id) { 372 if err := p.validateImageInGraph(d.img.id, imgs, d.imgIndex); err != nil { 373 return fmt.Errorf("image validation failed: %v", err) 374 } 375 } 376 377 if err := p.graph.register(d.img, reader); err != nil { 378 return err 379 } 380 381 if err := p.graph.setLayerDigest(d.img.id, d.digest); err != nil { 382 return err 383 } 384 385 if err := p.graph.setV1CompatibilityConfig(d.img.id, d.img.v1Compatibility); err != nil { 386 return err 387 } 388 389 return nil 390 }() 391 if err != nil { 392 return false, err 393 } 394 395 d.broadcaster.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.id), "Pull complete", nil)) 396 d.broadcaster.Close() 397 tagUpdated = true 398 } 399 400 manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo.LocalName) 401 if err != nil { 402 return false, err 403 } 404 405 // Check for new tag if no layers downloaded 406 if !tagUpdated { 407 repo, err := p.Get(p.repoInfo.LocalName) 408 if err != nil { 409 return false, err 410 } 411 if repo != nil { 412 if _, exists := repo[tag]; !exists { 413 tagUpdated = true 414 } 415 } else { 416 tagUpdated = true 417 } 418 } 419 420 firstID := layerIDs[len(layerIDs)-1] 421 if utils.DigestReference(tag) { 422 // TODO(stevvooe): Ideally, we should always set the digest so we can 423 // use the digest whether we pull by it or not. Unfortunately, the tag 424 // store treats the digest as a separate tag, meaning there may be an 425 // untagged digest image that would seem to be dangling by a user. 426 if err = p.SetDigest(p.repoInfo.LocalName, tag, firstID); err != nil { 427 return false, err 428 } 429 } else { 430 // only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest) 431 if err = p.Tag(p.repoInfo.LocalName, tag, firstID, true); err != nil { 432 return false, err 433 } 434 } 435 436 if manifestDigest != "" { 437 out.Write(p.sf.FormatStatus("", "Digest: %s", manifestDigest)) 438 } 439 440 return tagUpdated, nil 441 } 442 443 func verifyManifest(signedManifest *manifest.SignedManifest, tag string) (m *manifest.Manifest, err error) { 444 // If pull by digest, then verify the manifest digest. NOTE: It is 445 // important to do this first, before any other content validation. If the 446 // digest cannot be verified, don't even bother with those other things. 447 if manifestDigest, err := digest.ParseDigest(tag); err == nil { 448 verifier, err := digest.NewDigestVerifier(manifestDigest) 449 if err != nil { 450 return nil, err 451 } 452 payload, err := signedManifest.Payload() 453 if err != nil { 454 // If this failed, the signatures section was corrupted 455 // or missing. Treat the entire manifest as the payload. 456 payload = signedManifest.Raw 457 } 458 if _, err := verifier.Write(payload); err != nil { 459 return nil, err 460 } 461 if !verifier.Verified() { 462 err := fmt.Errorf("image verification failed for digest %s", manifestDigest) 463 logrus.Error(err) 464 return nil, err 465 } 466 467 var verifiedManifest manifest.Manifest 468 if err = json.Unmarshal(payload, &verifiedManifest); err != nil { 469 return nil, err 470 } 471 m = &verifiedManifest 472 } else { 473 m = &signedManifest.Manifest 474 } 475 476 if m.SchemaVersion != 1 { 477 return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag) 478 } 479 if len(m.FSLayers) != len(m.History) { 480 return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag) 481 } 482 if len(m.FSLayers) == 0 { 483 return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag) 484 } 485 return m, nil 486 } 487 488 // fixManifestLayers removes repeated layers from the manifest and checks the 489 // correctness of the parent chain. 490 func fixManifestLayers(m *manifest.Manifest) error { 491 images := make([]*image.Image, len(m.FSLayers)) 492 for i := range m.FSLayers { 493 img, err := image.NewImgJSON([]byte(m.History[i].V1Compatibility)) 494 if err != nil { 495 return err 496 } 497 images[i] = img 498 if err := image.ValidateID(img.ID); err != nil { 499 return err 500 } 501 } 502 503 if images[len(images)-1].Parent != "" { 504 return errors.New("Invalid parent ID in the base layer of the image.") 505 } 506 507 // check general duplicates to error instead of a deadlock 508 idmap := make(map[string]struct{}) 509 510 var lastID string 511 for _, img := range images { 512 // skip IDs that appear after each other, we handle those later 513 if _, exists := idmap[img.ID]; img.ID != lastID && exists { 514 return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID) 515 } 516 lastID = img.ID 517 idmap[lastID] = struct{}{} 518 } 519 520 // backwards loop so that we keep the remaining indexes after removing items 521 for i := len(images) - 2; i >= 0; i-- { 522 if images[i].ID == images[i+1].ID { // repeated ID. remove and continue 523 m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...) 524 m.History = append(m.History[:i], m.History[i+1:]...) 525 } else if images[i].Parent != images[i+1].ID { 526 return fmt.Errorf("Invalid parent ID. Expected %v, got %v.", images[i+1].ID, images[i].Parent) 527 } 528 } 529 530 return nil 531 } 532 533 // getImageInfos returns an imageinfo struct for every image in the manifest. 534 // These objects contain both calculated strongIDs and compatibilityIDs found 535 // in v1Compatibility object. 536 func (p *v2Puller) getImageInfos(m *manifest.Manifest) ([]contentAddressableDescriptor, error) { 537 imgs := make([]contentAddressableDescriptor, len(m.FSLayers)) 538 539 var parent digest.Digest 540 for i := len(imgs) - 1; i >= 0; i-- { 541 var err error 542 imgs[i], err = newContentAddressableImage([]byte(m.History[i].V1Compatibility), m.FSLayers[i].BlobSum, parent) 543 if err != nil { 544 return nil, err 545 } 546 parent = imgs[i].strongID 547 } 548 549 p.attemptIDReuse(imgs) 550 551 return imgs, nil 552 } 553 554 // attemptIDReuse does a best attempt to match verified compatibilityIDs 555 // already in the graph with the computed strongIDs so we can keep using them. 556 // This process will never fail but may just return the strongIDs if none of 557 // the compatibilityIDs exists or can be verified. If the strongIDs themselves 558 // fail verification, we deterministically generate alternate IDs to use until 559 // we find one that's available or already exists with the correct data. 560 func (p *v2Puller) attemptIDReuse(imgs []contentAddressableDescriptor) { 561 // This function needs to be protected with a global lock, because it 562 // locks multiple IDs at once, and there's no good way to make sure 563 // the locking happens a deterministic order. 564 p.graph.imagesMutex.Lock() 565 defer p.graph.imagesMutex.Unlock() 566 567 idMap := make(map[string]struct{}) 568 for _, img := range imgs { 569 idMap[img.id] = struct{}{} 570 idMap[img.compatibilityID] = struct{}{} 571 572 if p.graph.Exists(img.compatibilityID) { 573 if _, err := p.graph.GenerateV1CompatibilityChain(img.compatibilityID); err != nil { 574 logrus.Debugf("Migration v1Compatibility generation error: %v", err) 575 return 576 } 577 } 578 } 579 for id := range idMap { 580 p.graph.imageMutex.Lock(id) 581 defer p.graph.imageMutex.Unlock(id) 582 } 583 584 // continueReuse controls whether the function will try to find 585 // existing layers on disk under the old v1 IDs, to avoid repulling 586 // them. The hashes are checked to ensure these layers are okay to 587 // use. continueReuse starts out as true, but is set to false if 588 // the code encounters something that doesn't match the expected hash. 589 continueReuse := true 590 591 for i := len(imgs) - 1; i >= 0; i-- { 592 if p.graph.Exists(imgs[i].id) { 593 // Found an image in the graph under the strongID. Validate the 594 // image before using it. 595 if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil { 596 continueReuse = false 597 logrus.Debugf("not using existing strongID: %v", err) 598 599 // The strong ID existed in the graph but didn't 600 // validate successfully. We can't use the strong ID 601 // because it didn't validate successfully. Treat the 602 // graph like a hash table with probing... compute 603 // SHA256(id) until we find an ID that either doesn't 604 // already exist in the graph, or has existing content 605 // that validates successfully. 606 for { 607 if err := p.tryNextID(imgs, i, idMap); err != nil { 608 logrus.Debug(err.Error()) 609 } else { 610 break 611 } 612 } 613 } 614 continue 615 } 616 617 if continueReuse { 618 compatibilityID := imgs[i].compatibilityID 619 if err := p.validateImageInGraph(compatibilityID, imgs, i); err != nil { 620 logrus.Debugf("stopping ID reuse: %v", err) 621 continueReuse = false 622 } else { 623 // The compatibility ID exists in the graph and was 624 // validated. Use it. 625 imgs[i].id = compatibilityID 626 } 627 } 628 } 629 630 // fix up the parents of the images 631 for i := 0; i < len(imgs); i++ { 632 if i == len(imgs)-1 { // Base layer 633 imgs[i].parent = "" 634 } else { 635 imgs[i].parent = imgs[i+1].id 636 } 637 } 638 } 639 640 // validateImageInGraph checks that an image in the graph has the expected 641 // strongID. id is the entry in the graph to check, imgs is the slice of 642 // images being processed (for access to the parent), and i is the index 643 // into this slice which the graph entry should be checked against. 644 func (p *v2Puller) validateImageInGraph(id string, imgs []contentAddressableDescriptor, i int) error { 645 img, err := p.graph.Get(id) 646 if err != nil { 647 return fmt.Errorf("missing: %v", err) 648 } 649 layerID, err := p.graph.getLayerDigest(id) 650 if err != nil { 651 return fmt.Errorf("digest: %v", err) 652 } 653 var parentID digest.Digest 654 if i != len(imgs)-1 { 655 if img.Parent != imgs[i+1].id { // comparing that graph points to validated ID 656 return fmt.Errorf("parent: %v %v", img.Parent, imgs[i+1].id) 657 } 658 parentID = imgs[i+1].strongID 659 } else if img.Parent != "" { 660 return fmt.Errorf("unexpected parent: %v", img.Parent) 661 } 662 663 v1Config, err := p.graph.getV1CompatibilityConfig(img.ID) 664 if err != nil { 665 return fmt.Errorf("v1Compatibility: %v %v", img.ID, err) 666 } 667 668 json, err := image.MakeImageConfig(v1Config, layerID, parentID) 669 if err != nil { 670 return fmt.Errorf("make config: %v", err) 671 } 672 673 if dgst, err := image.StrongID(json); err == nil && dgst == imgs[i].strongID { 674 logrus.Debugf("Validated %v as %v", dgst, id) 675 } else { 676 return fmt.Errorf("digest mismatch: %v %v, error: %v", dgst, imgs[i].strongID, err) 677 } 678 679 // All clear 680 return nil 681 } 682 683 func (p *v2Puller) tryNextID(imgs []contentAddressableDescriptor, i int, idMap map[string]struct{}) error { 684 nextID, _ := digest.FromBytes([]byte(imgs[i].id)) 685 imgs[i].id = nextID.Hex() 686 687 if _, exists := idMap[imgs[i].id]; !exists { 688 p.graph.imageMutex.Lock(imgs[i].id) 689 defer p.graph.imageMutex.Unlock(imgs[i].id) 690 } 691 692 if p.graph.Exists(imgs[i].id) { 693 if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil { 694 return fmt.Errorf("not using existing strongID permutation %s: %v", imgs[i].id, err) 695 } 696 } 697 return nil 698 }