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