Do not re-tag non-distributable blob descriptors
Before this change buildkit was changing the media type for non-distributable layers to normal layers. It was also clearing out the urls to get those blobs. Now the layer mediatype and URL's are preserved. If a layer blob is seen more than once, if it has extra URL's they will be appended to the stored value. On export there is now a new exporter option to preserve the non-distributable data values. All URL's seen by buildkit will be added to the exported content. Signed-off-by: Brian Goff <cpuguy83@gmail.com>master
parent
2f996517bc
commit
78bb7137ee
|
@ -306,6 +306,7 @@ func (sr *immutableRef) setBlob(ctx context.Context, compressionType compression
|
||||||
sr.queueBlob(desc.Digest)
|
sr.queueBlob(desc.Digest)
|
||||||
sr.queueMediaType(desc.MediaType)
|
sr.queueMediaType(desc.MediaType)
|
||||||
sr.queueBlobSize(desc.Size)
|
sr.queueBlobSize(desc.Size)
|
||||||
|
sr.appendURLs(desc.URLs)
|
||||||
if err := sr.commitMetadata(); err != nil {
|
if err := sr.commitMetadata(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,6 +283,7 @@ func (cm *cacheManager) GetByBlob(ctx context.Context, desc ocispecs.Descriptor,
|
||||||
rec.queueBlobOnly(blobOnly)
|
rec.queueBlobOnly(blobOnly)
|
||||||
rec.queueMediaType(desc.MediaType)
|
rec.queueMediaType(desc.MediaType)
|
||||||
rec.queueBlobSize(desc.Size)
|
rec.queueBlobSize(desc.Size)
|
||||||
|
rec.appendURLs(desc.URLs)
|
||||||
rec.queueCommitted(true)
|
rec.queueCommitted(true)
|
||||||
|
|
||||||
if err := rec.commitMetadata(); err != nil {
|
if err := rec.commitMetadata(); err != nil {
|
||||||
|
|
|
@ -1508,6 +1508,65 @@ func checkVariantsCoverage(ctx context.Context, t *testing.T, variants idxToVari
|
||||||
require.Equal(t, 0, len(got))
|
require.Equal(t, 0, len(got))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that media type and urls are persisted for non-distributable blobs.
|
||||||
|
func TestNondistributableBlobs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), "buildkit-test")
|
||||||
|
|
||||||
|
tmpdir, err := ioutil.TempDir("", "cachemanager")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
snapshotter, err := native.NewSnapshotter(filepath.Join(tmpdir, "snapshots"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
co, cleanup, err := newCacheManager(ctx, cmOpt{
|
||||||
|
snapshotter: snapshotter,
|
||||||
|
snapshotterName: "native",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
cm := co.manager
|
||||||
|
|
||||||
|
ctx, done, err := leaseutil.WithLease(ctx, co.lm, leaseutil.MakeTemporary)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer done(context.TODO())
|
||||||
|
|
||||||
|
contentBuffer := contentutil.NewBuffer()
|
||||||
|
descHandlers := DescHandlers(map[digest.Digest]*DescHandler{})
|
||||||
|
|
||||||
|
data, desc, err := mapToBlob(map[string]string{"foo": "bar"}, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Pretend like this is non-distributable
|
||||||
|
desc.MediaType = ocispecs.MediaTypeImageLayerNonDistributable
|
||||||
|
desc.URLs = []string{"https://buildkit.moby.dev/foo"}
|
||||||
|
|
||||||
|
cw, err := contentBuffer.Writer(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = cw.Write(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = cw.Commit(ctx, 0, cw.Digest())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
descHandlers[desc.Digest] = &DescHandler{
|
||||||
|
Provider: func(_ session.Group) content.Provider { return contentBuffer },
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := cm.GetByBlob(ctx, desc, nil, descHandlers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
remotes, err := ref.GetRemotes(ctx, true, compression.Config{}, false, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
desc2 := remotes[0].Descriptors[0]
|
||||||
|
|
||||||
|
require.Equal(t, desc.MediaType, desc2.MediaType)
|
||||||
|
require.Equal(t, desc.URLs, desc2.URLs)
|
||||||
|
}
|
||||||
|
|
||||||
func checkInfo(ctx context.Context, t *testing.T, cs content.Store, info content.Info) {
|
func checkInfo(ctx context.Context, t *testing.T, cs content.Store, info content.Info) {
|
||||||
if info.Labels == nil {
|
if info.Labels == nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -37,6 +37,7 @@ const keyMediaType = "cache.mediatype"
|
||||||
const keyImageRefs = "cache.imageRefs"
|
const keyImageRefs = "cache.imageRefs"
|
||||||
const keyDeleted = "cache.deleted"
|
const keyDeleted = "cache.deleted"
|
||||||
const keyBlobSize = "cache.blobsize" // the packed blob size as specified in the oci descriptor
|
const keyBlobSize = "cache.blobsize" // the packed blob size as specified in the oci descriptor
|
||||||
|
const keyURLs = "cache.layer.urls"
|
||||||
|
|
||||||
// Indexes
|
// Indexes
|
||||||
const blobchainIndex = "blobchainid:"
|
const blobchainIndex = "blobchainid:"
|
||||||
|
@ -281,6 +282,17 @@ func (md *cacheMetadata) queueBlob(str digest.Digest) error {
|
||||||
return md.queueValue(keyBlob, str, "")
|
return md.queueValue(keyBlob, str, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (md *cacheMetadata) appendURLs(urls []string) error {
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return md.appendStringSlice(keyURLs, urls...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *cacheMetadata) getURLs() []string {
|
||||||
|
return md.GetStringSlice(keyURLs)
|
||||||
|
}
|
||||||
|
|
||||||
func (md *cacheMetadata) getBlob() digest.Digest {
|
func (md *cacheMetadata) getBlob() digest.Digest {
|
||||||
return digest.Digest(md.GetString(keyBlob))
|
return digest.Digest(md.GetString(keyBlob))
|
||||||
}
|
}
|
||||||
|
@ -468,6 +480,18 @@ func (md *cacheMetadata) GetString(key string) string {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (md *cacheMetadata) GetStringSlice(key string) []string {
|
||||||
|
v := md.si.Get(key)
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var val []string
|
||||||
|
if err := v.Unmarshal(&val); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
func (md *cacheMetadata) setTime(key string, value time.Time, index string) error {
|
func (md *cacheMetadata) setTime(key string, value time.Time, index string) error {
|
||||||
return md.setValue(key, value.UnixNano(), index)
|
return md.setValue(key, value.UnixNano(), index)
|
||||||
}
|
}
|
||||||
|
@ -512,7 +536,7 @@ func (md *cacheMetadata) getInt64(key string) (int64, bool) {
|
||||||
return i, true
|
return i, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (md *cacheMetadata) appendStringSlice(key string, value string) error {
|
func (md *cacheMetadata) appendStringSlice(key string, values ...string) error {
|
||||||
return md.si.GetAndSetValue(key, func(v *metadata.Value) (*metadata.Value, error) {
|
return md.si.GetAndSetValue(key, func(v *metadata.Value) (*metadata.Value, error) {
|
||||||
var slice []string
|
var slice []string
|
||||||
if v != nil {
|
if v != nil {
|
||||||
|
@ -520,12 +544,25 @@ func (md *cacheMetadata) appendStringSlice(key string, value string) error {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idx := make(map[string]struct{}, len(values))
|
||||||
|
for _, v := range values {
|
||||||
|
idx[v] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
for _, existing := range slice {
|
for _, existing := range slice {
|
||||||
if existing == value {
|
if _, ok := idx[existing]; ok {
|
||||||
return nil, metadata.ErrSkipSetValue
|
delete(idx, existing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slice = append(slice, value)
|
|
||||||
|
if len(idx) == 0 {
|
||||||
|
return nil, metadata.ErrSkipSetValue
|
||||||
|
}
|
||||||
|
|
||||||
|
for value := range idx {
|
||||||
|
slice = append(slice, value)
|
||||||
|
}
|
||||||
v, err := metadata.NewValue(slice)
|
v, err := metadata.NewValue(slice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -600,6 +600,7 @@ func (sr *immutableRef) ociDesc(ctx context.Context, dhs DescHandlers) (ocispecs
|
||||||
Size: sr.getBlobSize(),
|
Size: sr.getBlobSize(),
|
||||||
MediaType: sr.getMediaType(),
|
MediaType: sr.getMediaType(),
|
||||||
Annotations: make(map[string]string),
|
Annotations: make(map[string]string),
|
||||||
|
URLs: sr.getURLs(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if blobDesc, err := getBlobDesc(ctx, sr.cm.ContentStore, desc.Digest); err == nil {
|
if blobDesc, err := getBlobDesc(ctx, sr.cm.ContentStore, desc.Digest); err == nil {
|
||||||
|
@ -748,6 +749,7 @@ func getBlobDesc(ctx context.Context, cs content.Store, dgst digest.Digest) (oci
|
||||||
if !ok {
|
if !ok {
|
||||||
return ocispecs.Descriptor{}, fmt.Errorf("no media type is stored for %q", info.Digest)
|
return ocispecs.Descriptor{}, fmt.Errorf("no media type is stored for %q", info.Digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
desc := ocispecs.Descriptor{
|
desc := ocispecs.Descriptor{
|
||||||
Digest: info.Digest,
|
Digest: info.Digest,
|
||||||
Size: info.Size,
|
Size: info.Size,
|
||||||
|
|
|
@ -224,6 +224,7 @@ func (sr *immutableRef) getRemote(ctx context.Context, createIfNeeded bool, comp
|
||||||
newDesc.MediaType = blobDesc.MediaType
|
newDesc.MediaType = blobDesc.MediaType
|
||||||
newDesc.Digest = blobDesc.Digest
|
newDesc.Digest = blobDesc.Digest
|
||||||
newDesc.Size = blobDesc.Size
|
newDesc.Size = blobDesc.Size
|
||||||
|
newDesc.URLs = blobDesc.URLs
|
||||||
newDesc.Annotations = nil
|
newDesc.Annotations = nil
|
||||||
for _, k := range addAnnotations {
|
for _, k := range addAnnotations {
|
||||||
newDesc.Annotations[k] = desc.Annotations[k]
|
newDesc.Annotations[k] = desc.Annotations[k]
|
||||||
|
|
|
@ -44,6 +44,10 @@ const (
|
||||||
keyCompressionLevel = "compression-level"
|
keyCompressionLevel = "compression-level"
|
||||||
keyBuildInfo = "buildinfo"
|
keyBuildInfo = "buildinfo"
|
||||||
ociTypes = "oci-mediatypes"
|
ociTypes = "oci-mediatypes"
|
||||||
|
// propagateNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was
|
||||||
|
// already found to use a non-distributable media type.
|
||||||
|
// When this option is not set, the exporter will change the media type of the layer to a distributable one.
|
||||||
|
propagateNondistLayersKey = "propagate-nondist-layers"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opt struct {
|
type Opt struct {
|
||||||
|
@ -181,6 +185,12 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i.buildInfoMode = bimode
|
i.buildInfoMode = bimode
|
||||||
|
case propagateNondistLayersKey:
|
||||||
|
b, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "non-bool value %s specified for %s", v, k)
|
||||||
|
}
|
||||||
|
i.propagateNondistLayers = b
|
||||||
default:
|
default:
|
||||||
if i.meta == nil {
|
if i.meta == nil {
|
||||||
i.meta = make(map[string][]byte)
|
i.meta = make(map[string][]byte)
|
||||||
|
@ -197,19 +207,20 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp
|
||||||
|
|
||||||
type imageExporterInstance struct {
|
type imageExporterInstance struct {
|
||||||
*imageExporter
|
*imageExporter
|
||||||
targetName string
|
targetName string
|
||||||
push bool
|
push bool
|
||||||
pushByDigest bool
|
pushByDigest bool
|
||||||
unpack bool
|
unpack bool
|
||||||
insecure bool
|
insecure bool
|
||||||
ociTypes bool
|
ociTypes bool
|
||||||
nameCanonical bool
|
nameCanonical bool
|
||||||
danglingPrefix string
|
danglingPrefix string
|
||||||
layerCompression compression.Type
|
layerCompression compression.Type
|
||||||
forceCompression bool
|
forceCompression bool
|
||||||
compressionLevel *int
|
compressionLevel *int
|
||||||
buildInfoMode buildinfo.ExportMode
|
buildInfoMode buildinfo.ExportMode
|
||||||
meta map[string][]byte
|
meta map[string][]byte
|
||||||
|
propagateNondistLayers bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *imageExporterInstance) Name() string {
|
func (e *imageExporterInstance) Name() string {
|
||||||
|
@ -244,7 +255,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src exporter.Source,
|
||||||
}
|
}
|
||||||
defer done(context.TODO())
|
defer done(context.TODO())
|
||||||
|
|
||||||
desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes, e.compression(), e.buildInfoMode, sessionID)
|
desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes, e.compression(), e.buildInfoMode, e.propagateNondistLayers, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/moby/buildkit/util/bklog"
|
"github.com/moby/buildkit/util/bklog"
|
||||||
"github.com/moby/buildkit/util/buildinfo"
|
"github.com/moby/buildkit/util/buildinfo"
|
||||||
"github.com/moby/buildkit/util/compression"
|
"github.com/moby/buildkit/util/compression"
|
||||||
|
"github.com/moby/buildkit/util/convert"
|
||||||
"github.com/moby/buildkit/util/progress"
|
"github.com/moby/buildkit/util/progress"
|
||||||
"github.com/moby/buildkit/util/system"
|
"github.com/moby/buildkit/util/system"
|
||||||
"github.com/moby/buildkit/util/tracing"
|
"github.com/moby/buildkit/util/tracing"
|
||||||
|
@ -48,7 +49,7 @@ type ImageWriter struct {
|
||||||
opt WriterOpt
|
opt WriterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool, comp compression.Config, buildInfoMode buildinfo.ExportMode, sessionID string) (*ocispecs.Descriptor, error) {
|
func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool, comp compression.Config, buildInfoMode buildinfo.ExportMode, propagateNonDist bool, sessionID string) (*ocispecs.Descriptor, error) {
|
||||||
platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey]
|
platformsBytes, ok := inp.Metadata[exptypes.ExporterPlatformsKey]
|
||||||
|
|
||||||
if len(inp.Refs) > 0 && !ok {
|
if len(inp.Refs) > 0 && !ok {
|
||||||
|
@ -56,6 +57,7 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inp.Refs) == 0 {
|
if len(inp.Refs) == 0 {
|
||||||
|
// TODO: pass through non-dist layers?
|
||||||
remotes, err := ic.exportLayers(ctx, comp, session.NewGroup(sessionID), inp.Ref)
|
remotes, err := ic.exportLayers(ctx, comp, session.NewGroup(sessionID), inp.Ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -66,7 +68,7 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool
|
||||||
buildInfo = inp.Metadata[exptypes.ExporterBuildInfo]
|
buildInfo = inp.Metadata[exptypes.ExporterBuildInfo]
|
||||||
}
|
}
|
||||||
|
|
||||||
mfstDesc, configDesc, err := ic.commitDistributionManifest(ctx, inp.Ref, inp.Metadata[exptypes.ExporterImageConfigKey], &remotes[0], oci, inp.Metadata[exptypes.ExporterInlineCache], buildInfo)
|
mfstDesc, configDesc, err := ic.commitDistributionManifest(ctx, inp.Ref, inp.Metadata[exptypes.ExporterImageConfigKey], &remotes[0], oci, inp.Metadata[exptypes.ExporterInlineCache], buildInfo, propagateNonDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -94,6 +96,7 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool
|
||||||
refs = append(refs, r)
|
refs = append(refs, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Pass through non-distributable layers
|
||||||
remotes, err := ic.exportLayers(ctx, comp, session.NewGroup(sessionID), refs...)
|
remotes, err := ic.exportLayers(ctx, comp, session.NewGroup(sessionID), refs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -133,7 +136,7 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp exporter.Source, oci bool
|
||||||
buildInfo = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p.ID)]
|
buildInfo = inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p.ID)]
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, _, err := ic.commitDistributionManifest(ctx, r, config, &remotes[remotesMap[p.ID]], oci, inlineCache, buildInfo)
|
desc, _, err := ic.commitDistributionManifest(ctx, r, config, &remotes[remotesMap[p.ID]], oci, inlineCache, buildInfo, propagateNonDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -202,7 +205,7 @@ func (ic *ImageWriter) exportLayers(ctx context.Context, comp compression.Config
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, ref cache.ImmutableRef, config []byte, remote *solver.Remote, oci bool, inlineCache []byte, buildInfo []byte) (*ocispecs.Descriptor, *ocispecs.Descriptor, error) {
|
func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, ref cache.ImmutableRef, config []byte, remote *solver.Remote, oci bool, inlineCache []byte, buildInfo []byte, propagateNonDist bool) (*ocispecs.Descriptor, *ocispecs.Descriptor, error) {
|
||||||
if len(config) == 0 {
|
if len(config) == 0 {
|
||||||
var err error
|
var err error
|
||||||
config, err = emptyImageConfig()
|
config, err = emptyImageConfig()
|
||||||
|
@ -278,6 +281,10 @@ func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, ref cache
|
||||||
} else {
|
} else {
|
||||||
desc.Annotations = nil
|
desc.Annotations = nil
|
||||||
}
|
}
|
||||||
|
if !propagateNonDist {
|
||||||
|
desc.MediaType = convert.LayerToDistributable(oci, desc.MediaType)
|
||||||
|
desc.URLs = nil
|
||||||
|
}
|
||||||
mfst.Layers = append(mfst.Layers, desc)
|
mfst.Layers = append(mfst.Layers, desc)
|
||||||
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = desc.Digest.String()
|
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = desc.Digest.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,10 @@ const (
|
||||||
keyForceCompression = "force-compression"
|
keyForceCompression = "force-compression"
|
||||||
keyCompressionLevel = "compression-level"
|
keyCompressionLevel = "compression-level"
|
||||||
keyBuildInfo = "buildinfo"
|
keyBuildInfo = "buildinfo"
|
||||||
|
// propagateNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was
|
||||||
|
// already found to use a non-distributable media type.
|
||||||
|
// When this option is not set, the exporter will change the media type of the layer to a distributable one.
|
||||||
|
propagateNondistLayersKey = "propagate-nondist-layers"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opt struct {
|
type Opt struct {
|
||||||
|
@ -119,6 +123,12 @@ func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i.buildInfoMode = bimode
|
i.buildInfoMode = bimode
|
||||||
|
case propagateNondistLayersKey:
|
||||||
|
b, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "non-bool value specified for %s", k)
|
||||||
|
}
|
||||||
|
i.propagateNonDist = b
|
||||||
default:
|
default:
|
||||||
if i.meta == nil {
|
if i.meta == nil {
|
||||||
i.meta = make(map[string][]byte)
|
i.meta = make(map[string][]byte)
|
||||||
|
@ -147,6 +157,7 @@ type imageExporterInstance struct {
|
||||||
forceCompression bool
|
forceCompression bool
|
||||||
compressionLevel *int
|
compressionLevel *int
|
||||||
buildInfoMode buildinfo.ExportMode
|
buildInfoMode buildinfo.ExportMode
|
||||||
|
propagateNonDist bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *imageExporterInstance) Name() string {
|
func (e *imageExporterInstance) Name() string {
|
||||||
|
@ -185,7 +196,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src exporter.Source,
|
||||||
}
|
}
|
||||||
defer done(context.TODO())
|
defer done(context.TODO())
|
||||||
|
|
||||||
desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes, e.compression(), e.buildInfoMode, sessionID)
|
desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes, e.compression(), e.buildInfoMode, e.propagateNonDist, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,10 +15,18 @@ import (
|
||||||
"github.com/moby/buildkit/session/filesync"
|
"github.com/moby/buildkit/session/filesync"
|
||||||
"github.com/moby/buildkit/snapshot"
|
"github.com/moby/buildkit/snapshot"
|
||||||
"github.com/moby/buildkit/util/progress"
|
"github.com/moby/buildkit/util/progress"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/tonistiigi/fsutil"
|
"github.com/tonistiigi/fsutil"
|
||||||
fstypes "github.com/tonistiigi/fsutil/types"
|
fstypes "github.com/tonistiigi/fsutil/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// propagateNondistLayersKey is an exporter option which can be used to mark a layer as non-distributable if the layer reference was
|
||||||
|
// already found to use a non-distributable media type.
|
||||||
|
// When this option is not set, the exporter will change the media type of the layer to a distributable one.
|
||||||
|
propagateNondistLayersKey = "propagate-nondist-layers"
|
||||||
|
)
|
||||||
|
|
||||||
type Opt struct {
|
type Opt struct {
|
||||||
SessionManager *session.Manager
|
SessionManager *session.Manager
|
||||||
}
|
}
|
||||||
|
@ -34,11 +43,22 @@ func New(opt Opt) (exporter.Exporter, error) {
|
||||||
|
|
||||||
func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
|
func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
|
||||||
li := &localExporterInstance{localExporter: e}
|
li := &localExporterInstance{localExporter: e}
|
||||||
|
|
||||||
|
v, ok := opt[propagateNondistLayersKey]
|
||||||
|
if ok {
|
||||||
|
b, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "non-bool value for %s: %s", propagateNondistLayersKey, v)
|
||||||
|
}
|
||||||
|
li.propagateNonDist = b
|
||||||
|
}
|
||||||
|
|
||||||
return li, nil
|
return li, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type localExporterInstance struct {
|
type localExporterInstance struct {
|
||||||
*localExporter
|
*localExporter
|
||||||
|
propagateNonDist bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *localExporterInstance) Name() string {
|
func (e *localExporterInstance) Name() string {
|
||||||
|
|
|
@ -116,11 +116,11 @@ func (ct Type) IsMediaType(mt string) bool {
|
||||||
|
|
||||||
func FromMediaType(mediaType string) Type {
|
func FromMediaType(mediaType string) Type {
|
||||||
switch toOCILayerType[mediaType] {
|
switch toOCILayerType[mediaType] {
|
||||||
case ocispecs.MediaTypeImageLayer:
|
case ocispecs.MediaTypeImageLayer, ocispecs.MediaTypeImageLayerNonDistributable:
|
||||||
return Uncompressed
|
return Uncompressed
|
||||||
case ocispecs.MediaTypeImageLayerGzip:
|
case ocispecs.MediaTypeImageLayerGzip, ocispecs.MediaTypeImageLayerNonDistributableGzip:
|
||||||
return Gzip
|
return Gzip
|
||||||
case mediaTypeImageLayerZstd:
|
case mediaTypeImageLayerZstd, ocispecs.MediaTypeImageLayerNonDistributableZstd:
|
||||||
return Zstd
|
return Zstd
|
||||||
default:
|
default:
|
||||||
return UnknownCompression
|
return UnknownCompression
|
||||||
|
@ -193,25 +193,30 @@ func detectCompressionType(cr *io.SectionReader) (Type, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var toDockerLayerType = map[string]string{
|
var toDockerLayerType = map[string]string{
|
||||||
ocispecs.MediaTypeImageLayer: images.MediaTypeDockerSchema2Layer,
|
ocispecs.MediaTypeImageLayer: images.MediaTypeDockerSchema2Layer,
|
||||||
images.MediaTypeDockerSchema2Layer: images.MediaTypeDockerSchema2Layer,
|
images.MediaTypeDockerSchema2Layer: images.MediaTypeDockerSchema2Layer,
|
||||||
ocispecs.MediaTypeImageLayerGzip: images.MediaTypeDockerSchema2LayerGzip,
|
ocispecs.MediaTypeImageLayerGzip: images.MediaTypeDockerSchema2LayerGzip,
|
||||||
images.MediaTypeDockerSchema2LayerGzip: images.MediaTypeDockerSchema2LayerGzip,
|
images.MediaTypeDockerSchema2LayerGzip: images.MediaTypeDockerSchema2LayerGzip,
|
||||||
images.MediaTypeDockerSchema2LayerForeign: images.MediaTypeDockerSchema2Layer,
|
images.MediaTypeDockerSchema2LayerForeign: images.MediaTypeDockerSchema2LayerForeign,
|
||||||
images.MediaTypeDockerSchema2LayerForeignGzip: images.MediaTypeDockerSchema2LayerGzip,
|
images.MediaTypeDockerSchema2LayerForeignGzip: images.MediaTypeDockerSchema2LayerForeignGzip,
|
||||||
mediaTypeImageLayerZstd: mediaTypeDockerSchema2LayerZstd,
|
ocispecs.MediaTypeImageLayerNonDistributable: images.MediaTypeDockerSchema2LayerForeign,
|
||||||
mediaTypeDockerSchema2LayerZstd: mediaTypeDockerSchema2LayerZstd,
|
ocispecs.MediaTypeImageLayerNonDistributableGzip: images.MediaTypeDockerSchema2LayerForeignGzip,
|
||||||
|
mediaTypeImageLayerZstd: mediaTypeDockerSchema2LayerZstd,
|
||||||
|
mediaTypeDockerSchema2LayerZstd: mediaTypeDockerSchema2LayerZstd,
|
||||||
}
|
}
|
||||||
|
|
||||||
var toOCILayerType = map[string]string{
|
var toOCILayerType = map[string]string{
|
||||||
ocispecs.MediaTypeImageLayer: ocispecs.MediaTypeImageLayer,
|
ocispecs.MediaTypeImageLayer: ocispecs.MediaTypeImageLayer,
|
||||||
images.MediaTypeDockerSchema2Layer: ocispecs.MediaTypeImageLayer,
|
ocispecs.MediaTypeImageLayerNonDistributable: ocispecs.MediaTypeImageLayerNonDistributable,
|
||||||
ocispecs.MediaTypeImageLayerGzip: ocispecs.MediaTypeImageLayerGzip,
|
ocispecs.MediaTypeImageLayerNonDistributableGzip: ocispecs.MediaTypeImageLayerNonDistributableGzip,
|
||||||
images.MediaTypeDockerSchema2LayerGzip: ocispecs.MediaTypeImageLayerGzip,
|
ocispecs.MediaTypeImageLayerNonDistributableZstd: ocispecs.MediaTypeImageLayerNonDistributableZstd,
|
||||||
images.MediaTypeDockerSchema2LayerForeign: ocispecs.MediaTypeImageLayer,
|
images.MediaTypeDockerSchema2Layer: ocispecs.MediaTypeImageLayer,
|
||||||
images.MediaTypeDockerSchema2LayerForeignGzip: ocispecs.MediaTypeImageLayerGzip,
|
ocispecs.MediaTypeImageLayerGzip: ocispecs.MediaTypeImageLayerGzip,
|
||||||
mediaTypeImageLayerZstd: mediaTypeImageLayerZstd,
|
images.MediaTypeDockerSchema2LayerGzip: ocispecs.MediaTypeImageLayerGzip,
|
||||||
mediaTypeDockerSchema2LayerZstd: mediaTypeImageLayerZstd,
|
images.MediaTypeDockerSchema2LayerForeign: ocispecs.MediaTypeImageLayerNonDistributable,
|
||||||
|
images.MediaTypeDockerSchema2LayerForeignGzip: ocispecs.MediaTypeImageLayerNonDistributableGzip,
|
||||||
|
mediaTypeImageLayerZstd: mediaTypeImageLayerZstd,
|
||||||
|
mediaTypeDockerSchema2LayerZstd: mediaTypeImageLayerZstd,
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertLayerMediaType(mediaType string, oci bool) string {
|
func convertLayerMediaType(mediaType string, oci bool) string {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LayertoDistributable changes the passed in media type to the "distributable" version of the media type.
|
||||||
|
func LayerToDistributable(oci bool, mt string) string {
|
||||||
|
if !images.IsNonDistributable(mt) {
|
||||||
|
// Layer is already a distributable media type (or this is not even a layer).
|
||||||
|
// No conversion needed
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
|
||||||
|
if oci {
|
||||||
|
switch mt {
|
||||||
|
case ocispecs.MediaTypeImageLayerNonDistributable:
|
||||||
|
return ocispecs.MediaTypeImageLayer
|
||||||
|
case ocispecs.MediaTypeImageLayerNonDistributableGzip:
|
||||||
|
return ocispecs.MediaTypeImageLayerGzip
|
||||||
|
case ocispecs.MediaTypeImageLayerNonDistributableZstd:
|
||||||
|
return ocispecs.MediaTypeImageLayerZstd
|
||||||
|
default:
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mt {
|
||||||
|
case images.MediaTypeDockerSchema2LayerForeign:
|
||||||
|
return images.MediaTypeDockerSchema2Layer
|
||||||
|
case images.MediaTypeDockerSchema2LayerForeignGzip:
|
||||||
|
return images.MediaTypeDockerSchema2LayerGzip
|
||||||
|
default:
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
}
|
|
@ -225,7 +225,17 @@ func (p *provider) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (cont
|
||||||
func filterLayerBlobs(metadata map[digest.Digest]ocispecs.Descriptor, mu sync.Locker) images.HandlerFunc {
|
func filterLayerBlobs(metadata map[digest.Digest]ocispecs.Descriptor, mu sync.Locker) images.HandlerFunc {
|
||||||
return func(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
return func(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
||||||
switch desc.MediaType {
|
switch desc.MediaType {
|
||||||
case ocispecs.MediaTypeImageLayer, images.MediaTypeDockerSchema2Layer, ocispecs.MediaTypeImageLayerGzip, images.MediaTypeDockerSchema2LayerGzip, images.MediaTypeDockerSchema2LayerForeign, images.MediaTypeDockerSchema2LayerForeignGzip:
|
case
|
||||||
|
ocispecs.MediaTypeImageLayer,
|
||||||
|
ocispecs.MediaTypeImageLayerNonDistributable,
|
||||||
|
images.MediaTypeDockerSchema2Layer,
|
||||||
|
images.MediaTypeDockerSchema2LayerForeign,
|
||||||
|
ocispecs.MediaTypeImageLayerGzip,
|
||||||
|
images.MediaTypeDockerSchema2LayerGzip,
|
||||||
|
ocispecs.MediaTypeImageLayerNonDistributableGzip,
|
||||||
|
images.MediaTypeDockerSchema2LayerForeignGzip,
|
||||||
|
ocispecs.MediaTypeImageLayerZstd,
|
||||||
|
ocispecs.MediaTypeImageLayerNonDistributableZstd:
|
||||||
return nil, images.ErrSkipDesc
|
return nil, images.ErrSkipDesc
|
||||||
default:
|
default:
|
||||||
if metadata != nil {
|
if metadata != nil {
|
||||||
|
|
Loading…
Reference in New Issue