mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
fda0226a89
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
218 lines
5.3 KiB
Go
218 lines
5.3 KiB
Go
package containerimage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
distref "github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/layer"
|
|
"github.com/docker/docker/reference"
|
|
"github.com/moby/buildkit/exporter"
|
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
|
"github.com/moby/buildkit/util/compression"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
keyImageName = "name"
|
|
keyBuildInfo = "buildinfo"
|
|
keyBuildInfoAttrs = "buildinfo-attrs"
|
|
)
|
|
|
|
// Differ can make a moby layer from a snapshot
|
|
type Differ interface {
|
|
EnsureLayer(ctx context.Context, key string) ([]layer.DiffID, error)
|
|
}
|
|
|
|
// Opt defines a struct for creating new exporter
|
|
type Opt struct {
|
|
ImageStore image.Store
|
|
ReferenceStore reference.Store
|
|
Differ Differ
|
|
}
|
|
|
|
type imageExporter struct {
|
|
opt Opt
|
|
}
|
|
|
|
// New creates a new moby imagestore exporter
|
|
func New(opt Opt) (exporter.Exporter, error) {
|
|
im := &imageExporter{opt: opt}
|
|
return im, nil
|
|
}
|
|
|
|
func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
|
|
i := &imageExporterInstance{
|
|
imageExporter: e,
|
|
buildInfo: true,
|
|
}
|
|
for k, v := range opt {
|
|
switch k {
|
|
case keyImageName:
|
|
i.targetName = v
|
|
case keyBuildInfo:
|
|
if v == "" {
|
|
i.buildInfo = true
|
|
continue
|
|
}
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "non-bool value specified for %s", k)
|
|
}
|
|
i.buildInfo = b
|
|
case keyBuildInfoAttrs:
|
|
if v == "" {
|
|
i.buildInfoAttrs = false
|
|
continue
|
|
}
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "non-bool value specified for %s", k)
|
|
}
|
|
i.buildInfoAttrs = b
|
|
default:
|
|
if i.meta == nil {
|
|
i.meta = make(map[string][]byte)
|
|
}
|
|
i.meta[k] = []byte(v)
|
|
}
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
type imageExporterInstance struct {
|
|
*imageExporter
|
|
targetName string
|
|
meta map[string][]byte
|
|
buildInfo bool
|
|
buildInfoAttrs bool
|
|
}
|
|
|
|
func (e *imageExporterInstance) Name() string {
|
|
return "exporting to image"
|
|
}
|
|
|
|
func (e *imageExporterInstance) Config() exporter.Config {
|
|
return exporter.Config{
|
|
Compression: compression.Config{
|
|
Type: compression.Default,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *imageExporterInstance) Export(ctx context.Context, inp exporter.Source, sessionID string) (map[string]string, error) {
|
|
if len(inp.Refs) > 1 {
|
|
return nil, fmt.Errorf("exporting multiple references to image store is currently unsupported")
|
|
}
|
|
|
|
ref := inp.Ref
|
|
if ref != nil && len(inp.Refs) == 1 {
|
|
return nil, fmt.Errorf("invalid exporter input: Ref and Refs are mutually exclusive")
|
|
}
|
|
|
|
// only one loop
|
|
for _, v := range inp.Refs {
|
|
ref = v
|
|
}
|
|
|
|
var config []byte
|
|
var buildInfo []byte
|
|
switch len(inp.Refs) {
|
|
case 0:
|
|
config = inp.Metadata[exptypes.ExporterImageConfigKey]
|
|
if v, ok := inp.Metadata[exptypes.ExporterBuildInfo]; ok {
|
|
buildInfo = v
|
|
}
|
|
case 1:
|
|
platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot export image, missing platforms mapping")
|
|
}
|
|
var p exptypes.Platforms
|
|
if err := json.Unmarshal(platformsBytes, &p); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse platforms passed to exporter")
|
|
}
|
|
if len(p.Platforms) != len(inp.Refs) {
|
|
return nil, errors.Errorf("number of platforms does not match references %d %d", len(p.Platforms), len(inp.Refs))
|
|
}
|
|
config = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, p.Platforms[0].ID)]
|
|
if v, ok := inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p.Platforms[0].ID)]; ok {
|
|
buildInfo = v
|
|
}
|
|
}
|
|
|
|
var diffs []digest.Digest
|
|
if ref != nil {
|
|
layersDone := oneOffProgress(ctx, "exporting layers")
|
|
|
|
if err := ref.Finalize(ctx); err != nil {
|
|
return nil, layersDone(err)
|
|
}
|
|
|
|
diffIDs, err := e.opt.Differ.EnsureLayer(ctx, ref.ID())
|
|
if err != nil {
|
|
return nil, layersDone(err)
|
|
}
|
|
|
|
diffs = make([]digest.Digest, len(diffIDs))
|
|
for i := range diffIDs {
|
|
diffs[i] = digest.Digest(diffIDs[i])
|
|
}
|
|
|
|
_ = layersDone(nil)
|
|
}
|
|
|
|
if len(config) == 0 {
|
|
var err error
|
|
config, err = emptyImageConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
history, err := parseHistoryFromConfig(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diffs, history = normalizeLayersAndHistory(diffs, history, ref)
|
|
|
|
config, err = patchImageConfig(config, diffs, history, inp.Metadata[exptypes.ExporterInlineCache], buildInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configDigest := digest.FromBytes(config)
|
|
|
|
configDone := oneOffProgress(ctx, fmt.Sprintf("writing image %s", configDigest))
|
|
id, err := e.opt.ImageStore.Create(config)
|
|
if err != nil {
|
|
return nil, configDone(err)
|
|
}
|
|
_ = configDone(nil)
|
|
|
|
if e.opt.ReferenceStore != nil {
|
|
targetNames := strings.Split(e.targetName, ",")
|
|
for _, targetName := range targetNames {
|
|
tagDone := oneOffProgress(ctx, "naming to "+targetName)
|
|
tref, err := distref.ParseNormalizedNamed(targetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := e.opt.ReferenceStore.AddTag(tref, digest.Digest(id), true); err != nil {
|
|
return nil, tagDone(err)
|
|
}
|
|
_ = tagDone(nil)
|
|
}
|
|
}
|
|
|
|
return map[string]string{
|
|
exptypes.ExporterImageConfigDigestKey: configDigest.String(),
|
|
exptypes.ExporterImageDigestKey: id.String(),
|
|
}, nil
|
|
}
|