github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/builder/builder-next/worker/worker.go (about) 1 package worker 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 nethttp "net/http" 8 "time" 9 10 "github.com/containerd/containerd/content" 11 "github.com/containerd/containerd/images" 12 "github.com/containerd/containerd/platforms" 13 "github.com/containerd/containerd/rootfs" 14 "github.com/containerd/log" 15 imageadapter "github.com/docker/docker/builder/builder-next/adapters/containerimage" 16 mobyexporter "github.com/docker/docker/builder/builder-next/exporter" 17 distmetadata "github.com/docker/docker/distribution/metadata" 18 "github.com/docker/docker/distribution/xfer" 19 "github.com/docker/docker/image" 20 "github.com/docker/docker/internal/mod" 21 "github.com/docker/docker/layer" 22 pkgprogress "github.com/docker/docker/pkg/progress" 23 "github.com/moby/buildkit/cache" 24 cacheconfig "github.com/moby/buildkit/cache/config" 25 "github.com/moby/buildkit/client" 26 "github.com/moby/buildkit/client/llb/sourceresolver" 27 "github.com/moby/buildkit/executor" 28 "github.com/moby/buildkit/exporter" 29 localexporter "github.com/moby/buildkit/exporter/local" 30 tarexporter "github.com/moby/buildkit/exporter/tar" 31 "github.com/moby/buildkit/frontend" 32 "github.com/moby/buildkit/session" 33 "github.com/moby/buildkit/snapshot" 34 containerdsnapshot "github.com/moby/buildkit/snapshot/containerd" 35 "github.com/moby/buildkit/solver" 36 "github.com/moby/buildkit/solver/llbsolver/mounts" 37 "github.com/moby/buildkit/solver/llbsolver/ops" 38 "github.com/moby/buildkit/solver/pb" 39 "github.com/moby/buildkit/source" 40 "github.com/moby/buildkit/source/containerimage" 41 "github.com/moby/buildkit/source/git" 42 "github.com/moby/buildkit/source/http" 43 "github.com/moby/buildkit/source/local" 44 "github.com/moby/buildkit/util/archutil" 45 "github.com/moby/buildkit/util/contentutil" 46 "github.com/moby/buildkit/util/leaseutil" 47 "github.com/moby/buildkit/util/progress" 48 "github.com/moby/buildkit/version" 49 "github.com/opencontainers/go-digest" 50 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 51 "github.com/pkg/errors" 52 "golang.org/x/sync/semaphore" 53 ) 54 55 func init() { 56 if v := mod.Version("github.com/moby/buildkit"); v != "" { 57 version.Version = v 58 } 59 } 60 61 const labelCreatedAt = "buildkit/createdat" 62 63 // LayerAccess provides access to a moby layer from a snapshot 64 type LayerAccess interface { 65 GetDiffIDs(ctx context.Context, key string) ([]layer.DiffID, error) 66 EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error) 67 } 68 69 // Opt defines a structure for creating a worker. 70 type Opt struct { 71 ID string 72 Labels map[string]string 73 GCPolicy []client.PruneInfo 74 Executor executor.Executor 75 Snapshotter snapshot.Snapshotter 76 ContentStore *containerdsnapshot.Store 77 CacheManager cache.Manager 78 LeaseManager *leaseutil.Manager 79 ImageSource *imageadapter.Source 80 DownloadManager *xfer.LayerDownloadManager 81 V2MetadataService distmetadata.V2MetadataService 82 Transport nethttp.RoundTripper 83 Exporter exporter.Exporter 84 Layers LayerAccess 85 Platforms []ocispec.Platform 86 } 87 88 // Worker is a local worker instance with dedicated snapshotter, cache, and so on. 89 // TODO: s/Worker/OpWorker/g ? 90 type Worker struct { 91 Opt 92 SourceManager *source.Manager 93 } 94 95 var _ interface { 96 GetRemotes(context.Context, cache.ImmutableRef, bool, cacheconfig.RefConfig, bool, session.Group) ([]*solver.Remote, error) 97 } = &Worker{} 98 99 // NewWorker instantiates a local worker 100 func NewWorker(opt Opt) (*Worker, error) { 101 sm, err := source.NewManager() 102 if err != nil { 103 return nil, err 104 } 105 106 cm := opt.CacheManager 107 sm.Register(opt.ImageSource) 108 109 gs, err := git.NewSource(git.Opt{ 110 CacheAccessor: cm, 111 }) 112 if err == nil { 113 sm.Register(gs) 114 } else { 115 log.G(context.TODO()).Warnf("Could not register builder git source: %s", err) 116 } 117 118 hs, err := http.NewSource(http.Opt{ 119 CacheAccessor: cm, 120 Transport: opt.Transport, 121 }) 122 if err == nil { 123 sm.Register(hs) 124 } else { 125 log.G(context.TODO()).Warnf("Could not register builder http source: %s", err) 126 } 127 128 ss, err := local.NewSource(local.Opt{ 129 CacheAccessor: cm, 130 }) 131 if err == nil { 132 sm.Register(ss) 133 } else { 134 log.G(context.TODO()).Warnf("Could not register builder local source: %s", err) 135 } 136 137 return &Worker{ 138 Opt: opt, 139 SourceManager: sm, 140 }, nil 141 } 142 143 // ID returns worker ID 144 func (w *Worker) ID() string { 145 return w.Opt.ID 146 } 147 148 // Labels returns map of all worker labels 149 func (w *Worker) Labels() map[string]string { 150 return w.Opt.Labels 151 } 152 153 // Platforms returns one or more platforms supported by the image. 154 func (w *Worker) Platforms(noCache bool) []ocispec.Platform { 155 if noCache { 156 pm := make(map[string]struct{}, len(w.Opt.Platforms)) 157 for _, p := range w.Opt.Platforms { 158 pm[platforms.Format(p)] = struct{}{} 159 } 160 for _, p := range archutil.SupportedPlatforms(noCache) { 161 if _, ok := pm[platforms.Format(p)]; !ok { 162 w.Opt.Platforms = append(w.Opt.Platforms, p) 163 } 164 } 165 } 166 if len(w.Opt.Platforms) == 0 { 167 return []ocispec.Platform{platforms.DefaultSpec()} 168 } 169 return w.Opt.Platforms 170 } 171 172 // GCPolicy returns automatic GC Policy 173 func (w *Worker) GCPolicy() []client.PruneInfo { 174 return w.Opt.GCPolicy 175 } 176 177 // BuildkitVersion returns BuildKit version 178 func (w *Worker) BuildkitVersion() client.BuildkitVersion { 179 return client.BuildkitVersion{ 180 Package: version.Package, 181 Version: version.Version + "-moby", 182 Revision: version.Revision, 183 } 184 } 185 186 // Close closes the worker and releases all resources 187 func (w *Worker) Close() error { 188 return nil 189 } 190 191 // ContentStore returns the wrapped content store 192 func (w *Worker) ContentStore() *containerdsnapshot.Store { 193 return w.Opt.ContentStore 194 } 195 196 // LeaseManager returns the wrapped lease manager 197 func (w *Worker) LeaseManager() *leaseutil.Manager { 198 return w.Opt.LeaseManager 199 } 200 201 // LoadRef loads a reference by ID 202 func (w *Worker) LoadRef(ctx context.Context, id string, hidden bool) (cache.ImmutableRef, error) { 203 var opts []cache.RefOption 204 if hidden { 205 opts = append(opts, cache.NoUpdateLastUsed) 206 } 207 if id == "" { 208 // results can have nil refs if they are optimized out to be equal to scratch, 209 // i.e. Diff(A,A) == scratch 210 return nil, nil 211 } 212 213 return w.CacheManager().Get(ctx, id, nil, opts...) 214 } 215 216 func (w *Worker) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, opt sourceresolver.Opt, sm *session.Manager, g session.Group) (*sourceresolver.MetaResponse, error) { 217 if opt.SourcePolicies != nil { 218 return nil, errors.New("source policies can not be set for worker") 219 } 220 221 var platform *pb.Platform 222 if p := opt.Platform; p != nil { 223 platform = &pb.Platform{ 224 Architecture: p.Architecture, 225 OS: p.OS, 226 Variant: p.Variant, 227 OSVersion: p.OSVersion, 228 } 229 } 230 231 id, err := w.SourceManager.Identifier(&pb.Op_Source{Source: op}, platform) 232 if err != nil { 233 return nil, err 234 } 235 236 switch idt := id.(type) { 237 case *containerimage.ImageIdentifier: 238 if opt.ImageOpt == nil { 239 opt.ImageOpt = &sourceresolver.ResolveImageOpt{} 240 } 241 dgst, config, err := w.ImageSource.ResolveImageConfig(ctx, idt.Reference.String(), opt, sm, g) 242 if err != nil { 243 return nil, err 244 } 245 return &sourceresolver.MetaResponse{ 246 Op: op, 247 Image: &sourceresolver.ResolveImageResponse{ 248 Digest: dgst, 249 Config: config, 250 }, 251 }, nil 252 } 253 254 return &sourceresolver.MetaResponse{ 255 Op: op, 256 }, nil 257 } 258 259 // ResolveOp converts a LLB vertex into a LLB operation 260 func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge, sm *session.Manager) (solver.Op, error) { 261 if baseOp, ok := v.Sys().(*pb.Op); ok { 262 // TODO do we need to pass a value here? Where should it come from? https://github.com/moby/buildkit/commit/b3cf7c43cfefdfd7a945002c0e76b54e346ab6cf 263 var parallelism *semaphore.Weighted 264 switch op := baseOp.Op.(type) { 265 case *pb.Op_Source: 266 return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, parallelism, sm, w) 267 case *pb.Op_Exec: 268 return ops.NewExecOp(v, op, baseOp.Platform, w.CacheManager(), parallelism, sm, w.Executor(), w) 269 case *pb.Op_File: 270 return ops.NewFileOp(v, op, w.CacheManager(), parallelism, w) 271 case *pb.Op_Build: 272 return ops.NewBuildOp(v, op, s, w) 273 case *pb.Op_Merge: 274 return ops.NewMergeOp(v, op, w) 275 case *pb.Op_Diff: 276 return ops.NewDiffOp(v, op, w) 277 } 278 } 279 return nil, errors.Errorf("could not resolve %v", v) 280 } 281 282 // ResolveImageConfig returns image config for an image 283 func (w *Worker) ResolveImageConfig(ctx context.Context, ref string, opt sourceresolver.Opt, sm *session.Manager, g session.Group) (digest.Digest, []byte, error) { 284 return w.ImageSource.ResolveImageConfig(ctx, ref, opt, sm, g) 285 } 286 287 // DiskUsage returns disk usage report 288 func (w *Worker) DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error) { 289 return w.CacheManager().DiskUsage(ctx, opt) 290 } 291 292 // Prune deletes reclaimable build cache 293 func (w *Worker) Prune(ctx context.Context, ch chan client.UsageInfo, info ...client.PruneInfo) error { 294 return w.CacheManager().Prune(ctx, ch, info...) 295 } 296 297 // Exporter returns exporter by name 298 func (w *Worker) Exporter(name string, sm *session.Manager) (exporter.Exporter, error) { 299 switch name { 300 case mobyexporter.Moby: 301 return w.Opt.Exporter, nil 302 case client.ExporterLocal: 303 return localexporter.New(localexporter.Opt{ 304 SessionManager: sm, 305 }) 306 case client.ExporterTar: 307 return tarexporter.New(tarexporter.Opt{ 308 SessionManager: sm, 309 }) 310 default: 311 return nil, errors.Errorf("exporter %q could not be found", name) 312 } 313 } 314 315 // GetRemotes returns the remote snapshot references given a local reference 316 func (w *Worker) GetRemotes(ctx context.Context, ref cache.ImmutableRef, createIfNeeded bool, _ cacheconfig.RefConfig, all bool, s session.Group) ([]*solver.Remote, error) { 317 if ref == nil { 318 return nil, nil 319 } 320 var diffIDs []layer.DiffID 321 var err error 322 if !createIfNeeded { 323 diffIDs, err = w.Layers.GetDiffIDs(ctx, ref.ID()) 324 if err != nil { 325 return nil, err 326 } 327 } else { 328 if err := ref.Finalize(ctx); err != nil { 329 return nil, err 330 } 331 if err := ref.Extract(ctx, s); err != nil { 332 return nil, err 333 } 334 diffIDs, err = w.Layers.EnsureLayer(ctx, ref.ID()) 335 if err != nil { 336 return nil, err 337 } 338 } 339 340 descriptors := make([]ocispec.Descriptor, len(diffIDs)) 341 for i, dgst := range diffIDs { 342 descriptors[i] = ocispec.Descriptor{ 343 MediaType: images.MediaTypeDockerSchema2Layer, 344 Digest: digest.Digest(dgst), 345 Size: -1, 346 } 347 } 348 349 return []*solver.Remote{{ 350 Descriptors: descriptors, 351 Provider: &emptyProvider{}, 352 }}, nil 353 } 354 355 // PruneCacheMounts removes the current cache snapshots for specified IDs 356 func (w *Worker) PruneCacheMounts(ctx context.Context, ids []string) error { 357 mu := mounts.CacheMountsLocker() 358 mu.Lock() 359 defer mu.Unlock() 360 361 for _, id := range ids { 362 mds, err := mounts.SearchCacheDir(ctx, w.CacheManager(), id) 363 if err != nil { 364 return err 365 } 366 for _, md := range mds { 367 if err := md.SetCachePolicyDefault(); err != nil { 368 return err 369 } 370 if err := md.ClearCacheDirIndex(); err != nil { 371 return err 372 } 373 // if ref is unused try to clean it up right away by releasing it 374 if mref, err := w.CacheManager().GetMutable(ctx, md.ID()); err == nil { 375 go mref.Release(context.TODO()) 376 } 377 } 378 } 379 380 mounts.ClearActiveCacheMounts() 381 return nil 382 } 383 384 func (w *Worker) getRef(ctx context.Context, diffIDs []layer.DiffID, opts ...cache.RefOption) (cache.ImmutableRef, error) { 385 var parent cache.ImmutableRef 386 if len(diffIDs) > 1 { 387 var err error 388 parent, err = w.getRef(ctx, diffIDs[:len(diffIDs)-1], opts...) 389 if err != nil { 390 return nil, err 391 } 392 defer parent.Release(context.TODO()) 393 } 394 return w.CacheManager().GetByBlob(context.TODO(), ocispec.Descriptor{ 395 Annotations: map[string]string{ 396 "containerd.io/uncompressed": diffIDs[len(diffIDs)-1].String(), 397 }, 398 }, parent, opts...) 399 } 400 401 // FromRemote converts a remote snapshot reference to a local one 402 func (w *Worker) FromRemote(ctx context.Context, remote *solver.Remote) (cache.ImmutableRef, error) { 403 rootfs, err := getLayers(ctx, remote.Descriptors) 404 if err != nil { 405 return nil, err 406 } 407 408 layers := make([]xfer.DownloadDescriptor, 0, len(rootfs)) 409 410 for _, l := range rootfs { 411 // ongoing.add(desc) 412 layers = append(layers, &layerDescriptor{ 413 desc: l.Blob, 414 diffID: layer.DiffID(l.Diff.Digest), 415 provider: remote.Provider, 416 w: w, 417 pctx: ctx, 418 }) 419 } 420 421 defer func() { 422 for _, l := range rootfs { 423 w.ContentStore().Delete(context.TODO(), l.Blob.Digest) 424 } 425 }() 426 427 r := image.NewRootFS() 428 rootFS, release, err := w.DownloadManager.Download(ctx, *r, layers, &discardProgress{}) 429 if err != nil { 430 return nil, err 431 } 432 defer release() 433 434 if len(rootFS.DiffIDs) != len(layers) { 435 return nil, errors.Errorf("invalid layer count mismatch %d vs %d", len(rootFS.DiffIDs), len(layers)) 436 } 437 438 for i := range rootFS.DiffIDs { 439 tm := time.Now() 440 if tmstr, ok := remote.Descriptors[i].Annotations[labelCreatedAt]; ok { 441 if err := (&tm).UnmarshalText([]byte(tmstr)); err != nil { 442 return nil, err 443 } 444 } 445 descr := fmt.Sprintf("imported %s", remote.Descriptors[i].Digest) 446 if v, ok := remote.Descriptors[i].Annotations["buildkit/description"]; ok { 447 descr = v 448 } 449 ref, err := w.getRef(ctx, rootFS.DiffIDs[:i+1], cache.WithDescription(descr), cache.WithCreationTime(tm)) 450 if err != nil { 451 return nil, err 452 } 453 if i == len(remote.Descriptors)-1 { 454 return ref, nil 455 } 456 defer ref.Release(context.TODO()) 457 } 458 459 return nil, errors.Errorf("unreachable") 460 } 461 462 // Executor returns executor.Executor for running processes 463 func (w *Worker) Executor() executor.Executor { 464 return w.Opt.Executor 465 } 466 467 // CacheManager returns cache.Manager for accessing local storage 468 func (w *Worker) CacheManager() cache.Manager { 469 return w.Opt.CacheManager 470 } 471 472 type discardProgress struct{} 473 474 func (*discardProgress) WriteProgress(_ pkgprogress.Progress) error { 475 return nil 476 } 477 478 // Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) 479 type layerDescriptor struct { 480 provider content.Provider 481 desc ocispec.Descriptor 482 diffID layer.DiffID 483 // ref ctdreference.Spec 484 w *Worker 485 pctx context.Context 486 } 487 488 func (ld *layerDescriptor) Key() string { 489 return "v2:" + ld.desc.Digest.String() 490 } 491 492 func (ld *layerDescriptor) ID() string { 493 return ld.desc.Digest.String() 494 } 495 496 func (ld *layerDescriptor) DiffID() (layer.DiffID, error) { 497 return ld.diffID, nil 498 } 499 500 func (ld *layerDescriptor) Download(ctx context.Context, progressOutput pkgprogress.Output) (io.ReadCloser, int64, error) { 501 done := oneOffProgress(ld.pctx, fmt.Sprintf("pulling %s", ld.desc.Digest)) 502 503 // TODO should this write output to progressOutput? Or use something similar to loggerFromContext()? see https://github.com/moby/buildkit/commit/aa29e7729464f3c2a773e27795e584023c751cb8 504 discardLogs := func(_ []byte) {} 505 if err := contentutil.Copy(ctx, ld.w.ContentStore(), ld.provider, ld.desc, "", discardLogs); err != nil { 506 return nil, 0, done(err) 507 } 508 _ = done(nil) 509 510 ra, err := ld.w.ContentStore().ReaderAt(ctx, ld.desc) 511 if err != nil { 512 return nil, 0, err 513 } 514 515 return io.NopCloser(content.NewReader(ra)), ld.desc.Size, nil 516 } 517 518 func (ld *layerDescriptor) Close() { 519 // ld.is.ContentStore().Delete(context.TODO(), ld.desc.Digest) 520 } 521 522 func (ld *layerDescriptor) Registered(diffID layer.DiffID) { 523 // Cache mapping from this layer's DiffID to the blobsum 524 ld.w.V2MetadataService.Add(diffID, distmetadata.V2Metadata{Digest: ld.desc.Digest}) 525 } 526 527 func getLayers(ctx context.Context, descs []ocispec.Descriptor) ([]rootfs.Layer, error) { 528 layers := make([]rootfs.Layer, len(descs)) 529 for i, desc := range descs { 530 diffIDStr := desc.Annotations["containerd.io/uncompressed"] 531 if diffIDStr == "" { 532 return nil, errors.Errorf("%s missing uncompressed digest", desc.Digest) 533 } 534 diffID, err := digest.Parse(diffIDStr) 535 if err != nil { 536 return nil, err 537 } 538 layers[i].Diff = ocispec.Descriptor{ 539 MediaType: ocispec.MediaTypeImageLayer, 540 Digest: diffID, 541 } 542 layers[i].Blob = ocispec.Descriptor{ 543 MediaType: desc.MediaType, 544 Digest: desc.Digest, 545 Size: desc.Size, 546 } 547 } 548 return layers, nil 549 } 550 551 func oneOffProgress(ctx context.Context, id string) func(err error) error { 552 pw, _, _ := progress.NewFromContext(ctx) 553 now := time.Now() 554 st := progress.Status{ 555 Started: &now, 556 } 557 _ = pw.Write(id, st) 558 return func(err error) error { 559 // TODO: set error on status 560 now := time.Now() 561 st.Completed = &now 562 _ = pw.Write(id, st) 563 _ = pw.Close() 564 return err 565 } 566 } 567 568 type emptyProvider struct{} 569 570 func (p *emptyProvider) ReaderAt(ctx context.Context, dec ocispec.Descriptor) (content.ReaderAt, error) { 571 return nil, errors.Errorf("ReaderAt not implemented for empty provider") 572 } 573 574 func (p *emptyProvider) Info(ctx context.Context, d digest.Digest) (content.Info, error) { 575 return content.Info{}, errors.Errorf("Info not implemented for empty provider") 576 }