diff --git a/docs/source/markdown/podman-volume-ls.1.md.in b/docs/source/markdown/podman-volume-ls.1.md.in index 6a45d1161e..aaf9b3818b 100644 --- a/docs/source/markdown/podman-volume-ls.1.md.in +++ b/docs/source/markdown/podman-volume-ls.1.md.in @@ -23,15 +23,16 @@ which is exclusive. Filters with different keys always work exclusive. Volumes can be filtered by the following attributes: -| **Filter** | **Description** | -| ---------- | ------------------------------------------------------------------------------------- | -| dangling | [Dangling] Matches all volumes not referenced by any containers | -| driver | [Driver] Matches volumes based on their driver | -| label | [Key] or [Key=Value] Label assigned to a volume | -| name | [Name] Volume name (accepts regex) | -| opt | Matches a storage driver options | -| scope | Filters volume by scope | -| until | Only remove volumes created before given timestamp | +| **Filter** | **Description** | +| ---------- | ------------------------------------------------------------------------------------- | +| dangling | [Dangling] Matches all volumes not referenced by any containers | +| driver | [Driver] Matches volumes based on their driver | +| label | [Key] or [Key=Value] Label assigned to a volume | +| name | [Name] Volume name (accepts regex) | +| opt | Matches a storage driver options | +| scope | Filters volume by scope | +| after/since | Filter by volumes created after the given VOLUME (name or tag) | +| until | Only remove volumes created before given timestamp | #### **--format**=*format* diff --git a/docs/source/markdown/podman-volume-prune.1.md b/docs/source/markdown/podman-volume-prune.1.md index a30ad3496f..9242a2b7e3 100644 --- a/docs/source/markdown/podman-volume-prune.1.md +++ b/docs/source/markdown/podman-volume-prune.1.md @@ -26,7 +26,8 @@ Supported filters: | Filter | Description | | :----------------: | --------------------------------------------------------------------------- | | *label* | Only remove volumes, with (or without, in the case of label!=[...] is used) the specified labels. | -| *until* | Only remove volumes created before given timestamp. | +| *until* | Only remove volumes created before given timestamp. | +| *after/since* | Filter by volumes created after the given VOLUME (name or tag) | The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*key*=*value*, which removes volumes with the specified labels. The other format is the `label!`=*key* or `label!`=*key*=*value*, which removes volumes without the specified labels. diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index e17a5c3be0..4ba6f22418 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -44,7 +44,7 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { volumeFilters := []libpod.VolumeFilter{} for filter, filterValues := range *filtersMap { - filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues) + filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues, runtime) if err != nil { utils.InternalServerError(w, err) } @@ -276,7 +276,7 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { f := (url.Values)(*filterMap) filterFuncs := []libpod.VolumeFilter{} for filter, filterValues := range f { - filterFunc, err := filters.GeneratePruneVolumeFilters(filter, filterValues) + filterFunc, err := filters.GeneratePruneVolumeFilters(filter, filterValues, runtime) if err != nil { utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse filters for %s: %w", f.Encode(), err)) return diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index bb2bdc3709..589fe71e9a 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -121,7 +121,7 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { volumeFilters := []libpod.VolumeFilter{} for filter, filterValues := range *filterMap { - filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues) + filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues, runtime) if err != nil { utils.InternalServerError(w, err) return @@ -168,7 +168,7 @@ func pruneVolumesHelper(r *http.Request) ([]*reports.PruneReport, error) { f := (url.Values)(*filterMap) filterFuncs := []libpod.VolumeFilter{} for filter, filterValues := range f { - filterFunc, err := filters.GeneratePruneVolumeFilters(filter, filterValues) + filterFunc, err := filters.GeneratePruneVolumeFilters(filter, filterValues, runtime) if err != nil { return nil, err } diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 45edd2a84c..4c534e44cf 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -3,14 +3,17 @@ package filters import ( "fmt" "strings" + "time" "github.com/containers/common/pkg/filters" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/util" ) -func GenerateVolumeFilters(filter string, filterValues []string) (libpod.VolumeFilter, error) { +func GenerateVolumeFilters(filter string, filterValues []string, runtime *libpod.Runtime) (libpod.VolumeFilter, error) { switch filter { + case "after", "since": + return createAfterFilterVolumeFunction(filterValues, runtime) case "name": return func(v *libpod.Volume) bool { return util.StringMatchRegexSlice(v.Name(), filterValues) @@ -98,8 +101,10 @@ func GenerateVolumeFilters(filter string, filterValues []string) (libpod.VolumeF return nil, fmt.Errorf("%q is an invalid volume filter", filter) } -func GeneratePruneVolumeFilters(filter string, filterValues []string) (libpod.VolumeFilter, error) { +func GeneratePruneVolumeFilters(filter string, filterValues []string, runtime *libpod.Runtime) (libpod.VolumeFilter, error) { switch filter { + case "after", "since": + return createAfterFilterVolumeFunction(filterValues, runtime) case "label": return func(v *libpod.Volume) bool { return filters.MatchLabelFilters(filterValues, v.Labels()) @@ -126,3 +131,19 @@ func createUntilFilterVolumeFunction(filterValues []string) (libpod.VolumeFilter return false }, nil } + +func createAfterFilterVolumeFunction(filterValues []string, runtime *libpod.Runtime) (libpod.VolumeFilter, error) { + var createTime time.Time + for _, filterValue := range filterValues { + vol, err := runtime.LookupVolume(filterValue) + if err != nil { + return nil, err + } + if createTime.IsZero() || createTime.After(vol.CreatedTime()) { + createTime = vol.CreatedTime() + } + } + return func(v *libpod.Volume) bool { + return createTime.Before(v.CreatedTime()) + }, nil +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index cd58acb284..3588716011 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -124,7 +124,7 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin func (ic *ContainerEngine) VolumePrune(ctx context.Context, options entities.VolumePruneOptions) ([]*reports.PruneReport, error) { funcs := []libpod.VolumeFilter{} for filter, filterValues := range options.Filters { - filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues) + filterFunc, err := filters.GenerateVolumeFilters(filter, filterValues, ic.Libpod) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context, filterFuncs [ func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { volumeFilters := []libpod.VolumeFilter{} for filter, value := range opts.Filter { - filterFunc, err := filters.GenerateVolumeFilters(filter, value) + filterFunc, err := filters.GenerateVolumeFilters(filter, value, ic.Libpod) if err != nil { return nil, err } diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 2bdbe1798a..5adf6c43bf 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -211,4 +211,34 @@ var _ = Describe("Podman volume ls", func() { Expect(session.OutputToStringArray()).To(HaveLen(1)) Expect(session.OutputToStringArray()[0]).To(Equal(vol3Name)) }) + + It("podman ls volume with --filter since/after", func() { + vol1 := "vol1" + vol2 := "vol2" + vol3 := "vol3" + + session := podmanTest.Podman([]string{"volume", "create", vol1}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "create", vol2}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "create", vol3}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "ls", "-q", "--filter", "since=" + vol1}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToStringArray()).To(HaveLen(2)) + Expect(session.OutputToStringArray()[0]).To(Equal(vol2)) + Expect(session.OutputToStringArray()[1]).To(Equal(vol3)) + + session = podmanTest.Podman([]string{"volume", "ls", "-q", "--filter", "after=" + vol1}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToStringArray()).To(HaveLen(2)) + Expect(session.OutputToStringArray()[0]).To(Equal(vol2)) + Expect(session.OutputToStringArray()[1]).To(Equal(vol3)) + }) }) diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index 7089277c1c..0bf06147fd 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -163,4 +163,31 @@ var _ = Describe("Podman volume prune", func() { Expect(session).Should(Exit(0)) Expect(session.OutputToStringArray()).To(BeEmpty()) }) + + It("podman volume prune --filter since/after", func() { + vol1 := "vol1" + vol2 := "vol2" + vol3 := "vol3" + + session := podmanTest.Podman([]string{"volume", "create", vol1}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "create", vol2}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "create", vol3}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "prune", "-f", "--filter", "since=" + vol1}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "ls", "-q"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToStringArray()).To(HaveLen(1)) + Expect(session.OutputToStringArray()[0]).To(Equal(vol1)) + }) })