diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index ebddff686e..0534d2cf8d 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -30,9 +30,9 @@ type imageBackend interface { } type importExportBackend interface { - LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error + LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error ImportImage(src string, repository string, platform *specs.Platform, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error - ExportImage(names []string, outStream io.Writer) error + ExportImage(ctx context.Context, names []string, outStream io.Writer) error } type registryBackend interface { diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index fc397d4458..84d1a96f71 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -137,7 +137,7 @@ func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r names = r.Form["names"] } - if err := s.backend.ExportImage(names, output); err != nil { + if err := s.backend.ExportImage(ctx, names, output); err != nil { if !output.Flushed() { return err } @@ -156,7 +156,7 @@ func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, output := ioutils.NewWriteFlusher(w) defer output.Close() - if err := s.backend.LoadImage(r.Body, output, quiet); err != nil { + if err := s.backend.LoadImage(ctx, r.Body, output, quiet); err != nil { _, _ = output.Write(streamformatter.FormatError(err)) } return nil diff --git a/daemon/containerd/image_exporter.go b/daemon/containerd/image_exporter.go index 6d7c81d64a..9a02269016 100644 --- a/daemon/containerd/image_exporter.go +++ b/daemon/containerd/image_exporter.go @@ -1,19 +1,73 @@ package containerd -import "io" +import ( + "context" + "io" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/images/archive" + "github.com/containerd/containerd/platforms" + "github.com/docker/distribution/reference" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) // ExportImage exports a list of images to the given output stream. The // exported images are archived into a tar when written to the output // stream. All images with the given tag and all versions containing // the same tag are exported. names is the set of tags to export, and // outStream is the writer which the images are written to. -func (i *ImageService) ExportImage(names []string, outStream io.Writer) error { - panic("not implemented") +// +// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910 +func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error { + opts := []archive.ExportOpt{ + archive.WithPlatform(platforms.Ordered(platforms.DefaultSpec())), + archive.WithSkipNonDistributableBlobs(), + } + is := i.client.ImageService() + for _, imageRef := range names { + named, err := reference.ParseDockerRef(imageRef) + if err != nil { + return err + } + opts = append(opts, archive.WithImage(is, named.String())) + } + return i.client.Export(ctx, outStream, opts...) } // LoadImage uploads a set of images into the repository. This is the // complement of ExportImage. The input stream is an uncompressed tar // ball containing images and metadata. -func (i *ImageService) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error { - panic("not implemented") +// +// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910 +func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error { + platform := platforms.DefaultStrict() + imgs, err := i.client.Import(ctx, inTar, containerd.WithImportPlatform(platform)) + + if err != nil { + // TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405 + logrus.WithError(err).Warn("failed to import image to containerd") + return errors.Wrap(err, "failed to import image") + } + + for _, img := range imgs { + platformImg := containerd.NewImageWithPlatform(i.client, img, platform) + + unpacked, err := platformImg.IsUnpacked(ctx, containerd.DefaultSnapshotter) + if err != nil { + // TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405 + logrus.WithError(err).WithField("image", img.Name).Debug("failed to check if image is unpacked") + continue + } + + if !unpacked { + err := platformImg.Unpack(ctx, containerd.DefaultSnapshotter) + if err != nil { + // TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405 + logrus.WithError(err).WithField("image", img.Name).Warn("failed to unpack image") + return errors.Wrap(err, "failed to unpack image") + } + } + } + return nil } diff --git a/daemon/image_service.go b/daemon/image_service.go index 2fe5eddc38..0db2afda3b 100644 --- a/daemon/image_service.go +++ b/daemon/image_service.go @@ -29,8 +29,8 @@ type ImageService interface { PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error CreateImage(config []byte, parent string) (builder.Image, error) ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) - ExportImage(names []string, outStream io.Writer) error - LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error + ExportImage(ctx context.Context, names []string, outStream io.Writer) error + LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) LogImageEvent(imageID, refName, action string) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) diff --git a/daemon/images/image_exporter.go b/daemon/images/image_exporter.go index 037a694b45..2ab4af1a83 100644 --- a/daemon/images/image_exporter.go +++ b/daemon/images/image_exporter.go @@ -1,6 +1,7 @@ package images // import "github.com/docker/docker/daemon/images" import ( + "context" "io" "github.com/docker/docker/image/tarexport" @@ -11,7 +12,7 @@ import ( // stream. All images with the given tag and all versions containing // the same tag are exported. names is the set of tags to export, and // outStream is the writer which the images are written to. -func (i *ImageService) ExportImage(names []string, outStream io.Writer) error { +func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error { imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStore, i.referenceStore, i) return imageExporter.Save(names, outStream) } @@ -19,7 +20,7 @@ func (i *ImageService) ExportImage(names []string, outStream io.Writer) error { // LoadImage uploads a set of images into the repository. This is the // complement of ExportImage. The input stream is an uncompressed tar // ball containing images and metadata. -func (i *ImageService) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error { +func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error { imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStore, i.referenceStore, i) return imageExporter.Load(inTar, outStream, quiet) }