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