daemon/c8d: Implement TagImageWithReference
Implements image tagging under containerd image store. If an image with this tag already exists, and there's no other image with the same target, change its name. The name will have a special format `moby-dangling@<digest>` which isn't a valid canonical reference and doesn't resolve to any repository. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
parent
1ca85e835c
commit
eaa56afda9
2 changed files with 125 additions and 8 deletions
|
@ -3,19 +3,75 @@ package containerd
|
|||
import (
|
||||
"context"
|
||||
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TagImage creates the tag specified by newTag, pointing to the image named
|
||||
// imageName (alternatively, imageName can also be an image ID).
|
||||
func (i *ImageService) TagImage(ctx context.Context, imageName, repository, tag string) (string, error) {
|
||||
return "", errdefs.NotImplemented(errors.New("not implemented"))
|
||||
}
|
||||
// TagImage creates an image named as newTag and targeting the given descriptor id.
|
||||
func (i *ImageService) TagImage(ctx context.Context, imageID image.ID, newTag reference.Named) error {
|
||||
target, err := i.resolveDescriptor(ctx, imageID.String())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to resolve image id %q to a descriptor", imageID.String())
|
||||
}
|
||||
|
||||
// TagImageWithReference adds the given reference to the image ID provided.
|
||||
func (i *ImageService) TagImageWithReference(ctx context.Context, imageID image.ID, newTag reference.Named) error {
|
||||
return errdefs.NotImplemented(errors.New("not implemented"))
|
||||
newImg := containerdimages.Image{
|
||||
Name: newTag.String(),
|
||||
Target: target,
|
||||
}
|
||||
|
||||
is := i.client.ImageService()
|
||||
_, createErr := is.Create(ctx, newImg)
|
||||
if createErr != nil {
|
||||
if !cerrdefs.IsAlreadyExists(err) {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to create image with name %s and target %s", newImg.Name, newImg.Target.Digest.String()))
|
||||
}
|
||||
|
||||
replacedImg, err := is.Get(ctx, newImg.Name)
|
||||
if err != nil {
|
||||
return errdefs.Unknown(errors.Wrapf(err, "creating image %s failed because it already exists, but accessing it also failed", newImg.Name))
|
||||
}
|
||||
|
||||
// Check if image we would replace already resolves to the same target.
|
||||
// No need to do anything.
|
||||
if replacedImg.Target.Digest == target.Digest {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If there already exists an image with this tag, delete it
|
||||
if err := i.softImageDelete(ctx, replacedImg); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete previous image %s", replacedImg.Name)
|
||||
}
|
||||
|
||||
if _, err = is.Create(context.Background(), newImg); err != nil {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to create an image %s with target %s after deleting the existing one",
|
||||
newImg.Name, imageID.String()))
|
||||
}
|
||||
}
|
||||
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"imageID": imageID.String(),
|
||||
"tag": newTag.String(),
|
||||
})
|
||||
logger.Info("image created")
|
||||
|
||||
// The tag succeeded, check if the source image is dangling
|
||||
sourceDanglingImg, err := is.Get(context.Background(), danglingImageName(target.Digest))
|
||||
if err != nil {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
logger.WithError(err).Warn("unexpected error when checking if source image is dangling")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the source dangling image, as it's no longer dangling.
|
||||
if err := is.Delete(context.Background(), sourceDanglingImg.Name); err != nil {
|
||||
logger.WithError(err).Warn("unexpected error when deleting dangling image")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
61
daemon/containerd/soft_delete.go
Normal file
61
daemon/containerd/soft_delete.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// softImageDelete deletes the image, making sure that there are other images
|
||||
// that reference the content of the deleted image.
|
||||
// If no other image exists, a dangling one is created.
|
||||
func (i *ImageService) softImageDelete(ctx context.Context, img containerdimages.Image) error {
|
||||
is := i.client.ImageService()
|
||||
|
||||
// If the image already exists, persist it as dangling image
|
||||
// but only if no other image has the same target.
|
||||
digest := img.Target.Digest.String()
|
||||
imgs, err := is.List(ctx, "target.digest=="+digest)
|
||||
if err != nil {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to check if there are images targeting digest %s", digest))
|
||||
}
|
||||
|
||||
// From this point explicitly ignore the passed context
|
||||
// and don't allow to interrupt operation in the middle.
|
||||
|
||||
// Create dangling image if this is the last image pointing to this target.
|
||||
if len(imgs) == 1 {
|
||||
danglingImage := img
|
||||
|
||||
danglingImage.Name = danglingImageName(img.Target.Digest)
|
||||
delete(danglingImage.Labels, "io.containerd.image.name")
|
||||
delete(danglingImage.Labels, "org.opencontainers.image.ref.name")
|
||||
|
||||
_, err = is.Create(context.Background(), danglingImage)
|
||||
|
||||
// Error out in case we couldn't persist the old image.
|
||||
// If it already exists, then just continue.
|
||||
if err != nil && !cerrdefs.IsAlreadyExists(err) {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to create a dangling image for the replaced image %s with digest %s",
|
||||
danglingImage.Name, danglingImage.Target.Digest.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// Free the target name.
|
||||
err = is.Delete(context.Background(), img.Name)
|
||||
if err != nil {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to delete image %s which existed a moment before", img.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func danglingImageName(digest digest.Digest) string {
|
||||
return "moby-dangling@" + digest.String()
|
||||
}
|
Loading…
Reference in a new issue