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