Align the podman pod ps --filter behavior with podman ps

Filters with the same key work inclusive with the only exception being
`label` which is exclusive. Filters with different keys always work exclusive.

Also update the documentation with the new behavior.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger 2020-11-18 11:23:30 +01:00
parent 6ece1d97c4
commit e7fd9234cd
6 changed files with 117 additions and 75 deletions

View File

@ -80,17 +80,21 @@ Default: created
#### **--filter**, **-f**=*filter* #### **--filter**, **-f**=*filter*
Filter output based on conditions given Filter output based on conditions given.
Multiple filters can be given with multiple uses of the --filter flag.
Filters with the same key work inclusive with the only exception being
`label` which is exclusive. Filters with different keys always work exclusive.
Valid filters are listed below: Valid filters are listed below:
| **Filter** | **Description** | | **Filter** | **Description** |
| --------------- | ------------------------------------------------------------------- | | ---------- | ------------------------------------------------------------------------------------- |
| id | [ID] Pod's ID | | id | [ID] Pod's ID (accepts regex) |
| name | [Name] Pod's name | | name | [Name] Pod's name (accepts regex) |
| label | [Key] or [Key=Value] Label assigned to a container | | label | [Key] or [Key=Value] Label assigned to a container |
| ctr-names | Container name within the pod | | status | Pod's status: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded` |
| ctr-ids | Container ID within the pod | | ctr-names | Container name within the pod (accepts regex) |
| ctr-ids | Container ID within the pod (accepts regex) |
| ctr-status | Container status within the pod | | ctr-status | Container status within the pod |
| ctr-number | Number of containers in the pod | | ctr-number | Number of containers in the pod |

View File

@ -44,15 +44,15 @@ Display external containers that are not controlled by Podman but are stored in
Filter what containers are shown in the output. Filter what containers are shown in the output.
Multiple filters can be given with multiple uses of the --filter flag. Multiple filters can be given with multiple uses of the --filter flag.
If multiple filters are given, only containers which match all of the given filters will be shown. Filters with the same key work inclusive with the only exception being
Results will be drawn from all containers, regardless of whether --all was given. `label` which is exclusive. Filters with different keys always work exclusive.
Valid filters are listed below: Valid filters are listed below:
| **Filter** | **Description** | | **Filter** | **Description** |
| --------------- | -------------------------------------------------------------------------------- | | --------------- | -------------------------------------------------------------------------------- |
| id | [ID] Container's ID | | id | [ID] Container's ID (accepts regex) |
| name | [Name] Container's name | | name | [Name] Container's name (accepts regex) |
| label | [Key] or [Key=Value] Label assigned to a container | | label | [Key] or [Key=Value] Label assigned to a container |
| exited | [Int] Container's exit code | | exited | [Int] Container's exit code |
| status | [Status] Container's status: 'created', 'exited', 'paused', 'running', 'unknown' | | status | [Status] Container's status: 'created', 'exited', 'paused', 'running', 'unknown' |

View File

@ -1,7 +1,6 @@
package lpfilters package lpfilters
import ( import (
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -9,13 +8,12 @@ import (
"github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/util" "github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
// GeneratePodFilterFunc takes a filter and filtervalue (key, value) // GeneratePodFilterFunc takes a filter and filtervalue (key, value)
// and generates a libpod function that can be used to filter // and generates a libpod function that can be used to filter
// pods // pods
func GeneratePodFilterFunc(filter, filterValue string) ( func GeneratePodFilterFunc(filter string, filterValues []string) (
func(pod *libpod.Pod) bool, error) { func(pod *libpod.Pod) bool, error) {
switch filter { switch filter {
case "ctr-ids": case "ctr-ids":
@ -24,7 +22,10 @@ func GeneratePodFilterFunc(filter, filterValue string) (
if err != nil { if err != nil {
return false return false
} }
return util.StringInSlice(filterValue, ctrIds) for _, id := range ctrIds {
return util.StringMatchRegexSlice(id, filterValues)
}
return false
}, nil }, nil
case "ctr-names": case "ctr-names":
return func(p *libpod.Pod) bool { return func(p *libpod.Pod) bool {
@ -33,9 +34,7 @@ func GeneratePodFilterFunc(filter, filterValue string) (
return false return false
} }
for _, ctr := range ctrs { for _, ctr := range ctrs {
if filterValue == ctr.Name() { return util.StringMatchRegexSlice(ctr.Name(), filterValues)
return true
}
} }
return false return false
}, nil }, nil
@ -45,19 +44,23 @@ func GeneratePodFilterFunc(filter, filterValue string) (
if err != nil { if err != nil {
return false return false
} }
for _, filterValue := range filterValues {
fVint, err2 := strconv.Atoi(filterValue) fVint, err2 := strconv.Atoi(filterValue)
if err2 != nil { if err2 != nil {
return false return false
} }
return len(ctrIds) == fVint if len(ctrIds) == fVint {
return true
}
}
return false
}, nil }, nil
case "ctr-status": case "ctr-status":
if !util.StringInSlice(filterValue, for _, filterValue := range filterValues {
[]string{"created", "restarting", "running", "paused", if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) {
"exited", "unknown"}) {
return nil, errors.Errorf("%s is not a valid status", filterValue) return nil, errors.Errorf("%s is not a valid status", filterValue)
} }
}
return func(p *libpod.Pod) bool { return func(p *libpod.Pod) bool {
ctrStatuses, err := p.Status() ctrStatuses, err := p.Status()
if err != nil { if err != nil {
@ -67,55 +70,69 @@ func GeneratePodFilterFunc(filter, filterValue string) (
state := ctrStatus.String() state := ctrStatus.String()
if ctrStatus == define.ContainerStateConfigured { if ctrStatus == define.ContainerStateConfigured {
state = "created" state = "created"
} else if ctrStatus == define.ContainerStateStopped {
state = "exited"
}
for _, filterValue := range filterValues {
if filterValue == "stopped" {
filterValue = "exited"
} }
if state == filterValue { if state == filterValue {
return true return true
} }
} }
}
return false return false
}, nil }, nil
case "id": case "id":
return func(p *libpod.Pod) bool { return func(p *libpod.Pod) bool {
return strings.Contains(p.ID(), filterValue) return util.StringMatchRegexSlice(p.ID(), filterValues)
}, nil }, nil
case "name": case "name":
return func(p *libpod.Pod) bool { return func(p *libpod.Pod) bool {
match, err := regexp.MatchString(filterValue, p.Name()) return util.StringMatchRegexSlice(p.Name(), filterValues)
if err != nil {
logrus.Errorf("Failed to compile regex for 'name' filter: %v", err)
return false
}
return match
}, nil }, nil
case "status": case "status":
for _, filterValue := range filterValues {
if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}) { if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}) {
return nil, errors.Errorf("%s is not a valid pod status", filterValue) return nil, errors.Errorf("%s is not a valid pod status", filterValue)
} }
}
return func(p *libpod.Pod) bool { return func(p *libpod.Pod) bool {
status, err := p.GetPodStatus() status, err := p.GetPodStatus()
if err != nil { if err != nil {
return false return false
} }
for _, filterValue := range filterValues {
if strings.ToLower(status) == filterValue { if strings.ToLower(status) == filterValue {
return true return true
} }
}
return false return false
}, nil }, nil
case "label": case "label":
var filterArray = strings.SplitN(filterValue, "=", 2) return func(p *libpod.Pod) bool {
var filterKey = filterArray[0] labels := p.Labels()
for _, filterValue := range filterValues {
matched := false
filterArray := strings.SplitN(filterValue, "=", 2)
filterKey := filterArray[0]
if len(filterArray) > 1 { if len(filterArray) > 1 {
filterValue = filterArray[1] filterValue = filterArray[1]
} else { } else {
filterValue = "" filterValue = ""
} }
return func(p *libpod.Pod) bool { for labelKey, labelValue := range labels {
for labelKey, labelValue := range p.Labels() {
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
return true matched = true
break
} }
} }
if !matched {
return false return false
}
}
return true
}, nil }, nil
} }
return nil, errors.Errorf("%s is an invalid filter", filter) return nil, errors.Errorf("%s is an invalid filter", filter)

