github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/builder/builder-next/exporter/mobyexporter/writer.go (about)

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