| package containerimage |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "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" |
| digest "github.com/opencontainers/go-digest" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| const ( |
| keyImageName = "name" |
| ) |
| |
| // 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} |
| for k, v := range opt { |
| switch k { |
| case keyImageName: |
| for _, v := range strings.Split(v, ",") { |
| ref, err := distref.ParseNormalizedNamed(v) |
| if err != nil { |
| return nil, err |
| } |
| i.targetNames = append(i.targetNames, ref) |
| } |
| case exptypes.ExporterImageConfigKey: |
| if i.meta == nil { |
| i.meta = make(map[string][]byte) |
| } |
| i.meta[k] = []byte(v) |
| default: |
| logrus.Warnf("image exporter: unknown option %s", k) |
| } |
| } |
| return i, nil |
| } |
| |
| type imageExporterInstance struct { |
| *imageExporter |
| targetNames []distref.Named |
| meta map[string][]byte |
| } |
| |
| func (e *imageExporterInstance) Name() string { |
| return "exporting to image" |
| } |
| |
| func (e *imageExporterInstance) Export(ctx context.Context, inp exporter.Source) (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 |
| switch len(inp.Refs) { |
| case 0: |
| config = inp.Metadata[exptypes.ExporterImageConfigKey] |
| 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)] |
| } |
| |
| var diffs []digest.Digest |
| if ref != nil { |
| layersDone := oneOffProgress(ctx, "exporting layers") |
| |
| if err := ref.Finalize(ctx, true); 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]) |
| 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 { |
| for _, targetName := range e.targetNames { |
| tagDone := oneOffProgress(ctx, "naming to "+targetName.String()) |
| |
| if err := e.opt.ReferenceStore.AddTag(targetName, digest.Digest(id), true); err != nil { |
| return nil, tagDone(err) |
| } |
| tagDone(nil) |
| } |
| } |
| |
| return map[string]string{ |
| "containerimage.digest": id.String(), |
| }, nil |
| } |