github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/builder/builder-next/exporter/export.go (about)

     1  package containerimage
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  
     9  	distref "github.com/docker/distribution/reference"
    10  	"github.com/docker/docker/image"
    11  	"github.com/docker/docker/layer"
    12  	"github.com/docker/docker/reference"
    13  	"github.com/moby/buildkit/exporter"
    14  	"github.com/moby/buildkit/exporter/containerimage/exptypes"
    15  	digest "github.com/opencontainers/go-digest"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  const (
    21  	keyImageName = "name"
    22  )
    23  
    24  // Differ can make a moby layer from a snapshot
    25  type Differ interface {
    26  	EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error)
    27  }
    28  
    29  // Opt defines a struct for creating new exporter
    30  type Opt struct {
    31  	ImageStore     image.Store
    32  	ReferenceStore reference.Store
    33  	Differ         Differ
    34  }
    35  
    36  type imageExporter struct {
    37  	opt Opt
    38  }
    39  
    40  // New creates a new moby imagestore exporter
    41  func New(opt Opt) (exporter.Exporter, error) {
    42  	im := &imageExporter{opt: opt}
    43  	return im, nil
    44  }
    45  
    46  func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
    47  	i := &imageExporterInstance{imageExporter: e}
    48  	for k, v := range opt {
    49  		switch k {
    50  		case keyImageName:
    51  			for _, v := range strings.Split(v, ",") {
    52  				ref, err := distref.ParseNormalizedNamed(v)
    53  				if err != nil {
    54  					return nil, err
    55  				}
    56  				i.targetNames = append(i.targetNames, ref)
    57  			}
    58  		case exptypes.ExporterImageConfigKey:
    59  			if i.meta == nil {
    60  				i.meta = make(map[string][]byte)
    61  			}
    62  			i.meta[k] = []byte(v)
    63  		default:
    64  			logrus.Warnf("image exporter: unknown option %s", k)
    65  		}
    66  	}
    67  	return i, nil
    68  }
    69  
    70  type imageExporterInstance struct {
    71  	*imageExporter
    72  	targetNames []distref.Named
    73  	meta        map[string][]byte
    74  }
    75  
    76  func (e *imageExporterInstance) Name() string {
    77  	return "exporting to image"
    78  }
    79  
    80  func (e *imageExporterInstance) Export(ctx context.Context, inp exporter.Source) (map[string]string, error) {
    81  
    82  	if len(inp.Refs) > 1 {
    83  		return nil, fmt.Errorf("exporting multiple references to image store is currently unsupported")
    84  	}
    85  
    86  	ref := inp.Ref
    87  	if ref != nil && len(inp.Refs) == 1 {
    88  		return nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive")
    89  	}
    90  
    91  	// only one loop
    92  	for _, v := range inp.Refs {
    93  		ref = v
    94  	}
    95  
    96  	var config []byte
    97  	switch len(inp.Refs) {
    98  	case 0:
    99  		config = inp.Metadata[exptypes.ExporterImageConfigKey]
   100  	case 1:
   101  		platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey]
   102  		if !ok {
   103  			return nil, fmt.Errorf("cannot export image, missing platforms mapping")
   104  		}
   105  		var p exptypes.Platforms
   106  		if err := json.Unmarshal(platformsBytes, &p); err != nil {
   107  			return nil, errors.Wrapf(err, "failed to parse platforms passed to exporter")
   108  		}
   109  		if len(p.Platforms) != len(inp.Refs) {
   110  			return nil, errors.Errorf("number of platforms does not match references %d %d", len(p.Platforms), len(inp.Refs))
   111  		}
   112  		config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, p.Platforms[0].ID)]
   113  	}
   114  
   115  	var diffs []digest.Digest
   116  	if ref != nil {
   117  		layersDone := oneOffProgress(ctx, "exporting layers")
   118  
   119  		if err := ref.Finalize(ctx, true); err != nil {
   120  			return nil, layersDone(err)
   121  		}
   122  
   123  		diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID())
   124  		if err != nil {
   125  			return nil, layersDone(err)
   126  		}
   127  
   128  		diffs = make([]digest.Digest, len(diffIDs))
   129  		for i := range diffIDs {
   130  			diffs[i] = digest.Digest(diffIDs[i])
   131  		}
   132  
   133  		_ = layersDone(nil)
   134  	}
   135  
   136  	if len(config) == 0 {
   137  		var err error
   138  		config, err = emptyImageConfig()
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  	}
   143  
   144  	history, err := parseHistoryFromConfig(config)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	diffs, history = normalizeLayersAndHistory(diffs, history, ref)
   150  
   151  	config, err = patchImageConfig(config, diffs, history, inp.Metadata[exptypes.ExporterInlineCache])
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	configDigest := digest.FromBytes(config)
   157  
   158  	configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest))
   159  	id, err := e.opt.ImageStore.Create(config)
   160  	if err != nil {
   161  		return nil, configDone(err)
   162  	}
   163  	_ = configDone(nil)
   164  
   165  	if e.opt.ReferenceStore != nil {
   166  		for _, targetName := range e.targetNames {
   167  			tagDone := oneOffProgress(ctx, "naming to "+targetName.String())
   168  
   169  			if err := e.opt.ReferenceStore.AddTag(targetName, digest.Digest(id), true); err != nil {
   170  				return nil, tagDone(err)
   171  			}
   172  			_ = tagDone(nil)
   173  		}
   174  	}
   175  
   176  	return map[string]string{
   177  		"containerimage.digest": id.String(),
   178  	}, nil
   179  }