github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/builder-next/adapters/containerimage/pull.go (about) 1 package containerimage 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "path" 10 "runtime" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/containerd/containerd/content" 16 containerderrors "github.com/containerd/containerd/errdefs" 17 "github.com/containerd/containerd/images" 18 "github.com/containerd/containerd/platforms" 19 ctdreference "github.com/containerd/containerd/reference" 20 "github.com/containerd/containerd/remotes" 21 "github.com/containerd/containerd/remotes/docker" 22 "github.com/containerd/containerd/remotes/docker/schema1" 23 distreference "github.com/docker/distribution/reference" 24 "github.com/demonoid81/moby/distribution" 25 "github.com/demonoid81/moby/distribution/metadata" 26 "github.com/demonoid81/moby/distribution/xfer" 27 "github.com/demonoid81/moby/image" 28 "github.com/demonoid81/moby/layer" 29 pkgprogress "github.com/demonoid81/moby/pkg/progress" 30 "github.com/demonoid81/moby/reference" 31 "github.com/moby/buildkit/cache" 32 "github.com/moby/buildkit/client/llb" 33 "github.com/moby/buildkit/session" 34 "github.com/moby/buildkit/source" 35 "github.com/moby/buildkit/util/flightcontrol" 36 "github.com/moby/buildkit/util/imageutil" 37 "github.com/moby/buildkit/util/progress" 38 "github.com/moby/buildkit/util/resolver" 39 digest "github.com/opencontainers/go-digest" 40 "github.com/opencontainers/image-spec/identity" 41 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 42 "github.com/pkg/errors" 43 "github.com/sirupsen/logrus" 44 "golang.org/x/time/rate" 45 ) 46 47 // SourceOpt is options for creating the image source 48 type SourceOpt struct { 49 ContentStore content.Store 50 CacheAccessor cache.Accessor 51 ReferenceStore reference.Store 52 DownloadManager distribution.RootFSDownloadManager 53 MetadataStore metadata.V2MetadataService 54 ImageStore image.Store 55 RegistryHosts docker.RegistryHosts 56 LayerStore layer.Store 57 } 58 59 type imageSource struct { 60 SourceOpt 61 g flightcontrol.Group 62 resolverCache *resolverCache 63 } 64 65 // NewSource creates a new image source 66 func NewSource(opt SourceOpt) (source.Source, error) { 67 is := &imageSource{ 68 SourceOpt: opt, 69 resolverCache: newResolverCache(), 70 } 71 72 return is, nil 73 } 74 75 func (is *imageSource) ID() string { 76 return source.DockerImageScheme 77 } 78 79 func (is *imageSource) getResolver(ctx context.Context, hosts docker.RegistryHosts, ref string, sm *session.Manager) remotes.Resolver { 80 if res := is.resolverCache.Get(ctx, ref); res != nil { 81 return res 82 } 83 r := resolver.New(ctx, hosts, sm) 84 r = is.resolverCache.Add(ctx, ref, r) 85 return r 86 } 87 88 func (is *imageSource) resolveLocal(refStr string) (*image.Image, error) { 89 ref, err := distreference.ParseNormalizedNamed(refStr) 90 if err != nil { 91 return nil, err 92 } 93 dgst, err := is.ReferenceStore.Get(ref) 94 if err != nil { 95 return nil, err 96 } 97 img, err := is.ImageStore.Get(image.ID(dgst)) 98 if err != nil { 99 return nil, err 100 } 101 return img, nil 102 } 103 104 func (is *imageSource) resolveRemote(ctx context.Context, ref string, platform *ocispec.Platform, sm *session.Manager) (digest.Digest, []byte, error) { 105 type t struct { 106 dgst digest.Digest 107 dt []byte 108 } 109 res, err := is.g.Do(ctx, ref, func(ctx context.Context) (interface{}, error) { 110 dgst, dt, err := imageutil.Config(ctx, ref, is.getResolver(ctx, is.RegistryHosts, ref, sm), is.ContentStore, nil, platform) 111 if err != nil { 112 return nil, err 113 } 114 return &t{dgst: dgst, dt: dt}, nil 115 }) 116 var typed *t 117 if err != nil { 118 return "", nil, err 119 } 120 typed = res.(*t) 121 return typed.dgst, typed.dt, nil 122 } 123 124 func (is *imageSource) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt, sm *session.Manager) (digest.Digest, []byte, error) { 125 resolveMode, err := source.ParseImageResolveMode(opt.ResolveMode) 126 if err != nil { 127 return "", nil, err 128 } 129 switch resolveMode { 130 case source.ResolveModeForcePull: 131 dgst, dt, err := is.resolveRemote(ctx, ref, opt.Platform, sm) 132 // TODO: pull should fallback to local in case of failure to allow offline behavior 133 // the fallback doesn't work currently 134 return dgst, dt, err 135 /* 136 if err == nil { 137 return dgst, dt, err 138 } 139 // fallback to local 140 dt, err = is.resolveLocal(ref) 141 return "", dt, err 142 */ 143 144 case source.ResolveModeDefault: 145 // default == prefer local, but in the future could be smarter 146 fallthrough 147 case source.ResolveModePreferLocal: 148 img, err := is.resolveLocal(ref) 149 if err == nil { 150 if !platformMatches(img, opt.Platform) { 151 logrus.WithField("ref", ref).Debugf("Requested build platform %s does not match local image platform %s, checking remote", 152 path.Join(opt.Platform.OS, opt.Platform.Architecture, opt.Platform.Variant), 153 path.Join(img.OS, img.Architecture, img.Variant), 154 ) 155 } else { 156 return "", img.RawJSON(), err 157 } 158 } 159 // fallback to remote 160 return is.resolveRemote(ctx, ref, opt.Platform, sm) 161 } 162 // should never happen 163 return "", nil, fmt.Errorf("builder cannot resolve image %s: invalid mode %q", ref, opt.ResolveMode) 164 } 165 166 func (is *imageSource) Resolve(ctx context.Context, id source.Identifier, sm *session.Manager) (source.SourceInstance, error) { 167 imageIdentifier, ok := id.(*source.ImageIdentifier) 168 if !ok { 169 return nil, errors.Errorf("invalid image identifier %v", id) 170 } 171 172 platform := platforms.DefaultSpec() 173 if imageIdentifier.Platform != nil { 174 platform = *imageIdentifier.Platform 175 } 176 177 p := &puller{ 178 src: imageIdentifier, 179 is: is, 180 resolver: is.getResolver(ctx, is.RegistryHosts, imageIdentifier.Reference.String(), sm), 181 platform: platform, 182 sm: sm, 183 } 184 return p, nil 185 } 186 187 type puller struct { 188 is *imageSource 189 resolveOnce sync.Once 190 resolveLocalOnce sync.Once 191 src *source.ImageIdentifier 192 desc ocispec.Descriptor 193 ref string 194 resolveErr error 195 resolver remotes.Resolver 196 config []byte 197 platform ocispec.Platform 198 sm *session.Manager 199 } 200 201 func (p *puller) mainManifestKey(dgst digest.Digest, platform ocispec.Platform) (digest.Digest, error) { 202 dt, err := json.Marshal(struct { 203 Digest digest.Digest 204 OS string 205 Arch string 206 Variant string `json:",omitempty"` 207 }{ 208 Digest: p.desc.Digest, 209 OS: platform.OS, 210 Arch: platform.Architecture, 211 Variant: platform.Variant, 212 }) 213 if err != nil { 214 return "", err 215 } 216 return digest.FromBytes(dt), nil 217 } 218 219 func (p *puller) resolveLocal() { 220 p.resolveLocalOnce.Do(func() { 221 dgst := p.src.Reference.Digest() 222 if dgst != "" { 223 info, err := p.is.ContentStore.Info(context.TODO(), dgst) 224 if err == nil { 225 p.ref = p.src.Reference.String() 226 desc := ocispec.Descriptor{ 227 Size: info.Size, 228 Digest: dgst, 229 } 230 ra, err := p.is.ContentStore.ReaderAt(context.TODO(), desc) 231 if err == nil { 232 mt, err := imageutil.DetectManifestMediaType(ra) 233 if err == nil { 234 desc.MediaType = mt 235 p.desc = desc 236 } 237 } 238 } 239 } 240 241 if p.src.ResolveMode == source.ResolveModeDefault || p.src.ResolveMode == source.ResolveModePreferLocal { 242 ref := p.src.Reference.String() 243 img, err := p.is.resolveLocal(ref) 244 if err == nil { 245 if !platformMatches(img, &p.platform) { 246 logrus.WithField("ref", ref).Debugf("Requested build platform %s does not match local image platform %s, not resolving", 247 path.Join(p.platform.OS, p.platform.Architecture, p.platform.Variant), 248 path.Join(img.OS, img.Architecture, img.Variant), 249 ) 250 } else { 251 p.config = img.RawJSON() 252 } 253 } 254 } 255 }) 256 } 257 258 func (p *puller) resolve(ctx context.Context) error { 259 p.resolveOnce.Do(func() { 260 resolveProgressDone := oneOffProgress(ctx, "resolve "+p.src.Reference.String()) 261 262 ref, err := distreference.ParseNormalizedNamed(p.src.Reference.String()) 263 if err != nil { 264 p.resolveErr = err 265 _ = resolveProgressDone(err) 266 return 267 } 268 269 if p.desc.Digest == "" && p.config == nil { 270 origRef, desc, err := p.resolver.Resolve(ctx, ref.String()) 271 if err != nil { 272 p.resolveErr = err 273 _ = resolveProgressDone(err) 274 return 275 } 276 277 p.desc = desc 278 p.ref = origRef 279 } 280 281 // Schema 1 manifests cannot be resolved to an image config 282 // since the conversion must take place after all the content 283 // has been read. 284 // It may be possible to have a mapping between schema 1 manifests 285 // and the schema 2 manifests they are converted to. 286 if p.config == nil && p.desc.MediaType != images.MediaTypeDockerSchema1Manifest { 287 ref, err := distreference.WithDigest(ref, p.desc.Digest) 288 if err != nil { 289 p.resolveErr = err 290 _ = resolveProgressDone(err) 291 return 292 } 293 _, dt, err := p.is.ResolveImageConfig(ctx, ref.String(), llb.ResolveImageConfigOpt{Platform: &p.platform, ResolveMode: resolveModeToString(p.src.ResolveMode)}, p.sm) 294 if err != nil { 295 p.resolveErr = err 296 _ = resolveProgressDone(err) 297 return 298 } 299 300 p.config = dt 301 } 302 _ = resolveProgressDone(nil) 303 }) 304 return p.resolveErr 305 } 306 307 func (p *puller) CacheKey(ctx context.Context, index int) (string, bool, error) { 308 p.resolveLocal() 309 310 if p.desc.Digest != "" && index == 0 { 311 dgst, err := p.mainManifestKey(p.desc.Digest, p.platform) 312 if err != nil { 313 return "", false, err 314 } 315 return dgst.String(), false, nil 316 } 317 318 if p.config != nil { 319 k := cacheKeyFromConfig(p.config).String() 320 if k == "" { 321 return digest.FromBytes(p.config).String(), true, nil 322 } 323 return k, true, nil 324 } 325 326 if err := p.resolve(ctx); err != nil { 327 return "", false, err 328 } 329 330 if p.desc.Digest != "" && index == 0 { 331 dgst, err := p.mainManifestKey(p.desc.Digest, p.platform) 332 if err != nil { 333 return "", false, err 334 } 335 return dgst.String(), false, nil 336 } 337 338 k := cacheKeyFromConfig(p.config).String() 339 if k == "" { 340 dgst, err := p.mainManifestKey(p.desc.Digest, p.platform) 341 if err != nil { 342 return "", false, err 343 } 344 return dgst.String(), true, nil 345 } 346 347 return k, true, nil 348 } 349 350 func (p *puller) getRef(ctx context.Context, diffIDs []layer.DiffID, opts ...cache.RefOption) (cache.ImmutableRef, error) { 351 var parent cache.ImmutableRef 352 if len(diffIDs) > 1 { 353 var err error 354 parent, err = p.getRef(ctx, diffIDs[:len(diffIDs)-1], opts...) 355 if err != nil { 356 return nil, err 357 } 358 defer parent.Release(context.TODO()) 359 } 360 return p.is.CacheAccessor.GetByBlob(ctx, ocispec.Descriptor{ 361 Annotations: map[string]string{ 362 "containerd.io/uncompressed": diffIDs[len(diffIDs)-1].String(), 363 }, 364 }, parent, opts...) 365 } 366 367 func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) { 368 p.resolveLocal() 369 if err := p.resolve(ctx); err != nil { 370 return nil, err 371 } 372 373 if p.config != nil { 374 img, err := p.is.ImageStore.Get(image.ID(digest.FromBytes(p.config))) 375 if err == nil { 376 if len(img.RootFS.DiffIDs) == 0 { 377 return nil, nil 378 } 379 l, err := p.is.LayerStore.Get(img.RootFS.ChainID()) 380 if err == nil { 381 layer.ReleaseAndLog(p.is.LayerStore, l) 382 ref, err := p.getRef(ctx, img.RootFS.DiffIDs, cache.WithDescription(fmt.Sprintf("from local %s", p.ref))) 383 if err != nil { 384 return nil, err 385 } 386 return ref, nil 387 } 388 } 389 } 390 391 ongoing := newJobs(p.ref) 392 393 pctx, stopProgress := context.WithCancel(ctx) 394 395 pw, _, ctx := progress.FromContext(ctx) 396 defer pw.Close() 397 398 progressDone := make(chan struct{}) 399 go func() { 400 showProgress(pctx, ongoing, p.is.ContentStore, pw) 401 close(progressDone) 402 }() 403 defer func() { 404 <-progressDone 405 }() 406 407 fetcher, err := p.resolver.Fetcher(ctx, p.ref) 408 if err != nil { 409 stopProgress() 410 return nil, err 411 } 412 413 platform := platforms.Only(p.platform) 414 // workaround for GCR bug that requires a request to manifest endpoint for authentication to work. 415 // if current resolver has not used manifests do a dummy request. 416 // in most cases resolver should be cached and extra request is not needed. 417 ensureManifestRequested(ctx, p.resolver, p.ref) 418 419 var ( 420 schema1Converter *schema1.Converter 421 handlers []images.Handler 422 ) 423 if p.desc.MediaType == images.MediaTypeDockerSchema1Manifest { 424 schema1Converter = schema1.NewConverter(p.is.ContentStore, fetcher) 425 handlers = append(handlers, schema1Converter) 426 427 // TODO: Optimize to do dispatch and integrate pulling with download manager, 428 // leverage existing blob mapping and layer storage 429 } else { 430 431 // TODO: need a wrapper snapshot interface that combines content 432 // and snapshots as 1) buildkit shouldn't have a dependency on contentstore 433 // or 2) cachemanager should manage the contentstore 434 handlers = append(handlers, images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 435 switch desc.MediaType { 436 case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest, 437 images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex, 438 images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: 439 default: 440 return nil, images.ErrSkipDesc 441 } 442 ongoing.add(desc) 443 return nil, nil 444 })) 445 446 // Get all the children for a descriptor 447 childrenHandler := images.ChildrenHandler(p.is.ContentStore) 448 // Set any children labels for that content 449 childrenHandler = images.SetChildrenLabels(p.is.ContentStore, childrenHandler) 450 // Filter the children by the platform 451 childrenHandler = images.FilterPlatforms(childrenHandler, platform) 452 // Limit manifests pulled to the best match in an index 453 childrenHandler = images.LimitManifests(childrenHandler, platform, 1) 454 455 handlers = append(handlers, 456 remotes.FetchHandler(p.is.ContentStore, fetcher), 457 childrenHandler, 458 ) 459 } 460 461 if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, p.desc); err != nil { 462 stopProgress() 463 return nil, err 464 } 465 defer stopProgress() 466 467 if schema1Converter != nil { 468 p.desc, err = schema1Converter.Convert(ctx) 469 if err != nil { 470 return nil, err 471 } 472 } 473 474 mfst, err := images.Manifest(ctx, p.is.ContentStore, p.desc, platform) 475 if err != nil { 476 return nil, err 477 } 478 479 config, err := images.Config(ctx, p.is.ContentStore, p.desc, platform) 480 if err != nil { 481 return nil, err 482 } 483 484 dt, err := content.ReadBlob(ctx, p.is.ContentStore, config) 485 if err != nil { 486 return nil, err 487 } 488 489 var img ocispec.Image 490 if err := json.Unmarshal(dt, &img); err != nil { 491 return nil, err 492 } 493 494 if len(mfst.Layers) != len(img.RootFS.DiffIDs) { 495 return nil, errors.Errorf("invalid config for manifest") 496 } 497 498 pchan := make(chan pkgprogress.Progress, 10) 499 defer close(pchan) 500 501 go func() { 502 m := map[string]struct { 503 st time.Time 504 limiter *rate.Limiter 505 }{} 506 for p := range pchan { 507 if p.Action == "Extracting" { 508 st, ok := m[p.ID] 509 if !ok { 510 st.st = time.Now() 511 st.limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1) 512 m[p.ID] = st 513 } 514 var end *time.Time 515 if p.LastUpdate || st.limiter.Allow() { 516 if p.LastUpdate { 517 tm := time.Now() 518 end = &tm 519 } 520 _ = pw.Write("extracting "+p.ID, progress.Status{ 521 Action: "extract", 522 Started: &st.st, 523 Completed: end, 524 }) 525 } 526 } 527 } 528 }() 529 530 if len(mfst.Layers) == 0 { 531 return nil, nil 532 } 533 534 layers := make([]xfer.DownloadDescriptor, 0, len(mfst.Layers)) 535 536 for i, desc := range mfst.Layers { 537 ongoing.add(desc) 538 layers = append(layers, &layerDescriptor{ 539 desc: desc, 540 diffID: layer.DiffID(img.RootFS.DiffIDs[i]), 541 fetcher: fetcher, 542 ref: p.src.Reference, 543 is: p.is, 544 }) 545 } 546 547 defer func() { 548 <-progressDone 549 for _, desc := range mfst.Layers { 550 p.is.ContentStore.Delete(context.TODO(), desc.Digest) 551 } 552 }() 553 554 r := image.NewRootFS() 555 rootFS, release, err := p.is.DownloadManager.Download(ctx, *r, runtime.GOOS, layers, pkgprogress.ChanOutput(pchan)) 556 stopProgress() 557 if err != nil { 558 return nil, err 559 } 560 561 ref, err := p.getRef(ctx, rootFS.DiffIDs, cache.WithDescription(fmt.Sprintf("pulled from %s", p.ref))) 562 release() 563 if err != nil { 564 return nil, err 565 } 566 567 // TODO: handle windows layers for cross platform builds 568 569 if p.src.RecordType != "" && cache.GetRecordType(ref) == "" { 570 if err := cache.SetRecordType(ref, p.src.RecordType); err != nil { 571 ref.Release(context.TODO()) 572 return nil, err 573 } 574 } 575 576 return ref, nil 577 } 578 579 // Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) 580 type layerDescriptor struct { 581 is *imageSource 582 fetcher remotes.Fetcher 583 desc ocispec.Descriptor 584 diffID layer.DiffID 585 ref ctdreference.Spec 586 } 587 588 func (ld *layerDescriptor) Key() string { 589 return "v2:" + ld.desc.Digest.String() 590 } 591 592 func (ld *layerDescriptor) ID() string { 593 return ld.desc.Digest.String() 594 } 595 596 func (ld *layerDescriptor) DiffID() (layer.DiffID, error) { 597 return ld.diffID, nil 598 } 599 600 func (ld *layerDescriptor) Download(ctx context.Context, progressOutput pkgprogress.Output) (io.ReadCloser, int64, error) { 601 rc, err := ld.fetcher.Fetch(ctx, ld.desc) 602 if err != nil { 603 return nil, 0, err 604 } 605 defer rc.Close() 606 607 refKey := remotes.MakeRefKey(ctx, ld.desc) 608 609 ld.is.ContentStore.Abort(ctx, refKey) 610 611 if err := content.WriteBlob(ctx, ld.is.ContentStore, refKey, rc, ld.desc); err != nil { 612 ld.is.ContentStore.Abort(ctx, refKey) 613 return nil, 0, err 614 } 615 616 ra, err := ld.is.ContentStore.ReaderAt(ctx, ld.desc) 617 if err != nil { 618 return nil, 0, err 619 } 620 621 return ioutil.NopCloser(content.NewReader(ra)), ld.desc.Size, nil 622 } 623 624 func (ld *layerDescriptor) Close() { 625 // ld.is.ContentStore.Delete(context.TODO(), ld.desc.Digest)) 626 } 627 628 func (ld *layerDescriptor) Registered(diffID layer.DiffID) { 629 // Cache mapping from this layer's DiffID to the blobsum 630 ld.is.MetadataStore.Add(diffID, metadata.V2Metadata{Digest: ld.desc.Digest, SourceRepository: ld.ref.Locator}) 631 } 632 633 func showProgress(ctx context.Context, ongoing *jobs, cs content.Store, pw progress.Writer) { 634 var ( 635 ticker = time.NewTicker(100 * time.Millisecond) 636 statuses = map[string]statusInfo{} 637 done bool 638 ) 639 defer ticker.Stop() 640 641 for { 642 select { 643 case <-ticker.C: 644 case <-ctx.Done(): 645 done = true 646 } 647 648 resolved := "resolved" 649 if !ongoing.isResolved() { 650 resolved = "resolving" 651 } 652 statuses[ongoing.name] = statusInfo{ 653 Ref: ongoing.name, 654 Status: resolved, 655 } 656 657 actives := make(map[string]statusInfo) 658 659 if !done { 660 active, err := cs.ListStatuses(ctx) 661 if err != nil { 662 // log.G(ctx).WithError(err).Error("active check failed") 663 continue 664 } 665 // update status of active entries! 666 for _, active := range active { 667 actives[active.Ref] = statusInfo{ 668 Ref: active.Ref, 669 Status: "downloading", 670 Offset: active.Offset, 671 Total: active.Total, 672 StartedAt: active.StartedAt, 673 UpdatedAt: active.UpdatedAt, 674 } 675 } 676 } 677 678 // now, update the items in jobs that are not in active 679 for _, j := range ongoing.jobs() { 680 refKey := remotes.MakeRefKey(ctx, j.Descriptor) 681 if a, ok := actives[refKey]; ok { 682 started := j.started 683 _ = pw.Write(j.Digest.String(), progress.Status{ 684 Action: a.Status, 685 Total: int(a.Total), 686 Current: int(a.Offset), 687 Started: &started, 688 }) 689 continue 690 } 691 692 if !j.done { 693 info, err := cs.Info(context.TODO(), j.Digest) 694 if err != nil { 695 if containerderrors.IsNotFound(err) { 696 // _ = pw.Write(j.Digest.String(), progress.Status{ 697 // Action: "waiting", 698 // }) 699 continue 700 } 701 } else { 702 j.done = true 703 } 704 705 if done || j.done { 706 started := j.started 707 createdAt := info.CreatedAt 708 _ = pw.Write(j.Digest.String(), progress.Status{ 709 Action: "done", 710 Current: int(info.Size), 711 Total: int(info.Size), 712 Completed: &createdAt, 713 Started: &started, 714 }) 715 } 716 } 717 } 718 if done { 719 return 720 } 721 } 722 } 723 724 // jobs provides a way of identifying the download keys for a particular task 725 // encountering during the pull walk. 726 // 727 // This is very minimal and will probably be replaced with something more 728 // featured. 729 type jobs struct { 730 name string 731 added map[digest.Digest]*job 732 mu sync.Mutex 733 resolved bool 734 } 735 736 type job struct { 737 ocispec.Descriptor 738 done bool 739 started time.Time 740 } 741 742 func newJobs(name string) *jobs { 743 return &jobs{ 744 name: name, 745 added: make(map[digest.Digest]*job), 746 } 747 } 748 749 func (j *jobs) add(desc ocispec.Descriptor) { 750 j.mu.Lock() 751 defer j.mu.Unlock() 752 753 if _, ok := j.added[desc.Digest]; ok { 754 return 755 } 756 j.added[desc.Digest] = &job{ 757 Descriptor: desc, 758 started: time.Now(), 759 } 760 } 761 762 func (j *jobs) jobs() []*job { 763 j.mu.Lock() 764 defer j.mu.Unlock() 765 766 descs := make([]*job, 0, len(j.added)) 767 for _, j := range j.added { 768 descs = append(descs, j) 769 } 770 return descs 771 } 772 773 func (j *jobs) isResolved() bool { 774 j.mu.Lock() 775 defer j.mu.Unlock() 776 return j.resolved 777 } 778 779 type statusInfo struct { 780 Ref string 781 Status string 782 Offset int64 783 Total int64 784 StartedAt time.Time 785 UpdatedAt time.Time 786 } 787 788 func oneOffProgress(ctx context.Context, id string) func(err error) error { 789 pw, _, _ := progress.FromContext(ctx) 790 now := time.Now() 791 st := progress.Status{ 792 Started: &now, 793 } 794 _ = pw.Write(id, st) 795 return func(err error) error { 796 // TODO: set error on status 797 now := time.Now() 798 st.Completed = &now 799 _ = pw.Write(id, st) 800 _ = pw.Close() 801 return err 802 } 803 } 804 805 // cacheKeyFromConfig returns a stable digest from image config. If image config 806 // is a known oci image we will use chainID of layers. 807 func cacheKeyFromConfig(dt []byte) digest.Digest { 808 var img ocispec.Image 809 err := json.Unmarshal(dt, &img) 810 if err != nil { 811 return digest.FromBytes(dt) 812 } 813 if img.RootFS.Type != "layers" || len(img.RootFS.DiffIDs) == 0 { 814 return "" 815 } 816 return identity.ChainID(img.RootFS.DiffIDs) 817 } 818 819 // resolveModeToString is the equivalent of github.com/moby/buildkit/solver/llb.ResolveMode.String() 820 // FIXME: add String method on source.ResolveMode 821 func resolveModeToString(rm source.ResolveMode) string { 822 switch rm { 823 case source.ResolveModeDefault: 824 return "default" 825 case source.ResolveModeForcePull: 826 return "pull" 827 case source.ResolveModePreferLocal: 828 return "local" 829 } 830 return "" 831 } 832 833 type resolverCache struct { 834 mu sync.Mutex 835 m map[string]cachedResolver 836 } 837 838 type cachedResolver struct { 839 counter int64 // needs to be 64bit aligned for 32bit systems 840 timeout time.Time 841 remotes.Resolver 842 } 843 844 func (cr *cachedResolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) { 845 atomic.AddInt64(&cr.counter, 1) 846 return cr.Resolver.Resolve(ctx, ref) 847 } 848 849 func (r *resolverCache) Add(ctx context.Context, ref string, resolver remotes.Resolver) remotes.Resolver { 850 r.mu.Lock() 851 defer r.mu.Unlock() 852 853 ref = r.repo(ref) + "-" + session.FromContext(ctx) 854 855 cr, ok := r.m[ref] 856 cr.timeout = time.Now().Add(time.Minute) 857 if ok { 858 return &cr 859 } 860 861 cr.Resolver = resolver 862 r.m[ref] = cr 863 return &cr 864 } 865 866 func (r *resolverCache) repo(refStr string) string { 867 ref, err := distreference.ParseNormalizedNamed(refStr) 868 if err != nil { 869 return refStr 870 } 871 return ref.Name() 872 } 873 874 func (r *resolverCache) Get(ctx context.Context, ref string) remotes.Resolver { 875 r.mu.Lock() 876 defer r.mu.Unlock() 877 878 ref = r.repo(ref) + "-" + session.FromContext(ctx) 879 880 cr, ok := r.m[ref] 881 if !ok { 882 return nil 883 } 884 return &cr 885 } 886 887 func (r *resolverCache) clean(now time.Time) { 888 r.mu.Lock() 889 for k, cr := range r.m { 890 if now.After(cr.timeout) { 891 delete(r.m, k) 892 } 893 } 894 r.mu.Unlock() 895 } 896 897 func newResolverCache() *resolverCache { 898 rc := &resolverCache{ 899 m: map[string]cachedResolver{}, 900 } 901 t := time.NewTicker(time.Minute) 902 go func() { 903 for { 904 rc.clean(<-t.C) 905 } 906 }() 907 return rc 908 } 909 910 func ensureManifestRequested(ctx context.Context, res remotes.Resolver, ref string) { 911 cr, ok := res.(*cachedResolver) 912 if !ok { 913 return 914 } 915 if atomic.LoadInt64(&cr.counter) == 0 { 916 res.Resolve(ctx, ref) 917 } 918 } 919 920 func platformMatches(img *image.Image, p *ocispec.Platform) bool { 921 if img.Architecture != p.Architecture { 922 return false 923 } 924 if img.Variant != "" && img.Variant != p.Variant { 925 return false 926 } 927 return img.OS == p.OS 928 }