From e2e2c5737d16f6d695468358e55fb9f9b6cbce6c Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Fri, 1 Jan 2021 06:29:00 +0900 Subject: [PATCH] export: add --skip-non-distributable The flag skips adding non-distributable blobs such as Windows layers to archive. Signed-off-by: Akihiro Suda --- cmd/ctr/commands/images/export.go | 8 ++++++ images/archive/exporter.go | 47 ++++++++++++++++++++++++++----- images/mediatypes.go | 6 ++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/cmd/ctr/commands/images/export.go b/cmd/ctr/commands/images/export.go index fc06a53ed..67f8361eb 100644 --- a/cmd/ctr/commands/images/export.go +++ b/cmd/ctr/commands/images/export.go @@ -44,6 +44,10 @@ When '--all-platforms' is given all images in a manifest list must be available. Name: "skip-manifest-json", Usage: "do not add Docker compatible manifest.json to archive", }, + cli.BoolFlag{ + Name: "skip-non-distributable", + Usage: "do not add non-distributable blobs such as Windows layers to archive", + }, cli.StringSliceFlag{ Name: "platform", 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. exportOpts = append(exportOpts, archive.WithSkipDockerManifest()) } + if context.Bool("skip-non-distributable") { + exportOpts = append(exportOpts, archive.WithSkipNonDistributableBlobs()) + } + client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err diff --git a/images/archive/exporter.go b/images/archive/exporter.go index c9d3f6ec7..7801b2552 100644 --- a/images/archive/exporter.go +++ b/images/archive/exporter.go @@ -39,6 +39,7 @@ type exportOptions struct { platform platforms.MatchComparer allPlatforms bool skipDockerManifest bool + blobRecordOptions blobRecordOptions } // ExportOpt defines options for configuring exported descriptors @@ -108,6 +109,25 @@ func WithManifest(manifest ocispec.Descriptor, names ...string) ExportOpt { } } +// BlobFilter returns false if the blob should not be included in the archive. +type BlobFilter func(ocispec.Descriptor) bool + +// WithBlobFilter specifies BlobFilter. +func WithBlobFilter(f BlobFilter) ExportOpt { + return func(ctx context.Context, o *exportOptions) error { + o.blobRecordOptions.blobFilter = f + return nil + } +} + +// WithSkipNonDistributableBlobs excludes non-distributable blobs such as Windows base layers. +func WithSkipNonDistributableBlobs() ExportOpt { + f := func(desc ocispec.Descriptor) bool { + return !images.IsNonDistributable(desc.MediaType) + } + return WithBlobFilter(f) +} + func addNameAnnotation(name string, base map[string]string) map[string]string { annotations := map[string]string{} for k, v := range base { @@ -143,7 +163,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts mt, ok := dManifests[desc.Digest] if !ok { // TODO(containerd): Skip if already added - r, err := getRecords(ctx, store, desc, algorithms) + r, err := getRecords(ctx, store, desc, algorithms, &eo.blobRecordOptions) if err != nil { return err } @@ -162,7 +182,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: d, ok := resolvedIndex[desc.Digest] if !ok { - records = append(records, blobRecord(store, desc)) + records = append(records, blobRecord(store, desc, &eo.blobRecordOptions)) p, err := content.ReadBlob(ctx, store, desc) if err != nil { @@ -184,7 +204,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts } } - r, err := getRecords(ctx, store, m, algorithms) + r, err := getRecords(ctx, store, m, algorithms, &eo.blobRecordOptions) if err != nil { return err } @@ -248,10 +268,10 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts return writeTar(ctx, tw, records) } -func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}) ([]tarRecord, error) { +func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}, brOpts *blobRecordOptions) ([]tarRecord, error) { var records []tarRecord exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - records = append(records, blobRecord(store, desc)) + records = append(records, blobRecord(store, desc, brOpts)) algorithms[desc.Digest.Algorithm().String()] = struct{}{} return nil, nil } @@ -277,7 +297,14 @@ type tarRecord struct { CopyTo func(context.Context, io.Writer) (int64, error) } -func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord { +type blobRecordOptions struct { + blobFilter BlobFilter +} + +func blobRecord(cs content.Provider, desc ocispec.Descriptor, opts *blobRecordOptions) tarRecord { + if opts != nil && opts.blobFilter != nil && !opts.blobFilter(desc) { + return tarRecord{} + } path := path.Join("blobs", desc.Digest.Algorithm().String(), desc.Digest.Encoded()) return tarRecord{ Header: &tar.Header{ @@ -438,7 +465,13 @@ func manifestsRecord(ctx context.Context, store content.Provider, manifests map[ }, nil } -func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error { +func writeTar(ctx context.Context, tw *tar.Writer, recordsWithEmpty []tarRecord) error { + var records []tarRecord + for _, r := range recordsWithEmpty { + if r.Header != nil { + records = append(records, r) + } + } sort.Slice(records, func(i, j int) bool { return records[i].Header.Name < records[j].Header.Name }) diff --git a/images/mediatypes.go b/images/mediatypes.go index b548d6314..5402fa407 100644 --- a/images/mediatypes.go +++ b/images/mediatypes.go @@ -102,6 +102,12 @@ func parseMediaTypes(mt string) (string, []string) { return s[0], ext } +// IsNonDistributable returns true if the media type is non-distributable. +func IsNonDistributable(mt string) bool { + return strings.HasPrefix(mt, "application/vnd.oci.image.layer.nondistributable.") || + strings.HasPrefix(mt, "application/vnd.docker.image.rootfs.foreign.") +} + // IsLayerTypes returns true if the media type is a layer func IsLayerType(mt string) bool { if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") {