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>
235 lines
5.6 KiB
Go
235 lines
5.6 KiB
Go
package containerimage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/moby/buildkit/cache"
|
|
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
|
|
"github.com/moby/buildkit/util/progress"
|
|
"github.com/moby/buildkit/util/system"
|
|
"github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// const (
|
|
// emptyGZLayer = digest.Digest("sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1")
|
|
// )
|
|
|
|
func emptyImageConfig() ([]byte, error) {
|
|
pl := platforms.Normalize(platforms.DefaultSpec())
|
|
img := ocispec.Image{
|
|
Architecture: pl.Architecture,
|
|
OS: pl.OS,
|
|
Variant: pl.Variant,
|
|
}
|
|
img.RootFS.Type = "layers"
|
|
img.Config.WorkingDir = "/"
|
|
img.Config.Env = []string{"PATH=" + system.DefaultPathEnv(pl.OS)}
|
|
dt, err := json.Marshal(img)
|
|
return dt, errors.Wrap(err, "failed to create empty image config")
|
|
}
|
|
|
|
func parseHistoryFromConfig(dt []byte) ([]ocispec.History, error) {
|
|
var config struct {
|
|
History []ocispec.History
|
|
}
|
|
if err := json.Unmarshal(dt, &config); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal history from config")
|
|
}
|
|
return config.History, nil
|
|
}
|
|
|
|
func patchImageConfig(dt []byte, dps []digest.Digest, history []ocispec.History, cache []byte, buildInfo []byte) ([]byte, error) {
|
|
m := map[string]json.RawMessage{}
|
|
if err := json.Unmarshal(dt, &m); err != nil {
|
|
return nil, errors.Wrap(err, "failed to parse image config for patch")
|
|
}
|
|
|
|
var rootFS ocispec.RootFS
|
|
rootFS.Type = "layers"
|
|
rootFS.DiffIDs = append(rootFS.DiffIDs, dps...)
|
|
|
|
dt, err := json.Marshal(rootFS)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal rootfs")
|
|
}
|
|
m["rootfs"] = dt
|
|
|
|
dt, err = json.Marshal(history)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal history")
|
|
}
|
|
m["history"] = dt
|
|
|
|
if _, ok := m["created"]; !ok {
|
|
var tm *time.Time
|
|
for _, h := range history {
|
|
if h.Created != nil {
|
|
tm = h.Created
|
|
}
|
|
}
|
|
dt, err = json.Marshal(&tm)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal creation time")
|
|
}
|
|
m["created"] = dt
|
|
}
|
|
|
|
if cache != nil {
|
|
dt, err := json.Marshal(cache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m["moby.buildkit.cache.v0"] = dt
|
|
}
|
|
|
|
if buildInfo != nil {
|
|
dt, err := json.Marshal(buildInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m[binfotypes.ImageConfigField] = dt
|
|
} else {
|
|
delete(m, binfotypes.ImageConfigField)
|
|
}
|
|
|
|
dt, err = json.Marshal(m)
|
|
return dt, errors.Wrap(err, "failed to marshal config after patch")
|
|
}
|
|
|
|
func normalizeLayersAndHistory(diffs []digest.Digest, history []ocispec.History, ref cache.ImmutableRef) ([]digest.Digest, []ocispec.History) {
|
|
refMeta := getRefMetadata(ref, len(diffs))
|
|
var historyLayers int
|
|
for _, h := range history {
|
|
if !h.EmptyLayer {
|
|
historyLayers++
|
|
}
|
|
}
|
|
if historyLayers > len(diffs) {
|
|
// this case shouldn't happen but if it does force set history layers empty
|
|
// from the bottom
|
|
logrus.Warn("invalid image config with unaccounted layers")
|
|
historyCopy := make([]ocispec.History, 0, len(history))
|
|
var l int
|
|
for _, h := range history {
|
|
if l >= len(diffs) {
|
|
h.EmptyLayer = true
|
|
}
|
|
if !h.EmptyLayer {
|
|
l++
|
|
}
|
|
historyCopy = append(historyCopy, h)
|
|
}
|
|
history = historyCopy
|
|
}
|
|
|
|
if len(diffs) > historyLayers {
|
|
// some history items are missing. add them based on the ref metadata
|
|
for _, md := range refMeta[historyLayers:] {
|
|
history = append(history, ocispec.History{
|
|
Created: md.createdAt,
|
|
CreatedBy: md.description,
|
|
Comment: "buildkit.exporter.image.v0",
|
|
})
|
|
}
|
|
}
|
|
|
|
var layerIndex int
|
|
for i, h := range history {
|
|
if !h.EmptyLayer {
|
|
if h.Created == nil {
|
|
h.Created = refMeta[layerIndex].createdAt
|
|
}
|
|
layerIndex++
|
|
}
|
|
history[i] = h
|
|
}
|
|
|
|
// Find the first new layer time. Otherwise, the history item for a first
|
|
// metadata command would be the creation time of a base image layer.
|
|
// If there is no such then the last layer with timestamp.
|
|
var created *time.Time
|
|
var noCreatedTime bool
|
|
for _, h := range history {
|
|
if h.Created != nil {
|
|
created = h.Created
|
|
if noCreatedTime {
|
|
break
|
|
}
|
|
} else {
|
|
noCreatedTime = true
|
|
}
|
|
}
|
|
|
|
// Fill in created times for all history items to be either the first new
|
|
// layer time or the previous layer.
|
|
noCreatedTime = false
|
|
for i, h := range history {
|
|
if h.Created != nil {
|
|
if noCreatedTime {
|
|
created = h.Created
|
|
}
|
|
} else {
|
|
noCreatedTime = true
|
|
h.Created = created
|
|
}
|
|
history[i] = h
|
|
}
|
|
|
|
return diffs, history
|
|
}
|
|
|
|
type refMetadata struct {
|
|
description string
|
|
createdAt *time.Time
|
|
}
|
|
|
|
func getRefMetadata(ref cache.ImmutableRef, limit int) []refMetadata {
|
|
if ref == nil {
|
|
return make([]refMetadata, limit)
|
|
}
|
|
|
|
layerChain := ref.LayerChain()
|
|
defer layerChain.Release(context.TODO())
|
|
|
|
if limit < len(layerChain) {
|
|
layerChain = layerChain[len(layerChain)-limit:]
|
|
}
|
|
|
|
metas := make([]refMetadata, len(layerChain))
|
|
for i, layer := range layerChain {
|
|
meta := &metas[i]
|
|
|
|
if description := layer.GetDescription(); description != "" {
|
|
meta.description = description
|
|
} else {
|
|
meta.description = "created by buildkit" // shouldn't be shown but don't fail build
|
|
}
|
|
|
|
createdAt := layer.GetCreatedAt()
|
|
meta.createdAt = &createdAt
|
|
}
|
|
return metas
|
|
}
|
|
|
|
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
|
pw, _, _ := progress.NewFromContext(ctx)
|
|
now := time.Now()
|
|
st := progress.Status{
|
|
Started: &now,
|
|
}
|
|
_ = pw.Write(id, st)
|
|
return func(err error) error {
|
|
// TODO: set error on status
|
|
now := time.Now()
|
|
st.Completed = &now
|
|
_ = pw.Write(id, st)
|
|
_ = pw.Close()
|
|
return err
|
|
}
|
|
}
|