github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/builder/builder-next/exporter/export.go (about)

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