c8d/pull: Keep the replaced image as dangling

With graphdrivers, the old image was still kept as a dangling image.
Keep the same behavior with containerd.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit db40a6132b)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2024-08-26 14:46:26 +02:00
parent b27de4ef16
commit ea58dab95e
2 changed files with 77 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/pkg/snapshotters"
"github.com/containerd/containerd/remotes/docker"
cerrdefs "github.com/containerd/errdefs"
@@ -79,10 +80,43 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor
resolver, _ := i.newResolverFromAuthConfig(ctx, authConfig, ref)
opts = append(opts, containerd.WithResolver(resolver))
old, err := i.resolveDescriptor(ctx, ref.String())
oldImage, err := i.resolveImage(ctx, ref.String())
if err != nil && !errdefs.IsNotFound(err) {
return err
}
// Will be set to the new image after pull succeeds.
var outNewImg *containerd.Image
if oldImage.Target.Digest != "" {
// Lease the old image content to prevent it from being garbage collected until we keep it as dangling image.
lm := i.client.LeasesService()
lease, err := lm.Create(ctx, leases.WithRandomID())
if err != nil {
return errdefs.System(fmt.Errorf("failed to create lease: %w", err))
}
err = leaseContent(ctx, i.content, lm, lease, oldImage.Target)
if err != nil {
return errdefs.System(fmt.Errorf("failed to lease content: %w", err))
}
// If the pulled image is different than the old image, we will keep the old image as a dangling image.
defer func() {
if outNewImg != nil {
img := *outNewImg
if img.Target().Digest != oldImage.Target.Digest {
if err := i.ensureDanglingImage(ctx, oldImage); err != nil {
log.G(ctx).WithError(err).Warn("failed to keep the previous image as dangling")
}
}
}
if err := lm.Delete(ctx, lease); err != nil {
log.G(ctx).WithError(err).Warn("failed to delete lease")
}
}()
}
p := platforms.Default()
if platform != nil {
p = platforms.Only(*platform)
@@ -100,7 +134,6 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor
pp := pullProgress{store: i.content, showExists: true}
finishProgress := jobs.showProgress(ctx, out, pp)
var outNewImg *containerd.Image
defer func() {
finishProgress()
@@ -116,7 +149,8 @@ func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platfor
if outNewImg != nil {
img := *outNewImg
progress.Message(out, "", "Digest: "+img.Target().Digest.String())
writeStatus(out, reference.FamiliarString(ref), old.Digest != img.Target().Digest)
newer := oldImage.Target.Digest != img.Target().Digest
writeStatus(out, reference.FamiliarString(ref), newer)
}
}()

View File

@@ -1,6 +1,7 @@
package image
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -17,6 +18,7 @@ import (
"github.com/containerd/platforms"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/testutil/daemon"
"github.com/docker/docker/testutil/registry"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
@@ -195,3 +197,41 @@ func TestImagePullNonExisting(t *testing.T) {
})
}
}
func TestImagePullKeepOldAsDangling(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Can't run new daemons on Windows")
ctx := setupTest(t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t)
defer d.Cleanup(t)
apiClient := d.NewClientT(t)
inspect1, _, err := apiClient.ImageInspectWithRaw(ctx, "busybox:latest")
assert.NilError(t, err)
prevID := inspect1.ID
t.Log(inspect1)
assert.NilError(t, apiClient.ImageTag(ctx, "busybox:latest", "alpine:latest"))
_, err = apiClient.ImageRemove(ctx, "busybox:latest", image.RemoveOptions{})
assert.NilError(t, err)
rc, err := apiClient.ImagePull(ctx, "alpine:latest", image.PullOptions{})
assert.NilError(t, err)
defer rc.Close()
var b bytes.Buffer
_, _ = io.Copy(&b, rc)
t.Log(b.String())
_, _, err = apiClient.ImageInspectWithRaw(ctx, prevID)
assert.NilError(t, err)
}