export: add --skip-non-distributable

The flag skips adding non-distributable blobs such as Windows layers to archive.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2021-01-01 06:29:00 +09:00
parent 9067796ce4
commit e2e2c5737d
No known key found for this signature in database
GPG Key ID: 49524C6F9F638F1A
3 changed files with 54 additions and 7 deletions

View File

@ -44,6 +44,10 @@ When '--all-platforms' is given all images in a manifest list must be available.
Name: "skip-manifest-json", Name: "skip-manifest-json",
Usage: "do not add Docker compatible manifest.json to archive", 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{ cli.StringSliceFlag{
Name: "platform", Name: "platform",
Usage: "Pull content from a specific 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()) exportOpts = append(exportOpts, archive.WithSkipDockerManifest())
} }
if context.Bool("skip-non-distributable") {
exportOpts = append(exportOpts, archive.WithSkipNonDistributableBlobs())
}
client, ctx, cancel, err := commands.NewClient(context) client, ctx, cancel, err := commands.NewClient(context)
if err != nil { if err != nil {
return err return err

View File

@ -39,6 +39,7 @@ type exportOptions struct {
platform platforms.MatchComparer platform platforms.MatchComparer
allPlatforms bool allPlatforms bool
skipDockerManifest bool skipDockerManifest bool
blobRecordOptions blobRecordOptions
} }
// ExportOpt defines options for configuring exported descriptors // 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 { func addNameAnnotation(name string, base map[string]string) map[string]string {
annotations := map[string]string{} annotations := map[string]string{}
for k, v := range base { 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] mt, ok := dManifests[desc.Digest]
if !ok { if !ok {
// TODO(containerd): Skip if already added // 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 { if err != nil {
return err return err
} }
@ -162,7 +182,7 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
d, ok := resolvedIndex[desc.Digest] d, ok := resolvedIndex[desc.Digest]
if !ok { if !ok {
records = append(records, blobRecord(store, desc)) records = append(records, blobRecord(store, desc, &eo.blobRecordOptions))
p, err := content.ReadBlob(ctx, store, desc) p, err := content.ReadBlob(ctx, store, desc)
if err != nil { 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 { if err != nil {
return err return err
} }
@ -248,10 +268,10 @@ func Export(ctx context.Context, store content.Provider, writer io.Writer, opts
return writeTar(ctx, tw, records) 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 var records []tarRecord
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 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{}{} algorithms[desc.Digest.Algorithm().String()] = struct{}{}
return nil, nil return nil, nil
} }
@ -277,7 +297,14 @@ type tarRecord struct {
CopyTo func(context.Context, io.Writer) (int64, error) 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()) path := path.Join("blobs", desc.Digest.Algorithm().String(), desc.Digest.Encoded())
return tarRecord{ return tarRecord{
Header: &tar.Header{ Header: &tar.Header{
@ -438,7 +465,13 @@ func manifestsRecord(ctx context.Context, store content.Provider, manifests map[
}, nil }, 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 { sort.Slice(records, func(i, j int) bool {
return records[i].Header.Name < records[j].Header.Name return records[i].Header.Name < records[j].Header.Name
}) })

View File

@ -102,6 +102,12 @@ func parseMediaTypes(mt string) (string, []string) {
return s[0], ext 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 // IsLayerTypes returns true if the media type is a layer
func IsLayerType(mt string) bool { func IsLayerType(mt string) bool {
if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") { if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") {