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