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