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  }