|
@@ -23,7 +23,6 @@ import (
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/compose-spec/compose-go/types"
|
|
"github.com/compose-spec/compose-go/types"
|
|
|
- "github.com/distribution/distribution/v3/reference"
|
|
|
|
|
moby "github.com/docker/docker/api/types"
|
|
moby "github.com/docker/docker/api/types"
|
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/filters"
|
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/errdefs"
|
|
@@ -32,7 +31,6 @@ import (
|
|
|
|
|
|
|
|
"github.com/docker/compose/v2/pkg/api"
|
|
"github.com/docker/compose/v2/pkg/api"
|
|
|
"github.com/docker/compose/v2/pkg/progress"
|
|
"github.com/docker/compose/v2/pkg/progress"
|
|
|
- "github.com/docker/compose/v2/pkg/utils"
|
|
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type downOp func() error
|
|
type downOp func() error
|
|
@@ -125,7 +123,12 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) ([]downOp, error) {
|
|
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) ([]downOp, error) {
|
|
|
- images, err := s.getServiceImagesToRemove(ctx, options, project)
|
|
|
|
|
|
|
+ imagePruner := NewImagePruner(s.apiClient(), project)
|
|
|
|
|
+ pruneOpts := ImagePruneOptions{
|
|
|
|
|
+ Mode: ImagePruneMode(options.Images),
|
|
|
|
|
+ RemoveOrphans: options.RemoveOrphans,
|
|
|
|
|
+ }
|
|
|
|
|
+ images, err := imagePruner.ImagesToPrune(ctx, pruneOpts)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
@@ -201,110 +204,6 @@ func (s *composeService) removeNetwork(ctx context.Context, name string, w progr
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-//nolint:gocyclo
|
|
|
|
|
-func (s *composeService) getServiceImagesToRemove(ctx context.Context, options api.DownOptions, project *types.Project) ([]string, error) {
|
|
|
|
|
- if options.Images == "" {
|
|
|
|
|
- return nil, nil
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var localServiceImages []string
|
|
|
|
|
- var imagesToRemove []string
|
|
|
|
|
- addImageToRemove := func(img string, checkExistence bool) {
|
|
|
|
|
- // since some references come from user input (service.image) and some
|
|
|
|
|
- // come from the engine API, we standardize them, opting for the
|
|
|
|
|
- // familiar name format since they'll also be displayed in the CLI
|
|
|
|
|
- ref, err := reference.ParseNormalizedNamed(img)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- ref = reference.TagNameOnly(ref)
|
|
|
|
|
- img = reference.FamiliarString(ref)
|
|
|
|
|
- if utils.StringContains(imagesToRemove, img) {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if checkExistence {
|
|
|
|
|
- _, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
|
|
|
|
|
- if errdefs.IsNotFound(err) {
|
|
|
|
|
- // err on the side of caution: only skip if we successfully
|
|
|
|
|
- // queried the API and got back a definitive "not exists"
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- imagesToRemove = append(imagesToRemove, img)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- imageListOpts := moby.ImageListOptions{
|
|
|
|
|
- Filters: filters.NewArgs(
|
|
|
|
|
- projectFilter(project.Name),
|
|
|
|
|
- // TODO(milas): we should really clean up the dangling images as
|
|
|
|
|
- // well (historically we have NOT); need to refactor this to handle
|
|
|
|
|
- // it gracefully without producing confusing CLI output, i.e. we
|
|
|
|
|
- // do not want to print out a bunch of untagged/dangling image IDs,
|
|
|
|
|
- // they should be grouped into a logical operation for the relevant
|
|
|
|
|
- // service
|
|
|
|
|
- filters.Arg("dangling", "false"),
|
|
|
|
|
- ),
|
|
|
|
|
- }
|
|
|
|
|
- projectImages, err := s.apiClient().ImageList(ctx, imageListOpts)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, err
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 1. Remote / custom-named images - only deleted on `--rmi="all"`
|
|
|
|
|
- for _, service := range project.Services {
|
|
|
|
|
- if service.Image == "" {
|
|
|
|
|
- localServiceImages = append(localServiceImages, service.Name)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if options.Images == "all" {
|
|
|
|
|
- addImageToRemove(service.Image, true)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 2. *LABELED* Locally-built images with implicit image names
|
|
|
|
|
- //
|
|
|
|
|
- // If `--remove-orphans` is being used, then ALL images for the project
|
|
|
|
|
- // will be selected for removal. Otherwise, only those that match a known
|
|
|
|
|
- // service based on the loaded project will be included.
|
|
|
|
|
- for _, img := range projectImages {
|
|
|
|
|
- if len(img.RepoTags) == 0 {
|
|
|
|
|
- // currently, we're only removing the tagged references, but
|
|
|
|
|
- // if we start removing the dangling images and grouping by
|
|
|
|
|
- // service, we can remove this (and should rely on `Image::ID`)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- shouldRemove := options.RemoveOrphans
|
|
|
|
|
- for _, service := range localServiceImages {
|
|
|
|
|
- if img.Labels[api.ServiceLabel] == service {
|
|
|
|
|
- shouldRemove = true
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if shouldRemove {
|
|
|
|
|
- addImageToRemove(img.RepoTags[0], false)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 3. *UNLABELED* Locally-built images with implicit image names
|
|
|
|
|
- //
|
|
|
|
|
- // This is a fallback for (2) to handle images built by previous
|
|
|
|
|
- // versions of Compose, which did not label their built images.
|
|
|
|
|
- for _, serviceName := range localServiceImages {
|
|
|
|
|
- service, err := project.GetService(serviceName)
|
|
|
|
|
- if err != nil || service.Image != "" {
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- imgName := api.GetImageNameOrDefault(service, project.Name)
|
|
|
|
|
- addImageToRemove(imgName, true)
|
|
|
|
|
- }
|
|
|
|
|
- return imagesToRemove, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
|
|
func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
|
|
|
id := fmt.Sprintf("Image %s", image)
|
|
id := fmt.Sprintf("Image %s", image)
|
|
|
w.Event(progress.NewEvent(id, progress.Working, "Removing"))
|
|
w.Event(progress.NewEvent(id, progress.Working, "Removing"))
|