github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/builder/builder-next/exporter/mobyexporter/export.go (about)

     1  package mobyexporter
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/containerd/containerd/content"
    11  	"github.com/containerd/containerd/leases"
    12  	distref "github.com/distribution/reference"
    13  	"github.com/docker/docker/image"
    14  	"github.com/docker/docker/layer"
    15  	"github.com/moby/buildkit/exporter"
    16  	"github.com/moby/buildkit/exporter/containerimage"
    17  	"github.com/moby/buildkit/exporter/containerimage/exptypes"
    18  	"github.com/opencontainers/go-digest"
    19  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // Differ can make a moby layer from a snapshot
    24  type Differ interface {
    25  	EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error)
    26  }
    27  
    28  type ImageTagger interface {
    29  	TagImage(ctx context.Context, imageID image.ID, newTag distref.Named) error
    30  }
    31  
    32  // Opt defines a struct for creating new exporter
    33  type Opt struct {
    34  	ImageStore   image.Store
    35  	Differ       Differ
    36  	ImageTagger  ImageTagger
    37  	ContentStore content.Store
    38  	LeaseManager leases.Manager
    39  }
    40  
    41  type imageExporter struct {
    42  	opt Opt
    43  }
    44  
    45  // New creates a new moby imagestore exporter
    46  func New(opt Opt) (exporter.Exporter, error) {
    47  	im := &imageExporter{opt: opt}
    48  	return im, nil
    49  }
    50  
    51  func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) {
    52  	i := &imageExporterInstance{
    53  		imageExporter: e,
    54  		id:            id,
    55  	}
    56  	for k, v := range opt {
    57  		switch exptypes.ImageExporterOptKey(k) {
    58  		case exptypes.OptKeyName:
    59  			for _, v := range strings.Split(v, ",") {
    60  				ref, err := distref.ParseNormalizedNamed(v)
    61  				if err != nil {
    62  					return nil, err
    63  				}
    64  				i.targetNames = append(i.targetNames, ref)
    65  			}
    66  		default:
    67  			if i.meta == nil {
    68  				i.meta = make(map[string][]byte)
    69  			}
    70  			i.meta[k] = []byte(v)
    71  		}
    72  	}
    73  	return i, nil
    74  }
    75  
    76  type imageExporterInstance struct {
    77  	*imageExporter
    78  	id          int
    79  	targetNames []distref.Named
    80  	meta        map[string][]byte
    81  }
    82  
    83  func (e *imageExporterInstance) ID() int {
    84  	return e.id
    85  }
    86  
    87  func (e *imageExporterInstance) Name() string {
    88  	return "exporting to image"
    89  }
    90  
    91  func (e *imageExporterInstance) Config() *exporter.Config {
    92  	return exporter.NewConfig()
    93  }
    94  
    95  func (e *imageExporterInstance) Export(ctx context.Context, inp *exporter.Source, inlineCache exptypes.InlineCache, sessionID string) (map[string]string, exporter.DescriptorReference, error) {
    96  	if len(inp.Refs) > 1 {
    97  		return nil, nil, fmt.Errorf("exporting multiple references to image store is currently unsupported")
    98  	}
    99  
   100  	ref := inp.Ref
   101  	if ref != nil && len(inp.Refs) == 1 {
   102  		return nil, nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive")
   103  	}
   104  
   105  	// only one loop
   106  	for _, v := range inp.Refs {
   107  		ref = v
   108  	}
   109  
   110  	var config []byte
   111  	switch len(inp.Refs) {
   112  	case 0:
   113  		config = inp.Metadata[exptypes.ExporterImageConfigKey]
   114  	case 1:
   115  		ps, err := exptypes.ParsePlatforms(inp.Metadata)
   116  		if err != nil {
   117  			return nil, nil, fmt.Errorf("cannot export image, failed to parse platforms: %w", err)
   118  		}
   119  		if len(ps.Platforms) != len(inp.Refs) {
   120  			return nil, nil, errors.Errorf("number of platforms does not match references %d %d", len(ps.Platforms), len(inp.Refs))
   121  		}
   122  		config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, ps.Platforms[0].ID)]
   123  	}
   124  
   125  	var diffs []digest.Digest
   126  	if ref != nil {
   127  		layersDone := oneOffProgress(ctx, "exporting layers")
   128  
   129  		if err := ref.Finalize(ctx); err != nil {
   130  			return nil, nil, layersDone(err)
   131  		}
   132  
   133  		if err := ref.Extract(ctx, nil); err != nil {
   134  			return nil, nil, err
   135  		}
   136  
   137  		diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID())
   138  		if err != nil {
   139  			return nil, nil, layersDone(err)
   140  		}
   141  
   142  		diffs = make([]digest.Digest, len(diffIDs))
   143  		for i := range diffIDs {
   144  			diffs[i] = digest.Digest(diffIDs[i])
   145  		}
   146  
   147  		_ = layersDone(nil)
   148  	}
   149  
   150  	if len(config) == 0 {
   151  		var err error
   152  		config, err = emptyImageConfig()
   153  		if err != nil {
   154  			return nil, nil, err
   155  		}
   156  	}
   157  
   158  	history, err := parseHistoryFromConfig(config)
   159  	if err != nil {
   160  		return nil, nil, err
   161  	}
   162  
   163  	diffs, history = normalizeLayersAndHistory(diffs, history, ref)
   164  
   165  	var inlineCacheEntry *exptypes.InlineCacheEntry
   166  	if inlineCache != nil {
   167  		inlineCacheResult, err := inlineCache(ctx)
   168  		if err != nil {
   169  			return nil, nil, err
   170  		}
   171  		if inlineCacheResult != nil {
   172  			if ref != nil {
   173  				inlineCacheEntry, _ = inlineCacheResult.FindRef(ref.ID())
   174  			} else {
   175  				inlineCacheEntry = inlineCacheResult.Ref
   176  			}
   177  		}
   178  	}
   179  	config, err = patchImageConfig(config, diffs, history, inlineCacheEntry)
   180  	if err != nil {
   181  		return nil, nil, err
   182  	}
   183  
   184  	configDigest := digest.FromBytes(config)
   185  
   186  	configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest))
   187  	id, err := e.opt.ImageStore.Create(config)
   188  	if err != nil {
   189  		return nil, nil, configDone(err)
   190  	}
   191  	_ = configDone(nil)
   192  
   193  	var names []string
   194  	for _, targetName := range e.targetNames {
   195  		names = append(names, targetName.String())
   196  		if e.opt.ImageTagger != nil {
   197  			tagDone := oneOffProgress(ctx, "naming to "+targetName.String())
   198  			if err := e.opt.ImageTagger.TagImage(ctx, image.ID(digest.Digest(id)), targetName); err != nil {
   199  				return nil, nil, tagDone(err)
   200  			}
   201  			_ = tagDone(nil)
   202  		}
   203  	}
   204  
   205  	resp := map[string]string{
   206  		exptypes.ExporterImageConfigDigestKey: configDigest.String(),
   207  		exptypes.ExporterImageDigestKey:       id.String(),
   208  	}
   209  	if len(names) > 0 {
   210  		resp["image.name"] = strings.Join(names, ",")
   211  	}
   212  
   213  	descRef, err := e.newTempReference(ctx, config)
   214  	if err != nil {
   215  		return nil, nil, fmt.Errorf("failed to create a temporary descriptor reference: %w", err)
   216  	}
   217  
   218  	return resp, descRef, nil
   219  }
   220  
   221  func (e *imageExporterInstance) newTempReference(ctx context.Context, config []byte) (exporter.DescriptorReference, error) {
   222  	lm := e.opt.LeaseManager
   223  
   224  	dgst := digest.FromBytes(config)
   225  	lease, err := lm.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour))
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	desc := ocispec.Descriptor{
   231  		Digest:    dgst,
   232  		MediaType: "application/vnd.docker.container.image.v1+json",
   233  		Size:      int64(len(config)),
   234  	}
   235  
   236  	if err := content.WriteBlob(ctx, e.opt.ContentStore, desc.Digest.String(), bytes.NewReader(config), desc); err != nil {
   237  		return nil, fmt.Errorf("failed to save temporary image config: %w", err)
   238  	}
   239  
   240  	return containerimage.NewDescriptorReference(desc, func(ctx context.Context) error {
   241  		return lm.Delete(ctx, lease)
   242  	}), nil
   243  }