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