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