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