Skip to content

Commit

Permalink
Merge pull request #8689 from bblenard/issue-8672-volume-prune
Browse files Browse the repository at this point in the history
Add volume prune --filter support
  • Loading branch information
openshift-merge-robot authored Dec 14, 2020
2 parents bdbf47f + a0204ad commit e5741b9
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 25 deletions.
16 changes: 15 additions & 1 deletion cmd/podman/volumes/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import (
"strings"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/filters"
"github.com/spf13/cobra"
)

Expand All @@ -28,6 +30,7 @@ var (
RunE: prune,
ValidArgsFunction: completion.AutocompleteNone,
}
filter = []string{}
)

func init() {
Expand All @@ -37,10 +40,17 @@ func init() {
Parent: volumeCmd,
})
flags := pruneCommand.Flags()

filterFlagName := "filter"
flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')")
_ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters)
flags.BoolP("force", "f", false, "Do not prompt for confirmation")
}

func prune(cmd *cobra.Command, args []string) error {
var (
pruneOptions = entities.VolumePruneOptions{}
)
// Prompt for confirmation if --force is not set
force, err := cmd.Flags().GetBool("force")
if err != nil {
Expand All @@ -58,7 +68,11 @@ func prune(cmd *cobra.Command, args []string) error {
return nil
}
}
responses, err := registry.ContainerEngine().VolumePrune(context.Background())
pruneOptions.Filters, err = filters.ParseFilterArgumentsIntoFilters(filter)
if err != nil {
return err
}
responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions)
if err != nil {
return err
}
Expand Down
16 changes: 15 additions & 1 deletion docs/source/markdown/podman-volume-prune.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ podman\-volume\-prune - Remove all unused volumes

## DESCRIPTION

Removes all unused volumes. You will be prompted to confirm the removal of all the
Removes unused volumes. By default all unused volumes will be removed, the **--filter** flag can
be used to filter specific volumes. You will be prompted to confirm the removal of all the
unused volumes. To bypass the confirmation, use the **--force** flag.


Expand All @@ -18,6 +19,17 @@ unused volumes. To bypass the confirmation, use the **--force** flag.

Do not prompt for confirmation.

#### **--filter**

Filter volumes to be pruned. Volumes can be filtered by the following attributes:

- dangling
- driver
- label
- name
- opt
- scope

#### **--help**

