123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package mobyexporter
- import (
- "context"
- "encoding/json"
- "time"
- "github.com/containerd/containerd/platforms"
- "github.com/containerd/log"
- "github.com/moby/buildkit/cache"
- "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"
- )
- 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) ([]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
- }
- 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
- log.G(context.TODO()).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
- }
- }
|