From 35037b78fdfdd054f17b54440b6b66bcb7147258 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 16 Nov 2016 21:46:37 -0800 Subject: [PATCH] Convert DanglingOnly to Filters for `docker image prune` This fix convert DanglingOnly in ImagesPruneConfig to Filters, so that it is possible to maintain API compatibility in the future. Several integration tests have been added to cover changes. This fix is related to 28497. A follow up to this PR will be done once this PR is merged. Signed-off-by: Yong Tang Upstream-commit: a6be56b54e871c4e7a6e72881770a64676c27c3c Component: engine --- .../api/server/router/container/backend.go | 3 +- .../router/container/container_routes.go | 10 ++-- .../engine/api/server/router/image/backend.go | 2 +- .../api/server/router/image/image_routes.go | 10 ++-- .../api/server/router/network/backend.go | 3 +- .../server/router/network/network_routes.go | 11 +---- .../api/server/router/volume/backend.go | 3 +- .../api/server/router/volume/volume_routes.go | 13 +---- components/engine/api/swagger.yaml | 49 +++++++++++++------ components/engine/api/types/types.go | 21 -------- .../engine/cli/command/container/prune.go | 4 +- components/engine/cli/command/image/prune.go | 9 ++-- .../engine/cli/command/network/prune.go | 4 +- components/engine/cli/command/volume/prune.go | 4 +- components/engine/client/container_prune.go | 10 +++- components/engine/client/image_prune.go | 10 +++- components/engine/client/interface.go | 8 +-- components/engine/client/network_prune.go | 14 +++++- components/engine/client/utils.go | 20 +++++++- components/engine/client/volume_prune.go | 10 +++- components/engine/daemon/prune.go | 31 ++++++++---- .../docker_cli_prune_unix_test.go | 30 ++++++++++++ 22 files changed, 171 insertions(+), 108 deletions(-) diff --git a/components/engine/api/server/router/container/backend.go b/components/engine/api/server/router/container/backend.go index 5b4134eba5..0d20188ccf 100644 --- a/components/engine/api/server/router/container/backend.go +++ b/components/engine/api/server/router/container/backend.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/archive" ) @@ -64,7 +65,7 @@ type attachBackend interface { // systemBackend includes functions to implement to provide system wide containers functionality type systemBackend interface { - ContainersPrune(config *types.ContainersPruneConfig) (*types.ContainersPruneReport, error) + ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) } // Backend is all the methods that need to be implemented to provide container specific functionality. diff --git a/components/engine/api/server/router/container/container_routes.go b/components/engine/api/server/router/container/container_routes.go index 18bc50bcac..9c9bc0f8c3 100644 --- a/components/engine/api/server/router/container/container_routes.go +++ b/components/engine/api/server/router/container/container_routes.go @@ -541,16 +541,12 @@ func (s *containerRouter) postContainersPrune(ctx context.Context, w http.Respon return err } - if err := httputils.CheckForJSON(r); err != nil { + pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + if err != nil { return err } - var cfg types.ContainersPruneConfig - if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { - return err - } - - pruneReport, err := s.backend.ContainersPrune(&cfg) + pruneReport, err := s.backend.ContainersPrune(pruneFilters) if err != nil { return err } diff --git a/components/engine/api/server/router/image/backend.go b/components/engine/api/server/router/image/backend.go index 5209d7e879..19a67a5ed0 100644 --- a/components/engine/api/server/router/image/backend.go +++ b/components/engine/api/server/router/image/backend.go @@ -29,7 +29,7 @@ type imageBackend interface { Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) LookupImage(name string) (*types.ImageInspect, error) TagImage(imageName, repository, tag string) error - ImagesPrune(config *types.ImagesPruneConfig) (*types.ImagesPruneReport, error) + ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) } type importExportBackend interface { diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go index 2b12503ddc..69403652a0 100644 --- a/components/engine/api/server/router/image/image_routes.go +++ b/components/engine/api/server/router/image/image_routes.go @@ -331,16 +331,12 @@ func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter return err } - if err := httputils.CheckForJSON(r); err != nil { + pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + if err != nil { return err } - var cfg types.ImagesPruneConfig - if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { - return err - } - - pruneReport, err := s.backend.ImagesPrune(&cfg) + pruneReport, err := s.backend.ImagesPrune(pruneFilters) if err != nil { return err } diff --git a/components/engine/api/server/router/network/backend.go b/components/engine/api/server/router/network/backend.go index cf82398c93..0d1dfb0123 100644 --- a/components/engine/api/server/router/network/backend.go +++ b/components/engine/api/server/router/network/backend.go @@ -2,6 +2,7 @@ package network import ( "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" "github.com/docker/libnetwork" ) @@ -17,5 +18,5 @@ type Backend interface { ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error DeleteNetwork(name string) error - NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) + NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) } diff --git a/components/engine/api/server/router/network/network_routes.go b/components/engine/api/server/router/network/network_routes.go index 39b45e58bc..5b7b9738eb 100644 --- a/components/engine/api/server/router/network/network_routes.go +++ b/components/engine/api/server/router/network/network_routes.go @@ -297,16 +297,7 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr return err } - if err := httputils.CheckForJSON(r); err != nil { - return err - } - - var cfg types.NetworksPruneConfig - if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { - return err - } - - pruneReport, err := n.backend.NetworksPrune(&cfg) + pruneReport, err := n.backend.NetworksPrune(filters.Args{}) if err != nil { return err } diff --git a/components/engine/api/server/router/volume/backend.go b/components/engine/api/server/router/volume/backend.go index 3a7608e0bd..180c06e5d3 100644 --- a/components/engine/api/server/router/volume/backend.go +++ b/components/engine/api/server/router/volume/backend.go @@ -3,6 +3,7 @@ package volume import ( // TODO return types need to be refactored into pkg "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" ) // Backend is the methods that need to be implemented to provide @@ -12,5 +13,5 @@ type Backend interface { VolumeInspect(name string) (*types.Volume, error) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) VolumeRm(name string, force bool) error - VolumesPrune(config *types.VolumesPruneConfig) (*types.VolumesPruneReport, error) + VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) } diff --git a/components/engine/api/server/router/volume/volume_routes.go b/components/engine/api/server/router/volume/volume_routes.go index e0398817c3..cfd4618a4d 100644 --- a/components/engine/api/server/router/volume/volume_routes.go +++ b/components/engine/api/server/router/volume/volume_routes.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/docker/docker/api/server/httputils" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" volumetypes "github.com/docker/docker/api/types/volume" "golang.org/x/net/context" ) @@ -72,16 +72,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit return err } - if err := httputils.CheckForJSON(r); err != nil { - return err - } - - var cfg types.VolumesPruneConfig - if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { - return err - } - - pruneReport, err := v.backend.VolumesPrune(&cfg) + pruneReport, err := v.backend.VolumesPrune(filters.Args{}) if err != nil { return err } diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index ac897cf7bc..ff43defc77 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -4187,11 +4187,17 @@ paths: /containers/prune: post: summary: "Delete stopped containers" - consumes: - - "application/json" produces: - "application/json" operationId: "ContainerPrune" + parameters: + - name: "filters" + in: "query" + description: | + Filters to process on the prune list, encoded as JSON (a `map[string][]string`). + + Available filters: + type: "string" responses: 200: description: "No error" @@ -4849,21 +4855,20 @@ paths: /images/prune: post: summary: "Delete unused images" - consumes: - - "application/json" produces: - "application/json" operationId: "ImagePrune" parameters: - - name: "body" - in: "body" - schema: - type: "object" - properties: - DanglingOnly: - description: "Only delete unused *and* untagged images" - type: "boolean" - default: false + - name: "filters" + in: "query" + description: | + Filters to process on the prune list, encoded as JSON (a `map[string][]string`). + + Available filters: + - `dangling=` When set to `true` (or `1`), prune only + unused *and* untagged images. When set to `false` + (or `0`), all unused images are pruned. + type: "string" responses: 200: description: "No error" @@ -5945,11 +5950,17 @@ paths: /volumes/prune: post: summary: "Delete unused volumes" - consumes: - - "application/json" produces: - "application/json" operationId: "VolumePrune" + parameters: + - name: "filters" + in: "query" + description: | + Filters to process on the prune list, encoded as JSON (a `map[string][]string`). + + Available filters: + type: "string" responses: 200: description: "No error" @@ -6287,6 +6298,14 @@ paths: produces: - "application/json" operationId: "NetworkPrune" + parameters: + - name: "filters" + in: "query" + description: | + Filters to process on the prune list, encoded as JSON (a `map[string][]string`). + + Available filters: + type: "string" responses: 200: description: "No error" diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go index 4a96ec556a..a82c3e88ef 100644 --- a/components/engine/api/types/types.go +++ b/components/engine/api/types/types.go @@ -509,27 +509,6 @@ type DiskUsage struct { Volumes []*Volume } -// ImagesPruneConfig contains the configuration for Engine API: -// POST "/images/prune" -type ImagesPruneConfig struct { - DanglingOnly bool -} - -// ContainersPruneConfig contains the configuration for Engine API: -// POST "/images/prune" -type ContainersPruneConfig struct { -} - -// VolumesPruneConfig contains the configuration for Engine API: -// POST "/images/prune" -type VolumesPruneConfig struct { -} - -// NetworksPruneConfig contains the configuration for Engine API: -// POST "/networks/prune" -type NetworksPruneConfig struct { -} - // ContainersPruneReport contains the response for Engine API: // POST "/containers/prune" type ContainersPruneReport struct { diff --git a/components/engine/cli/command/container/prune.go b/components/engine/cli/command/container/prune.go index ec6b0e3147..064f4c08e0 100644 --- a/components/engine/cli/command/container/prune.go +++ b/components/engine/cli/command/container/prune.go @@ -5,7 +5,7 @@ import ( "golang.org/x/net/context" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" units "github.com/docker/go-units" @@ -52,7 +52,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u return } - report, err := dockerCli.Client().ContainersPrune(context.Background(), types.ContainersPruneConfig{}) + report, err := dockerCli.Client().ContainersPrune(context.Background(), filters.Args{}) if err != nil { return } diff --git a/components/engine/cli/command/image/prune.go b/components/engine/cli/command/image/prune.go index ea84cda877..82c28fcf49 100644 --- a/components/engine/cli/command/image/prune.go +++ b/components/engine/cli/command/image/prune.go @@ -5,7 +5,7 @@ import ( "golang.org/x/net/context" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" units "github.com/docker/go-units" @@ -54,6 +54,9 @@ Are you sure you want to continue?` ) func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { + pruneFilters := filters.NewArgs() + pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all)) + warning := danglingWarning if opts.all { warning = allImageWarning @@ -62,9 +65,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u return } - report, err := dockerCli.Client().ImagesPrune(context.Background(), types.ImagesPruneConfig{ - DanglingOnly: !opts.all, - }) + report, err := dockerCli.Client().ImagesPrune(context.Background(), pruneFilters) if err != nil { return } diff --git a/components/engine/cli/command/network/prune.go b/components/engine/cli/command/network/prune.go index f2f8cc20c4..9f1979e6b5 100644 --- a/components/engine/cli/command/network/prune.go +++ b/components/engine/cli/command/network/prune.go @@ -5,7 +5,7 @@ import ( "golang.org/x/net/context" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/spf13/cobra" @@ -50,7 +50,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, e return } - report, err := dockerCli.Client().NetworksPrune(context.Background(), types.NetworksPruneConfig{}) + report, err := dockerCli.Client().NetworksPrune(context.Background(), filters.Args{}) if err != nil { return } diff --git a/components/engine/cli/command/volume/prune.go b/components/engine/cli/command/volume/prune.go index ac9c94451a..405fbeb295 100644 --- a/components/engine/cli/command/volume/prune.go +++ b/components/engine/cli/command/volume/prune.go @@ -5,7 +5,7 @@ import ( "golang.org/x/net/context" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" units "github.com/docker/go-units" @@ -52,7 +52,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u return } - report, err := dockerCli.Client().VolumesPrune(context.Background(), types.VolumesPruneConfig{}) + report, err := dockerCli.Client().VolumesPrune(context.Background(), filters.Args{}) if err != nil { return } diff --git a/components/engine/client/container_prune.go b/components/engine/client/container_prune.go index 3eabe71a7f..b582170867 100644 --- a/components/engine/client/container_prune.go +++ b/components/engine/client/container_prune.go @@ -5,18 +5,24 @@ import ( "fmt" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "golang.org/x/net/context" ) // ContainersPrune requests the daemon to delete unused data -func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) { +func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) { var report types.ContainersPruneReport if err := cli.NewVersionError("1.25", "container prune"); err != nil { return report, err } - serverResp, err := cli.post(ctx, "/containers/prune", nil, cfg, nil) + query, err := getFiltersQuery(pruneFilters) + if err != nil { + return report, err + } + + serverResp, err := cli.post(ctx, "/containers/prune", query, nil, nil) if err != nil { return report, err } diff --git a/components/engine/client/image_prune.go b/components/engine/client/image_prune.go index d5e69d5b19..5ef98b7f02 100644 --- a/components/engine/client/image_prune.go +++ b/components/engine/client/image_prune.go @@ -5,18 +5,24 @@ import ( "fmt" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "golang.org/x/net/context" ) // ImagesPrune requests the daemon to delete unused data -func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) { +func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (types.ImagesPruneReport, error) { var report types.ImagesPruneReport if err := cli.NewVersionError("1.25", "image prune"); err != nil { return report, err } - serverResp, err := cli.post(ctx, "/images/prune", nil, cfg, nil) + query, err := getFiltersQuery(pruneFilters) + if err != nil { + return report, err + } + + serverResp, err := cli.post(ctx, "/images/prune", query, nil, nil) if err != nil { return report, err } diff --git a/components/engine/client/interface.go b/components/engine/client/interface.go index 0d722d9075..6319f34f1e 100644 --- a/components/engine/client/interface.go +++ b/components/engine/client/interface.go @@ -64,7 +64,7 @@ type ContainerAPIClient interface { ContainerWait(ctx context.Context, container string) (int64, error) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error - ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) + ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) } // ImageAPIClient defines API client methods for the images @@ -82,7 +82,7 @@ type ImageAPIClient interface { ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) ImageTag(ctx context.Context, image, ref string) error - ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) + ImagesPrune(ctx context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) } // NetworkAPIClient defines API client methods for the networks @@ -94,7 +94,7 @@ type NetworkAPIClient interface { NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(ctx context.Context, networkID string) error - NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error) + NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) } // NodeAPIClient defines API client methods for the nodes @@ -157,7 +157,7 @@ type VolumeAPIClient interface { VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) VolumeRemove(ctx context.Context, volumeID string, force bool) error - VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) + VolumesPrune(ctx context.Context, pruneFilter filters.Args) (types.VolumesPruneReport, error) } // SecretAPIClient defines API client methods for secrets diff --git a/components/engine/client/network_prune.go b/components/engine/client/network_prune.go index 01185f2e02..7352a7f0c5 100644 --- a/components/engine/client/network_prune.go +++ b/components/engine/client/network_prune.go @@ -5,14 +5,24 @@ import ( "fmt" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "golang.org/x/net/context" ) // NetworksPrune requests the daemon to delete unused networks -func (cli *Client) NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error) { +func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) { var report types.NetworksPruneReport - serverResp, err := cli.post(ctx, "/networks/prune", nil, cfg, nil) + if err := cli.NewVersionError("1.25", "network prune"); err != nil { + return report, err + } + + query, err := getFiltersQuery(pruneFilters) + if err != nil { + return report, err + } + + serverResp, err := cli.post(ctx, "/networks/prune", query, nil, nil) if err != nil { return report, err } diff --git a/components/engine/client/utils.go b/components/engine/client/utils.go index 03bf4c82fa..23d520ecb8 100644 --- a/components/engine/client/utils.go +++ b/components/engine/client/utils.go @@ -1,6 +1,10 @@ package client -import "regexp" +import ( + "github.com/docker/docker/api/types/filters" + "net/url" + "regexp" +) var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`) @@ -13,3 +17,17 @@ func getDockerOS(serverHeader string) string { } return osType } + +// getFiltersQuery returns a url query with "filters" query term, based on the +// filters provided. +func getFiltersQuery(f filters.Args) (url.Values, error) { + query := url.Values{} + if f.Len() > 0 { + filterJSON, err := filters.ToParam(f) + if err != nil { + return query, err + } + query.Set("filters", filterJSON) + } + return query, nil +} diff --git a/components/engine/client/volume_prune.go b/components/engine/client/volume_prune.go index ea4e234a30..a07e4ce637 100644 --- a/components/engine/client/volume_prune.go +++ b/components/engine/client/volume_prune.go @@ -5,18 +5,24 @@ import ( "fmt" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "golang.org/x/net/context" ) // VolumesPrune requests the daemon to delete unused data -func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) { +func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (types.VolumesPruneReport, error) { var report types.VolumesPruneReport if err := cli.NewVersionError("1.25", "volume prune"); err != nil { return report, err } - serverResp, err := cli.post(ctx, "/volumes/prune", nil, cfg, nil) + query, err := getFiltersQuery(pruneFilters) + if err != nil { + return report, err + } + + serverResp, err := cli.post(ctx, "/volumes/prune", query, nil, nil) if err != nil { return report, err } diff --git a/components/engine/daemon/prune.go b/components/engine/daemon/prune.go index 953a568d91..a693beb4e1 100644 --- a/components/engine/daemon/prune.go +++ b/components/engine/daemon/prune.go @@ -1,11 +1,13 @@ package daemon import ( + "fmt" "regexp" "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/pkg/directory" @@ -16,7 +18,7 @@ import ( ) // ContainersPrune removes unused containers -func (daemon *Daemon) ContainersPrune(config *types.ContainersPruneConfig) (*types.ContainersPruneReport, error) { +func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) { rep := &types.ContainersPruneReport{} allContainers := daemon.List() @@ -40,7 +42,7 @@ func (daemon *Daemon) ContainersPrune(config *types.ContainersPruneConfig) (*typ } // VolumesPrune removes unused local volumes -func (daemon *Daemon) VolumesPrune(config *types.VolumesPruneConfig) (*types.VolumesPruneReport, error) { +func (daemon *Daemon) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) { rep := &types.VolumesPruneReport{} pruneVols := func(v volume.Volume) error { @@ -70,11 +72,20 @@ func (daemon *Daemon) VolumesPrune(config *types.VolumesPruneConfig) (*types.Vol } // ImagesPrune removes unused images -func (daemon *Daemon) ImagesPrune(config *types.ImagesPruneConfig) (*types.ImagesPruneReport, error) { +func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) { rep := &types.ImagesPruneReport{} + danglingOnly := true + if pruneFilters.Include("dangling") { + if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") { + danglingOnly = false + } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") { + return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling")) + } + } + var allImages map[image.ID]*image.Image - if config.DanglingOnly { + if danglingOnly { allImages = daemon.imageStore.Heads() } else { allImages = daemon.imageStore.Map() @@ -106,7 +117,7 @@ func (daemon *Daemon) ImagesPrune(config *types.ImagesPruneConfig) (*types.Image deletedImages := []types.ImageDelete{} refs := daemon.referenceStore.References(dgst) if len(refs) > 0 { - if config.DanglingOnly { + if danglingOnly { // Not a dangling image continue } @@ -156,7 +167,7 @@ func (daemon *Daemon) ImagesPrune(config *types.ImagesPruneConfig) (*types.Image } // localNetworksPrune removes unused local networks -func (daemon *Daemon) localNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) { +func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) { rep := &types.NetworksPruneReport{} var err error // When the function returns true, the walk will stop. @@ -177,7 +188,7 @@ func (daemon *Daemon) localNetworksPrune(config *types.NetworksPruneConfig) (*ty } // clusterNetworksPrune removes unused cluster networks -func (daemon *Daemon) clusterNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) { +func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) { rep := &types.NetworksPruneReport{} cluster := daemon.GetCluster() networks, err := cluster.GetNetworks() @@ -207,15 +218,15 @@ func (daemon *Daemon) clusterNetworksPrune(config *types.NetworksPruneConfig) (* } // NetworksPrune removes unused networks -func (daemon *Daemon) NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) { +func (daemon *Daemon) NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) { rep := &types.NetworksPruneReport{} - clusterRep, err := daemon.clusterNetworksPrune(config) + clusterRep, err := daemon.clusterNetworksPrune(pruneFilters) if err != nil { logrus.Warnf("could not remove cluster networks: %v", err) } else { rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...) } - localRep, err := daemon.localNetworksPrune(config) + localRep, err := daemon.localNetworksPrune(pruneFilters) if err != nil { logrus.Warnf("could not remove local networks: %v", err) } else { diff --git a/components/engine/integration-cli/docker_cli_prune_unix_test.go b/components/engine/integration-cli/docker_cli_prune_unix_test.go index 5585cab302..dabbc72081 100644 --- a/components/engine/integration-cli/docker_cli_prune_unix_test.go +++ b/components/engine/integration-cli/docker_cli_prune_unix_test.go @@ -59,3 +59,33 @@ func (s *DockerSwarmSuite) TestPruneNetwork(c *check.C) { waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 0) pruneNetworkAndVerify(c, d, []string{}, []string{"n1", "n3"}) } + +func (s *DockerDaemonSuite) TestPruneImageDangling(c *check.C) { + c.Assert(s.d.StartWithBusybox(), checker.IsNil) + + out, _, err := s.d.buildImageWithOut("test", + `FROM busybox + LABEL foo=bar`, true, "-q") + c.Assert(err, checker.IsNil) + id := strings.TrimSpace(out) + + out, err = s.d.Cmd("images", "-q", "--no-trunc") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Contains, id) + + out, err = s.d.Cmd("image", "prune", "--force") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id) + + out, err = s.d.Cmd("images", "-q", "--no-trunc") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Contains, id) + + out, err = s.d.Cmd("image", "prune", "--force", "--all") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Contains, id) + + out, err = s.d.Cmd("images", "-q", "--no-trunc") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id) +}