gopkg.in/docker/docker.v20@v20.10.27/builder/builder-next/exporter/writer.go (about)

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