github.com/rumpl/bof@v23.0.0-rc.2+incompatible/builder/builder-next/exporter/writer.go (about)

     1  package containerimage
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"time"
     7  
     8  	"github.com/containerd/containerd/platforms"
     9  	"github.com/moby/buildkit/cache"
    10  	binfotypes "github.com/moby/buildkit/util/buildinfo/types"
    11  	"github.com/moby/buildkit/util/progress"
    12  	"github.com/moby/buildkit/util/system"
    13  	"github.com/opencontainers/go-digest"
    14  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  // const (
    20  // 	emptyGZLayer = digest.Digest("sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1")
    21  // )
    22  
    23  func emptyImageConfig() ([]byte, error) {
    24  	pl := platforms.Normalize(platforms.DefaultSpec())
    25  	img := ocispec.Image{}
    26  	img.Architecture = pl.Architecture
    27  	img.OS = pl.OS
    28  	img.Variant = pl.Variant
    29  	img.RootFS.Type = "layers"
    30  	img.Config.WorkingDir = "/"
    31  	img.Config.Env = []string{"PATH=" + system.DefaultPathEnv(pl.OS)}
    32  	dt, err := json.Marshal(img)
    33  	return dt, errors.Wrap(err, "failed to create empty image config")
    34  }
    35  
    36  func parseHistoryFromConfig(dt []byte) ([]ocispec.History, error) {
    37  	var config struct {
    38  		History []ocispec.History
    39  	}
    40  	if err := json.Unmarshal(dt, &config); err != nil {
    41  		return nil, errors.Wrap(err, "failed to unmarshal history from config")
    42  	}
    43  	return config.History, nil
    44  }
    45  
    46  func patchImageConfig(dt []byte, dps []digest.Digest, history []ocispec.History, cache []byte, buildInfo []byte) ([]byte, error) {
    47  	m := map[string]json.RawMessage{}
    48  	if err := json.Unmarshal(dt, &m); err != nil {
    49  		return nil, errors.Wrap(err, "failed to parse image config for patch")
    50  	}
    51  
    52  	var rootFS ocispec.RootFS
    53  	rootFS.Type = "layers"
    54  	rootFS.DiffIDs = append(rootFS.DiffIDs, dps...)
    55  
    56  	dt, err := json.Marshal(rootFS)
    57  	if err != nil {
    58  		return nil, errors.Wrap(err, "failed to marshal rootfs")
    59  	}
    60  	m["rootfs"] = dt
    61  
    62  	dt, err = json.Marshal(history)
    63  	if err != nil {
    64  		return nil, errors.Wrap(err, "failed to marshal history")
    65  	}
    66  	m["history"] = dt
    67  
    68  	if _, ok := m["created"]; !ok {
    69  		var tm *time.Time
    70  		for _, h := range history {
    71  			if h.Created != nil {
    72  				tm = h.Created
    73  			}
    74  		}
    75  		dt, err = json.Marshal(&tm)
    76  		if err != nil {
    77  			return nil, errors.Wrap(err, "failed to marshal creation time")
    78  		}
    79  		m["created"] = dt
    80  	}
    81  
    82  	if cache != nil {
    83  		dt, err := json.Marshal(cache)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		m["moby.buildkit.cache.v0"] = dt
    88  	}
    89  
    90  	if buildInfo != nil {
    91  		dt, err := json.Marshal(buildInfo)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  		m[binfotypes.ImageConfigField] = dt
    96  	} else {
    97  		delete(m, binfotypes.ImageConfigField)
    98  	}
    99  
   100  	dt, err = json.Marshal(m)
   101  	return dt, errors.Wrap(err, "failed to marshal config after patch")
   102  }
   103  
   104  func normalizeLayersAndHistory(diffs []digest.Digest, history []ocispec.History, ref cache.ImmutableRef) ([]digest.Digest, []ocispec.History) {
   105  	refMeta := getRefMetadata(ref, len(diffs))
   106  	var historyLayers int
   107  	for _, h := range history {
   108  		if !h.EmptyLayer {
   109  			historyLayers++
   110  		}
   111  	}
   112  	if historyLayers > len(diffs) {
   113  		// this case shouldn't happen but if it does force set history layers empty
   114  		// from the bottom
   115  		logrus.Warn("invalid image config with unaccounted layers")
   116  		historyCopy := make([]ocispec.History, 0, len(history))
   117  		var l int
   118  		for _, h := range history {
   119  			if l >= len(diffs) {
   120  				h.EmptyLayer = true
   121  			}
   122  			if !h.EmptyLayer {
   123  				l++
   124  			}
   125  			historyCopy = append(historyCopy, h)
   126  		}
   127  		history = historyCopy
   128  	}
   129  
   130  	if len(diffs) > historyLayers {
   131  		// some history items are missing. add them based on the ref metadata
   132  		for _, md := range refMeta[historyLayers:] {
   133  			history = append(history, ocispec.History{
   134  				Created:   md.createdAt,
   135  				CreatedBy: md.description,
   136  				Comment:   "buildkit.exporter.image.v0",
   137  			})
   138  		}
   139  	}
   140  
   141  	var layerIndex int
   142  	for i, h := range history {
   143  		if !h.EmptyLayer {
   144  			if h.Created == nil {
   145  				h.Created = refMeta[layerIndex].createdAt
   146  			}
   147  			layerIndex++
   148  		}
   149  		history[i] = h
   150  	}
   151  
   152  	// Find the first new layer time. Otherwise, the history item for a first
   153  	// metadata command would be the creation time of a base image layer.
   154  	// If there is no such then the last layer with timestamp.
   155  	var created *time.Time
   156  	var noCreatedTime bool
   157  	for _, h := range history {
   158  		if h.Created != nil {
   159  			created = h.Created
   160  			if noCreatedTime {
   161  				break
   162  			}
   163  		} else {
   164  			noCreatedTime = true
   165  		}
   166  	}
   167  
   168  	// Fill in created times for all history items to be either the first new
   169  	// layer time or the previous layer.
   170  	noCreatedTime = false
   171  	for i, h := range history {
   172  		if h.Created != nil {
   173  			if noCreatedTime {
   174  				created = h.Created
   175  			}
   176  		} else {
   177  			noCreatedTime = true
   178  			h.Created = created
   179  		}
   180  		history[i] = h
   181  	}
   182  
   183  	return diffs, history
   184  }
   185  
   186  type refMetadata struct {
   187  	description string
   188  	createdAt   *time.Time
   189  }
   190  
   191  func getRefMetadata(ref cache.ImmutableRef, limit int) []refMetadata {
   192  	if ref == nil {
   193  		return make([]refMetadata, limit)
   194  	}
   195  
   196  	layerChain := ref.LayerChain()
   197  	defer layerChain.Release(context.TODO())
   198  
   199  	if limit < len(layerChain) {
   200  		layerChain = layerChain[len(layerChain)-limit:]
   201  	}
   202  
   203  	metas := make([]refMetadata, len(layerChain))
   204  	for i, layer := range layerChain {
   205  		meta := &metas[i]
   206  
   207  		if description := layer.GetDescription(); description != "" {
   208  			meta.description = description
   209  		} else {
   210  			meta.description = "created by buildkit" // shouldn't be shown but don't fail build
   211  		}
   212  
   213  		createdAt := layer.GetCreatedAt()
   214  		meta.createdAt = &createdAt
   215  	}
   216  	return metas
   217  }
   218  
   219  func oneOffProgress(ctx context.Context, id string) func(err error) error {
   220  	pw, _, _ := progress.NewFromContext(ctx)
   221  	now := time.Now()
   222  	st := progress.Status{
   223  		Started: &now,
   224  	}
   225  	_ = pw.Write(id, st)
   226  	return func(err error) error {
   227  		// TODO: set error on status
   228  		now := time.Now()
   229  		st.Completed = &now
   230  		_ = pw.Write(id, st)
   231  		_ = pw.Close()
   232  		return err
   233  	}
   234  }