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