diff --git a/cmd/ctr/commands/images/export.go b/cmd/ctr/commands/images/export.go index d62a55181..629f49330 100644 --- a/cmd/ctr/commands/images/export.go +++ b/cmd/ctr/commands/images/export.go @@ -21,7 +21,7 @@ import ( "os" "github.com/containerd/containerd/cmd/ctr/commands" - oci "github.com/containerd/containerd/images/oci" + "github.com/containerd/containerd/images/oci" "github.com/containerd/containerd/reference" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -52,6 +52,10 @@ Currently, only OCI format is supported. Usage: "media type of manifest digest", Value: ocispec.MediaTypeImageManifest, }, + cli.BoolFlag{ + Name: "all-platforms", + Usage: "exports content from all platforms", + }, }, Action: func(context *cli.Context) error { var ( @@ -101,7 +105,14 @@ Currently, only OCI format is supported. return nil } } - r, err := client.Export(ctx, &oci.V1Exporter{}, desc) + + var ( + exportOpts []oci.V1ExporterOpt + ) + + exportOpts = append(exportOpts, oci.WithAllPlatforms(context.Bool("all-platforms"))) + + r, err := client.Export(ctx, desc, exportOpts...) if err != nil { return err } diff --git a/export.go b/export.go index bfc25316c..f5552231e 100644 --- a/export.go +++ b/export.go @@ -20,36 +20,23 @@ import ( "context" "io" - "github.com/containerd/containerd/images" + "github.com/containerd/containerd/images/oci" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) -type exportOpts struct { -} - -// ExportOpt allows the caller to specify export-specific options -type ExportOpt func(c *exportOpts) error - -func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) { - var eopts exportOpts - for _, o := range opts { - if err := o(&eopts); err != nil { - return eopts, err - } - } - return eopts, nil -} - // Export exports an image to a Tar stream. // OCI format is used by default. // It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. // TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. -func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) { - _, err := resolveExportOpt(opts...) // unused now +func (c *Client) Export(ctx context.Context, desc ocispec.Descriptor, opts ...oci.V1ExporterOpt) (io.ReadCloser, error) { + + exporter, err := oci.ResolveV1ExportOpt(opts...) if err != nil { return nil, err } + pr, pw := io.Pipe() go func() { pw.CloseWithError(errors.Wrap(exporter.Export(ctx, c.ContentStore(), desc, pw), "export failed")) diff --git a/export_test.go b/export_test.go index d947c1830..9e200dc79 100644 --- a/export_test.go +++ b/export_test.go @@ -21,8 +21,6 @@ import ( "io" "runtime" "testing" - - "github.com/containerd/containerd/images/oci" ) // TestOCIExport exports testImage as a tar stream @@ -44,7 +42,7 @@ func TestOCIExport(t *testing.T) { if err != nil { t.Fatal(err) } - exportedStream, err := client.Export(ctx, &oci.V1Exporter{}, pulled.Target) + exportedStream, err := client.Export(ctx, pulled.Target) if err != nil { t.Fatal(err) } diff --git a/images/oci/exporter.go b/images/oci/exporter.go index bf5751e6a..50b42c56e 100644 --- a/images/oci/exporter.go +++ b/images/oci/exporter.go @@ -25,6 +25,7 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" ocispecs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -37,6 +38,36 @@ import ( // e.g. application/vnd.docker.image.rootfs.diff.tar.gzip // -> application/vnd.oci.image.layer.v1.tar+gzip type V1Exporter struct { + AllPlatforms bool +} + +// V1ExporterOpt allows to set additional options to a newly V1Exporter +type V1ExporterOpt func(c *V1Exporter) error + +// DefaultV1Exporter return a default V1Exporter pointer +func DefaultV1Exporter() *V1Exporter { + return &V1Exporter{ + AllPlatforms: false, + } +} + +// ResolveV1ExportOpt return a new V1Exporter with V1ExporterOpt +func ResolveV1ExportOpt(opts ...V1ExporterOpt) (*V1Exporter, error) { + exporter := DefaultV1Exporter() + for _, o := range opts { + if err := o(exporter); err != nil { + return exporter, err + } + } + return exporter, nil +} + +// WithAllPlatforms set V1Exporter`s AllPlatforms option +func WithAllPlatforms(allPlatforms bool) V1ExporterOpt { + return func(c *V1Exporter) error { + c.AllPlatforms = allPlatforms + return nil + } } // Export implements Exporter. @@ -56,8 +87,19 @@ func (oe *V1Exporter) Export(ctx context.Context, store content.Provider, desc o return nil, nil } + childrenHandler := images.ChildrenHandler(store) + + if !oe.AllPlatforms { + // get local default platform to fetch image manifest + p, err := platforms.Parse(platforms.DefaultString()) + if err != nil { + return errors.Wrapf(err, "invalid platform %s", platforms.DefaultString()) + } + childrenHandler = images.FilterPlatforms(childrenHandler, platforms.Any(p)) + } + handlers := images.Handlers( - images.ChildrenHandler(store), + childrenHandler, images.HandlerFunc(exportHandler), ) diff --git a/import_test.go b/import_test.go index 0afd75521..c6cea6d54 100644 --- a/import_test.go +++ b/import_test.go @@ -29,7 +29,6 @@ import ( "github.com/containerd/containerd/archive/tartest" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/archive" - "github.com/containerd/containerd/images/oci" digest "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -56,7 +55,7 @@ func TestOCIExportAndImport(t *testing.T) { t.Fatal(err) } - exported, err := client.Export(ctx, &oci.V1Exporter{}, pulled.Target) + exported, err := client.Export(ctx, pulled.Target) if err != nil { t.Fatal(err) }