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{} img.Architecture = pl.Architecture img.OS = pl.OS img.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 } }