View File

@ -12,7 +12,6 @@ import (
func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport, error) { func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport, error) {
var ( var (
pods []*libpod.Pod pods []*libpod.Pod
filters []libpod.PodFilter
) )
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
@ -30,15 +29,14 @@ func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport
UnSupportedParameter("digests") UnSupportedParameter("digests")
} }
filters := make([]libpod.PodFilter, 0, len(query.Filters))
for k, v := range query.Filters { for k, v := range query.Filters {
for _, filter := range v { f, err := lpfilters.GeneratePodFilterFunc(k, v)
f, err := lpfilters.GeneratePodFilterFunc(k, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
filters = append(filters, f) filters = append(filters, f)
} }
}
pods, err := runtime.Pods(filters...) pods, err := runtime.Pods(filters...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -283,19 +283,16 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp
func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) {
var ( var (
err error err error
filters = []libpod.PodFilter{}
pds = []*libpod.Pod{} pds = []*libpod.Pod{}
) )
filters := make([]libpod.PodFilter, 0, len(options.Filters))
for k, v := range options.Filters { for k, v := range options.Filters {
for _, filter := range v { f, err := lpfilters.GeneratePodFilterFunc(k, v)
f, err := lpfilters.GeneratePodFilterFunc(k, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
filters = append(filters, f) filters = append(filters, f)
}
} }
if options.Latest { if options.Latest {
pod, err := ic.Libpod.GetLatestPod() pod, err := ic.Libpod.GetLatestPod()

View File

@ -194,12 +194,24 @@ var _ = Describe("Podman ps", func() {
Expect(session.OutputToString()).To(ContainSubstring(podid1)) Expect(session.OutputToString()).To(ContainSubstring(podid1))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid2))) Expect(session.OutputToString()).To(Not(ContainSubstring(podid2)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-names=test", "--filter", "ctr-status=running"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(podid1))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid2)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", fmt.Sprintf("ctr-ids=%s", cid)}) session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", fmt.Sprintf("ctr-ids=%s", cid)})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(podid2)) Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid1))) Expect(session.OutputToString()).To(Not(ContainSubstring(podid1)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-ids=" + cid[:40], "--filter", "ctr-ids=" + cid + "$"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid1)))
_, ec3, podid3 := podmanTest.CreatePod("") _, ec3, podid3 := podmanTest.CreatePod("")
Expect(ec3).To(Equal(0)) Expect(ec3).To(Equal(0))
@ -210,6 +222,13 @@ var _ = Describe("Podman ps", func() {
Expect(session.OutputToString()).To(ContainSubstring(podid2)) Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid3))) Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-number=1", "--filter", "ctr-number=0"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(podid1))
Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(ContainSubstring(podid3))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=running"}) session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=running"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
@ -224,6 +243,13 @@ var _ = Describe("Podman ps", func() {
Expect(session.OutputToString()).To(Not(ContainSubstring(podid1))) Expect(session.OutputToString()).To(Not(ContainSubstring(podid1)))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid3))) Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=exited", "--filter", "ctr-status=running"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(podid1))
Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=created"}) session = podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc", "--filter", "ctr-status=created"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))