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