Files
moby/daemon/containerd/image_exporter.go
Sebastiaan van Stijn 17315a20ee vendor: github.com/containerd/containerd v1.7.18
Update to containerd 1.7.18, which now migrated to the errdefs module. The
existing errdefs package is now an alias for the module, and should no longer
be used directly.

This patch:

- updates the containerd dependency: https://github.com/containerd/containerd/compare/v1.7.17...v1.7.18
- replaces uses of the old package in favor of the new module
- adds a linter check to prevent accidental re-introduction of the old package
- adds a linter check to prevent using the "log" package, which was also
  migrated to a separate module.

There are still some uses of the old package in (indirect) dependencies,
which should go away over time.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 86f7762d48)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-05 11:16:57 +02:00

320 lines
9.8 KiB
Go

package containerd
import (
"context"
"fmt"
"io"
"strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
containerdimages "github.com/containerd/containerd/images"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/platforms"
cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/images"
"github.com/docker/docker/errdefs"
dockerarchive "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/streamformatter"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func (i *ImageService) PerformWithBaseFS(ctx context.Context, c *container.Container, fn func(root string) error) error {
snapshotter := i.client.SnapshotService(c.Driver)
mounts, err := snapshotter.Mounts(ctx, c.ID)
if err != nil {
return err
}
path, err := i.refCountMounter.Mount(mounts, c.ID)
if err != nil {
return err
}
defer i.refCountMounter.Unmount(path)
return fn(path)
}
// 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.
//
// 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 {
platform := matchAllWithPreference(platforms.Default())
opts := []archive.ExportOpt{
archive.WithSkipNonDistributableBlobs(),
// This makes the exported archive also include `manifest.json`
// when the image is a manifest list. It is needed for backwards
// compatibility with Docker image format.
// The containerd will choose only one manifest for the `manifest.json`.
// Our preference is to have it point to the default platform.
// Example:
// Daemon is running on linux/arm64
// When we export linux/amd64 and linux/arm64, manifest.json will point to linux/arm64.
// When we export linux/amd64 only, manifest.json will point to linux/amd64.
// Note: This is only applicable if importing this archive into non-containerd Docker.
// Importing the same archive into containerd, will not restrict the platforms.
archive.WithPlatform(platform),
archive.WithSkipMissing(i.content),
}
leasesManager := i.client.LeasesService()
lease, err := leasesManager.Create(ctx, leases.WithRandomID())
if err != nil {
return errdefs.System(err)
}
defer func() {
if err := leasesManager.Delete(ctx, lease); err != nil {
log.G(ctx).WithError(err).Warn("cleaning up lease")
}
}()
addLease := func(ctx context.Context, target ocispec.Descriptor) error {
return leaseContent(ctx, i.content, leasesManager, lease, target)
}
exportImage := func(ctx context.Context, target ocispec.Descriptor, ref reference.Named) error {
if err := addLease(ctx, target); err != nil {
return err
}
if ref != nil {
opts = append(opts, archive.WithManifest(target, ref.String()))
log.G(ctx).WithFields(log.Fields{
"target": target,
"name": ref,
}).Debug("export image")
} else {
orgTarget := target
target.Annotations = make(map[string]string)
for k, v := range orgTarget.Annotations {
switch k {
case containerdimages.AnnotationImageName, ocispec.AnnotationRefName:
// Strip image name/tag annotations from the descriptor.
// Otherwise containerd will use it as name.
default:
target.Annotations[k] = v
}
}
opts = append(opts, archive.WithManifest(target))
log.G(ctx).WithFields(log.Fields{
"target": target,
}).Debug("export image without name")
}
i.LogImageEvent(target.Digest.String(), target.Digest.String(), events.ActionSave)
return nil
}
exportRepository := func(ctx context.Context, ref reference.Named) error {
imgs, err := i.getAllImagesWithRepository(ctx, ref)
if err != nil {
return errdefs.System(fmt.Errorf("failed to list all images from repository %s: %w", ref.Name(), err))
}
if len(imgs) == 0 {
return images.ErrImageDoesNotExist{Ref: ref}
}
for _, img := range imgs {
ref, err := reference.ParseNamed(img.Name)
if err != nil {
log.G(ctx).WithFields(log.Fields{
"image": img.Name,
"error": err,
}).Warn("couldn't parse image name as a valid named reference")
continue
}
if err := exportImage(ctx, img.Target, ref); err != nil {
return err
}
}
return nil
}
for _, name := range names {
target, resolveErr := i.resolveDescriptor(ctx, name)
// Check if the requested name is a truncated digest of the resolved descriptor.
// If yes, that means that the user specified a specific image ID so
// it's not referencing a repository.
specificDigestResolved := false
if resolveErr == nil {
nameWithoutDigestAlgorithm := strings.TrimPrefix(name, target.Digest.Algorithm().String()+":")
specificDigestResolved = strings.HasPrefix(target.Digest.Encoded(), nameWithoutDigestAlgorithm)
}
log.G(ctx).WithFields(log.Fields{
"name": name,
"resolveErr": resolveErr,
"specificDigestResolved": specificDigestResolved,
}).Debug("export requested")
ref, refErr := reference.ParseNormalizedNamed(name)
if refErr == nil {
if _, ok := ref.(reference.Digested); ok {
specificDigestResolved = true
}
}
if resolveErr != nil || !specificDigestResolved {
// Name didn't resolve to anything, or name wasn't explicitly referencing a digest
if refErr == nil && reference.IsNameOnly(ref) {
// Reference is valid, but doesn't include a specific tag.
// Export all images with the same repository.
if err := exportRepository(ctx, ref); err != nil {
return err
}
continue
}
}
if resolveErr != nil {
return resolveErr
}
if refErr != nil {
return refErr
}
// If user exports a specific digest, it shouldn't have a tag.
if specificDigestResolved {
ref = nil
}
if err := exportImage(ctx, target, ref); err != nil {
return err
}
}
return i.client.Export(ctx, outStream, opts...)
}
// leaseContent will add a resource to the lease for each child of the descriptor making sure that it and
// its children won't be deleted while the lease exists
func leaseContent(ctx context.Context, store content.Store, leasesManager leases.Manager, lease leases.Lease, desc ocispec.Descriptor) error {
return containerdimages.Walk(ctx, containerdimages.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
_, err := store.Info(ctx, desc.Digest)
if err != nil {
if errors.Is(err, cerrdefs.ErrNotFound) {
return nil, nil
}
return nil, errdefs.System(err)
}
r := leases.Resource{
ID: desc.Digest.String(),
Type: "content",
}
if err := leasesManager.AddResource(ctx, lease, r); err != nil {
return nil, errdefs.System(err)
}
return containerdimages.Children(ctx, store, desc)
}), desc)
}
// 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(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
decompressed, err := dockerarchive.DecompressStream(inTar)
if err != nil {
return errors.Wrap(err, "failed to decompress input tar archive")
}
defer decompressed.Close()
opts := []containerd.ImportOpt{
// TODO(vvoland): Allow user to pass platform
containerd.WithImportPlatform(platforms.All),
containerd.WithSkipMissing(),
// Create an additional image with dangling name for imported images...
containerd.WithDigestRef(danglingImageName),
// ... but only if they don't have a name or it's invalid.
containerd.WithSkipDigestRef(func(nameFromArchive string) bool {
if nameFromArchive == "" {
return false
}
_, err := reference.ParseNormalizedNamed(nameFromArchive)
return err == nil
}),
}
imgs, err := i.client.Import(ctx, decompressed, opts...)
if err != nil {
log.G(ctx).WithError(err).Debug("failed to import image to containerd")
return errdefs.System(err)
}
progress := streamformatter.NewStdoutWriter(outStream)
for _, img := range imgs {
name := img.Name
loadedMsg := "Loaded image"
if isDanglingImage(img) {
name = img.Target.Digest.String()
loadedMsg = "Loaded image ID"
} else if named, err := reference.ParseNormalizedNamed(img.Name); err == nil {
name = reference.FamiliarString(reference.TagNameOnly(named))
}
err = i.walkImageManifests(ctx, img, func(platformImg *ImageManifest) error {
logger := log.G(ctx).WithFields(log.Fields{
"image": name,
"manifest": platformImg.Target().Digest,
})
if isPseudo, err := platformImg.IsPseudoImage(ctx); isPseudo || err != nil {
if err != nil {
logger.WithError(err).Warn("failed to read manifest")
} else {
logger.Debug("don't unpack non-image manifest")
}
return nil
}
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
if err != nil {
logger.WithError(err).Warn("failed to check if image is unpacked")
return nil
}
if !unpacked {
err = platformImg.Unpack(ctx, i.snapshotter)
if err != nil {
return errdefs.System(err)
}
}
logger.WithField("alreadyUnpacked", unpacked).WithError(err).Debug("unpack")
return nil
})
if err != nil {
return errors.Wrap(err, "failed to unpack loaded image")
}
fmt.Fprintf(progress, "%s: %s\n", loadedMsg, name)
i.LogImageEvent(img.Target.Digest.String(), img.Target.Digest.String(), events.ActionLoad)
}
return nil
}