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