diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index ece336bef4..5a1496efde 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -21,7 +21,7 @@ type Backend interface { } type imageBackend interface { - ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) + ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) ImageHistory(imageName string) ([]*image.HistoryResponseItem, error) Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) GetImage(refOrID string, platform *specs.Platform) (retImg *dockerimage.Image, retErr error) diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index b186e61ade..f9af805d51 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -195,7 +195,7 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r force := httputils.BoolValue(r, "force") prune := !httputils.BoolValue(r, "noprune") - list, err := s.backend.ImageDelete(name, force, prune) + list, err := s.backend.ImageDelete(ctx, name, force, prune) if err != nil { return err } diff --git a/daemon/containerd/image_delete.go b/daemon/containerd/image_delete.go index 7ed92a42ce..a88669920b 100644 --- a/daemon/containerd/image_delete.go +++ b/daemon/containerd/image_delete.go @@ -1,6 +1,12 @@ package containerd -import "github.com/docker/docker/api/types" +import ( + "context" + + "github.com/containerd/containerd/images" + "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" +) // ImageDelete deletes the image referenced by the given imageRef from this // daemon. The given imageRef can be an image ID, ID prefix, or a repository @@ -35,6 +41,22 @@ import "github.com/docker/docker/api/types" // If prune is true, ancestor images will each attempt to be deleted quietly, // meaning any delete conflicts will cause the image to not be deleted and the // conflict will not be reported. -func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { - panic("not implemented") +// +// TODO(thaJeztah): implement ImageDelete "force" options; see https://github.com/moby/moby/issues/43850 +// TODO(thaJeztah): implement ImageDelete "prune" options; see https://github.com/moby/moby/issues/43849 +// TODO(thaJeztah): add support for image delete using image (short)ID; see https://github.com/moby/moby/issues/43854 +// TODO(thaJeztah): mage delete should send image "untag" events and prometheus counters; see https://github.com/moby/moby/issues/43855 +func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { + parsedRef, err := reference.ParseNormalizedNamed(imageRef) + if err != nil { + return nil, err + } + ref := reference.TagNameOnly(parsedRef) + + err = i.client.ImageService().Delete(ctx, ref.String(), images.SynchronousDelete()) + if err != nil { + return nil, err + } + + return []types.ImageDeleteResponseItem{{Untagged: reference.FamiliarString(parsedRef)}}, nil } diff --git a/daemon/image_service.go b/daemon/image_service.go index 032a9977e5..fd27dd907f 100644 --- a/daemon/image_service.go +++ b/daemon/image_service.go @@ -28,7 +28,7 @@ type ImageService interface { PullImage(ctx context.Context, image, tag string, platform *v1.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error CreateImage(config []byte, parent string) (builder.Image, error) - ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, 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 Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) diff --git a/daemon/images/image_delete.go b/daemon/images/image_delete.go index 9cad78b87c..b5e1f7d9c3 100644 --- a/daemon/images/image_delete.go +++ b/daemon/images/image_delete.go @@ -1,6 +1,7 @@ package images // import "github.com/docker/docker/daemon/images" import ( + "context" "fmt" "strings" "time" @@ -58,7 +59,7 @@ const ( // If prune is true, ancestor images will each attempt to be deleted quietly, // meaning any delete conflicts will cause the image to not be deleted and the // conflict will not be reported. -func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { +func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { start := time.Now() records := []types.ImageDeleteResponseItem{} diff --git a/daemon/images/image_prune.go b/daemon/images/image_prune.go index 1d2cd967c3..d3c4608f72 100644 --- a/daemon/images/image_prune.go +++ b/daemon/images/image_prune.go @@ -119,7 +119,7 @@ deleteImagesLoop: if shouldDelete { for _, ref := range refs { - imgDel, err := i.ImageDelete(ref.String(), false, true) + imgDel, err := i.ImageDelete(ctx, ref.String(), false, true) if imageDeleteFailed(ref.String(), err) { continue } @@ -128,7 +128,7 @@ deleteImagesLoop: } } else { hex := id.Digest().Hex() - imgDel, err := i.ImageDelete(hex, false, true) + imgDel, err := i.ImageDelete(ctx, hex, false, true) if imageDeleteFailed(hex, err) { continue } @@ -163,7 +163,7 @@ func imageDeleteFailed(ref string, err error) bool { switch { case err == nil: return false - case errdefs.IsConflict(err): + case errdefs.IsConflict(err), errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): return true default: logrus.Warnf("failed to prune image %s: %v", ref, err)