Print usage statement
Expand All @@ -29,6 +41,8 @@ Print usage statement
$ podman volume prune
$ podman volume prune --force
$ podman volume prune --filter label=mylabel=mylabelvalue
```

## SEE ALSO
Expand Down
4 changes: 2 additions & 2 deletions libpod/runtime_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
}

// PruneVolumes removes unused volumes from the system
func (r *Runtime) PruneVolumes(ctx context.Context) (map[string]error, error) {
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) (map[string]error, error) {
reports := make(map[string]error)
vols, err := r.GetAllVolumes()
vols, err := r.Volumes(filterFuncs...)
if err != nil {
return nil, err
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/api/handlers/compat/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package compat
import (
"encoding/json"
"net/http"
"net/url"
"time"

"github.com/containers/podman/v2/libpod"
Expand Down Expand Up @@ -254,14 +255,15 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
// TODO: We have no ability to pass pruning filters to `PruneVolumes()` so
// we'll explicitly reject the request if we see any
if len(query.Filters) > 0 {
utils.InternalServerError(w, errors.New("filters for pruning volumes is not implemented"))

f := (url.Values)(query.Filters)
filterFuncs, err := filters.GenerateVolumeFilters(f)
if err != nil {
utils.Error(w, "Something when wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters for %s", f.Encode()))
return
}

pruned, err := runtime.PruneVolumes(r.Context())
pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs)
if err != nil {
utils.InternalServerError(w, err)
return
Expand Down
20 changes: 19 additions & 1 deletion pkg/api/handlers/libpod/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package libpod
import (
"encoding/json"
"net/http"
"net/url"

"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
Expand Down Expand Up @@ -180,8 +181,25 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
func pruneVolumesHelper(r *http.Request) ([]*entities.VolumePruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
decoder = r.Context().Value("decoder").(*schema.Decoder)
)
pruned, err := runtime.PruneVolumes(r.Context())
query := struct {
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}

if err := decoder.Decode(&query, r.URL.Query()); err != nil {
return nil, err
}

f := (url.Values)(query.Filters)
filterFuncs, err := filters.GenerateVolumeFilters(f)
if err != nil {
return nil, err
}

pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs)
if err != nil {
return nil, err
}
Expand Down
41 changes: 37 additions & 4 deletions pkg/bindings/test/volumes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,15 @@ var _ = Describe("Podman volumes", func() {
Expect(vols[0].Name).To(Equal("homer"))
})

// TODO we need to add filtering to tests
It("prune unused volume", func() {
// Pruning when no volumes present should be ok
_, err := volumes.Prune(connText)
_, err := volumes.Prune(connText, nil)
Expect(err).To(BeNil())

// Removing an unused volume should work
_, err = volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
vols, err := volumes.Prune(connText)
vols, err := volumes.Prune(connText, nil)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))

Expand All @@ -163,11 +162,45 @@ var _ = Describe("Podman volumes", func() {
Expect(err).To(BeNil())
session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"})
session.Wait(45)
vols, err = volumes.Prune(connText)
vols, err = volumes.Prune(connText, nil)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))
_, err = volumes.Inspect(connText, "homer")
Expect(err).To(BeNil())

// Removing volume with non matching filter shouldn't prune any volumes
filters := make(map[string][]string)
filters["label"] = []string{"label1=idontmatch"}
_, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
"label1": "value1",
}})
Expect(err).To(BeNil())
vols, err = volumes.Prune(connText, filters)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 0))
vol2, err := volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
"label1": "value2",
}})
Expect(err).To(BeNil())
_, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{
"label1": "value3",
}})
Expect(err).To(BeNil())

// Removing volume with matching filter label and value should remove specific entry
filters = make(map[string][]string)
filters["label"] = []string{"label1=value2"}
vols, err = volumes.Prune(connText, filters)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))
Expect(vols[0].Id).To(Equal(vol2.Name))

// Removing volumes with matching filter label should remove all matching volumes
filters = make(map[string][]string)
filters["label"] = []string{"label1"}
vols, err = volumes.Prune(connText, filters)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 2))
})

})
12 changes: 10 additions & 2 deletions pkg/bindings/volumes/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,23 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL
}

// Prune removes unused volumes from the local filesystem.
func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
func Prune(ctx context.Context, filters map[string][]string) ([]*entities.VolumePruneReport, error) {
var (
pruned []*entities.VolumePruneReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil)
params := url.Values{}
if len(filters) > 0 {
strFilters, err := bindings.FiltersToString(filters)
if err != nil {
return nil, err
}
params.Set("filters", strFilters)
}
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/entities/engine_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ type ContainerEngine interface {
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error)
VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error)
VolumePrune(ctx context.Context) ([]*VolumePruneReport, error)
VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*VolumePruneReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
}
7 changes: 7 additions & 0 deletions pkg/domain/entities/volumes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package entities

import (
"net/url"
"time"

docker_api_types "github.com/docker/docker/api/types"
Expand Down Expand Up @@ -109,6 +110,12 @@ type VolumeInspectReport struct {
*VolumeConfigResponse
}

// VolumePruneOptions describes the options needed
// to prune a volume from the CLI
type VolumePruneOptions struct {
Filters url.Values `json:"filters" schema:"filters"`
}

type VolumePruneReport struct {
Err error
Id string //nolint
Expand Down
20 changes: 20 additions & 0 deletions pkg/domain/filters/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package filters

import (
"net/url"
"strings"

"github.com/pkg/errors"
)

func ParseFilterArgumentsIntoFilters(filters []string) (url.Values, error) {
parsedFilters := make(url.Values)
for _, f := range filters {
t := strings.SplitN(f, "=", 2)
if len(t) < 2 {
return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
parsedFilters.Add(t[0], t[1])
}
return parsedFilters, nil
}
3 changes: 2 additions & 1 deletion pkg/domain/filters/volumes.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package filters

import (
"net/url"
"strings"

"github.com/containers/podman/v2/libpod"
"github.com/pkg/errors"
)

func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) {
func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
var vf []libpod.VolumeFilter
for filter, v := range filters {
for _, val := range v {
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/abi/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...)
}
if options.Volume {
volumePruneReport, err := ic.pruneVolumesHelper(ctx)
volumePruneReport, err := ic.pruneVolumesHelper(ctx, nil)
if err != nil {
return nil, err
}
Expand Down
12 changes: 8 additions & 4 deletions pkg/domain/infra/abi/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,16 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
return reports, errs, nil
}

func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
return ic.pruneVolumesHelper(ctx)
func (ic *ContainerEngine) VolumePrune(ctx context.Context, options entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
filterFuncs, err := filters.GenerateVolumeFilters(options.Filters)
if err != nil {
return nil, err
}
return ic.pruneVolumesHelper(ctx, filterFuncs)
}

func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) {
pruned, err := ic.Libpod.PruneVolumes(ctx)
func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context, filterFuncs []libpod.VolumeFilter) ([]*entities.VolumePruneReport, error) {
pruned, err := ic.Libpod.PruneVolumes(ctx, filterFuncs)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/domain/infra/tunnel/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
return reports, errs, nil
}

func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
return volumes.Prune(ic.ClientCxt)
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
return volumes.Prune(ic.ClientCxt, (map[string][]string)(opts.Filters))
}

func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) {
Expand Down
Loading

0 comments on commit e5741b9

Please sign in to comment.