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,19 +80,23 @@ 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-status | Container status within the pod | | ctr-ids | Container ID within the pod (accepts regex) |
| ctr-number | Number of containers in the pod | | ctr-status | Container status within the pod |
| ctr-number | Number of containers in the pod |
#### **--help**, **-h** #### **--help**, **-h**

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,18 +44,22 @@ 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
}
if len(ctrIds) == fVint {
return true
}
} }
return len(ctrIds) == fVint 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()
@ -67,56 +70,70 @@ 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"
} }
if state == filterValue { for _, filterValue := range filterValues {
return true if filterValue == "stopped" {
filterValue = "exited"
}
if state == filterValue {
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":
if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}) { for _, filterValue := range filterValues {
return nil, errors.Errorf("%s is not a valid pod status", filterValue) 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 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
} }
if strings.ToLower(status) == filterValue { for _, filterValue := range filterValues {
return true if strings.ToLower(status) == filterValue {
}
return false
}, nil
case "label":
var filterArray = strings.SplitN(filterValue, "=", 2)
var filterKey = filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
return func(p *libpod.Pod) bool {
for labelKey, labelValue := range p.Labels() {
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
return true return true
} }
} }
return false return false
}, nil }, nil
case "label":
return func(p *libpod.Pod) bool {
labels := p.Labels()
for _, filterValue := range filterValues {
matched := false
filterArray := strings.SplitN(filterValue, "=", 2)
filterKey := filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
for labelKey, labelValue := range labels {
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
matched = true
break
}
}
if !matched {
return false
}
}
return true
}, nil
} }
return nil, errors.Errorf("%s is an invalid filter", filter) return nil, errors.Errorf("%s is an invalid filter", filter)
} }

View File

@ -11,8 +11,7 @@ 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,14 +29,13 @@ 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 {

View File

@ -282,20 +282,17 @@ 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))