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