package containerimage import ( "context" "encoding/json" "runtime" "time" "github.com/moby/buildkit/cache" "github.com/moby/buildkit/util/progress" "github.com/moby/buildkit/util/system" digest "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) { img := ocispec.Image{ Architecture: runtime.GOARCH, OS: runtime.GOOS, } img.RootFS.Type = "layers" img.Config.WorkingDir = "/" img.Config.Env = []string{"PATH=" + system.DefaultPathEnv} 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, errors.Wrap(err, "failed to marshal cache") } 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 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 limit <= 0 { return nil } meta := refMetadata{ description: "created by buildkit", // shouldn't be shown but don't fail build createdAt: time.Now(), } if ref == nil { return append(getRefMetadata(nil, limit-1), meta) } if descr := cache.GetDescription(ref.Metadata()); descr != "" { meta.description = descr } meta.createdAt = cache.GetCreatedAt(ref.Metadata()) p := ref.Parent() if p != nil { defer p.Release(context.TODO()) } return append(getRefMetadata(p, limit-1), meta) } func oneOffProgress(ctx context.Context, id string) func(err error) error { pw, _, _ := progress.FromContext(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 } }