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