Skip to content

Commit e2e2c57

Browse files
committed
export: add --skip-non-distributable
The flag skips adding non-distributable blobs such as Windows layers to archive. Signed-off-by: Akihiro Suda <[email protected]>
1 parent 9067796 commit e2e2c57

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

cmd/ctr/commands/images/export.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ When '--all-platforms' is given all images in a manifest list must be available.
4444
Name: "skip-manifest-json",
4545
Usage: "do not add Docker compatible manifest.json to archive",
4646
},
47+
cli.BoolFlag{
48+
Name: "skip-non-distributable",
49+
Usage: "do not add non-distributable blobs such as Windows layers to archive",
50+
},
4751
cli.StringSliceFlag{
4852
Name: "platform",
4953
Usage: "Pull content from a specific platform",
@@ -86,6 +90,10 @@ When '--all-platforms' is given all images in a manifest list must be available.
8690
exportOpts = append(exportOpts, archive.WithSkipDockerManifest())
8791
}
8892

93+
if context.Bool("skip-non-distributable") {
94+
exportOpts = append(exportOpts, archive.WithSkipNonDistributableBlobs())
95+
}
96+
8997
client, ctx, cancel, err := commands.NewClient(context)
9098
if err != nil {
9199
return err

images/archive/exporter.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type exportOptions struct {
3939
platform platforms.MatchComparer
4040
allPlatforms bool
4141
skipDockerManifest bool
42+
blobRecordOptions blobRecordOptions
4243
}
4344

4445
// ExportOpt defines options for configuring exported descriptors
@@ -108,6 +109,25 @@ func WithManifest(manifest ocispec.Descriptor, names ...string) ExportOpt {
108109
}
109110
}
110111

112+
// BlobFilter returns false if the blob should not be included in the archive.
113+
type BlobFilter func(ocispec.Descriptor) bool
114+
115+
// WithBlobFilter specifies BlobFilter.
116+
func WithBlobFilter(f BlobFilter) ExportOpt {
117+
return func(ctx context.Context, o *exportOptions) error {
118+
o.blobRecordOptions.blobFilter = f
119+
return nil
120+
}
121+
}
122+
123+
// WithSkipNonDistributableBlobs excludes non-distributable blobs such as Windows base layers.
124+
func WithSkipNonDistributableBlobs() ExportOpt {
125+
f := func(desc ocispec.Descriptor) bool {
126+
return !images.IsNonDistributable(desc.MediaType)
127+
}
128+
return WithBlobFilter(f)
129+
}
130+
111131
func addNameAnnotation(name string, base map[string]string) map[string]string {
112132
annotations := map[string]string{}
113133
for k, v := range base {
@@ -143,7 +163,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
143163
mt, ok := dManifests[desc.Digest]
144164
if !ok {
145165
// TODO(containerd): Skip if already added
146-
r, err := getRecords(ctx, store, desc, algorithms)
166+
r, err := getRecords(ctx, store, desc, algorithms, &eo.blobRecordOptions)
147167
if err != nil {
148168
return err
149169
}
@@ -162,7 +182,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
162182
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
163183
d, ok := resolvedIndex[desc.Digest]
164184
if !ok {
165-
records = append(records, blobRecord(store, desc))
185+
records = append(records, blobRecord(store, desc, &eo.blobRecordOptions))
166186

167187
p, err := content.ReadBlob(ctx, store, desc)
168188
if err != nil {
@@ -184,7 +204,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
184204
}
185205
}
186206

187-
r, err := getRecords(ctx, store, m, algorithms)
207+
r, err := getRecords(ctx, store, m, algorithms, &eo.blobRecordOptions)
188208
if err != nil {
189209
return err
190210
}
@@ -248,10 +268,10 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
248268
return writeTar(ctx, tw, records)
249269
}
250270

251-
func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}) ([]tarRecord, error) {
271+
func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}, brOpts *blobRecordOptions) ([]tarRecord, error) {
252272
var records []tarRecord
253273
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
254-
records = append(records, blobRecord(store, desc))
274+
records = append(records, blobRecord(store, desc, brOpts))
255275
algorithms[desc.Digest.Algorithm().String()] = struct{}{}
256276
return nil, nil
257277
}
@@ -277,7 +297,14 @@ type tarRecord struct {
277297
CopyTo func(context.Context, io.Writer) (int64, error)
278298
}
279299

280-
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
300+
type blobRecordOptions struct {
301+
blobFilter BlobFilter
302+
}
303+
304+
func blobRecord(cs content.Provider, desc ocispec.Descriptor, opts *blobRecordOptions) tarRecord {
305+
if opts != nil && opts.blobFilter != nil && !opts.blobFilter(desc) {
306+
return tarRecord{}
307+
}
281308
path := path.Join("blobs", desc.Digest.Algorithm().String(), desc.Digest.Encoded())
282309
return tarRecord{
283310
Header: &tar.Header{
@@ -438,7 +465,13 @@ func manifestsRecord(ctx context.Context, store content.Provider, manifests map[
438465
}, nil
439466
}
440467

441-
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
468+
func writeTar(ctx context.Context, tw *tar.Writer, recordsWithEmpty []tarRecord) error {
469+
var records []tarRecord
470+
for _, r := range recordsWithEmpty {
471+
if r.Header != nil {
472+
records = append(records, r)
473+
}
474+
}
442475
sort.Slice(records, func(i, j int) bool {
443476
return records[i].Header.Name < records[j].Header.Name
444477
})

images/mediatypes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ func parseMediaTypes(mt string) (string, []string) {
102102
return s[0], ext
103103
}
104104

105+
// IsNonDistributable returns true if the media type is non-distributable.
106+
func IsNonDistributable(mt string) bool {
107+
return strings.HasPrefix(mt, "application/vnd.oci.image.layer.nondistributable.") ||
108+
strings.HasPrefix(mt, "application/vnd.docker.image.rootfs.foreign.")
109+
}
110+
105111
// IsLayerTypes returns true if the media type is a layer
106112
func IsLayerType(mt string) bool {
107113
if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") {

0 commit comments

Comments
 (0)