Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 83 additions & 21 deletions daemon/containerd/image_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,17 @@ import (
"io"

"github.com/containerd/containerd"
cerrdefs "github.com/containerd/containerd/errdefs"
containerdimages "github.com/containerd/containerd/images"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/images/converter"
"github.com/containerd/containerd/platforms"
"github.com/docker/distribution/reference"
"github.com/opencontainers/image-spec/specs-go/v1"
"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(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.
Expand Down Expand Up @@ -64,3 +47,82 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
}
return nil
}

// 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(ctx context.Context, names []string, outStream io.Writer) error {
opts := []archive.ExportOpt{
archive.WithSkipNonDistributableBlobs(),
}

for _, imageRef := range names {
var err error
opts, err = i.appendImageForExport(ctx, opts, imageRef)
if err != nil {
return err
}
}

return i.client.Export(ctx, outStream, opts...)
}

func (i *ImageService) appendImageForExport(ctx context.Context, opts []archive.ExportOpt, name string) ([]archive.ExportOpt, error) {
ref, err := reference.ParseDockerRef(name)
if err != nil {
return opts, err
}

is := i.client.ImageService()

img, err := is.Get(ctx, ref.String())
if err != nil {
return opts, err
}

store := i.client.ContentStore()

if containerdimages.IsIndexType(img.Target.MediaType) {
children, err := containerdimages.Children(ctx, store, img.Target)
if err != nil {
return opts, err
}

// Check which platform manifests we have blobs for.
missingPlatforms := []v1.Platform{}
presentPlatforms := []v1.Platform{}
for _, child := range children {
if containerdimages.IsManifestType(child.MediaType) {
_, err := store.ReaderAt(ctx, child)
if cerrdefs.IsNotFound(err) {
missingPlatforms = append(missingPlatforms, *child.Platform)
logrus.WithField("digest", child.Digest.String()).Debug("missing blob, not exporting")
continue
} else if err != nil {
return opts, err
}
presentPlatforms = append(presentPlatforms, *child.Platform)
}
}

// If we have all the manifests, just export the original index.
if len(missingPlatforms) == 0 {
return append(opts, archive.WithImage(is, img.Name)), nil
}

// Create a new manifest which contains only the manifests we have in store.
srcRef := ref.String()
targetRef := srcRef + "-tmp-export"
newImg, err := converter.Convert(ctx, i.client, targetRef, srcRef,
converter.WithPlatform(platforms.Any(presentPlatforms...)))
if err != nil {
return opts, err
}
defer i.client.ImageService().Delete(ctx, newImg.Name, containerdimages.SynchronousDelete())
return append(opts, archive.WithManifest(newImg.Target, srcRef)), nil
}

return append(opts, archive.WithImage(is, img.Name)), nil
}