Filter pods through pod list api

Refactored current filter pods flow through the shared pod functions
so filter pod functionalities can be shared between api and cmd.

Signed-off-by: Sujil02 <sushah@redhat.com>
This commit is contained in:
Sujil02
2020-03-09 20:07:55 -04:00
parent f3a28de324
commit 15326f051d
8 changed files with 268 additions and 146 deletions

View File

@ -4,7 +4,6 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
@ -13,7 +12,6 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/pkg/util"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -29,8 +27,6 @@ const (
NUM_CTR_INFO = 10
)
type PodFilter func(*adapter.Pod) bool
var (
bc_opts shared.PsOptions
)
@ -174,29 +170,23 @@ func podPsCmd(c *cliconfig.PodPsValues) error {
opts.Format = genPodPsFormat(c)
var filterFuncs []PodFilter
if c.Filter != "" {
filters := strings.Split(c.Filter, ",")
for _, f := range filters {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1])
if err != nil {
return errors.Wrapf(err, "invalid filter")
}
filterFuncs = append(filterFuncs, generatedFunc)
}
}
var pods []*adapter.Pod
// If latest is set true filters are ignored.
if c.Latest {
pod, err := runtime.GetLatestPod()
if err != nil {
return err
}
pods = append(pods, pod)
return generatePodPsOutput(pods, opts)
}
if c.Filter != "" {
pods, err = runtime.GetPodsWithFilters(c.Filter)
if err != nil {
return err
}
} else {
pods, err = runtime.GetAllPods()
if err != nil {
@ -204,19 +194,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error {
}
}
podsFiltered := make([]*adapter.Pod, 0, len(pods))
for _, pod := range pods {
include := true
for _, filter := range filterFuncs {
include = include && filter(pod)
}
if include {
podsFiltered = append(podsFiltered, pod)
}
}
return generatePodPsOutput(podsFiltered, opts)
return generatePodPsOutput(pods, opts)
}
// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together
@ -235,88 +213,6 @@ func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error {
return nil
}
func generatePodFilterFuncs(filter, filterValue string) (func(pod *adapter.Pod) bool, error) {
switch filter {
case "ctr-ids":
return func(p *adapter.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
return util.StringInSlice(filterValue, ctrIds)
}, nil
case "ctr-names":
return func(p *adapter.Pod) bool {
ctrs, err := p.AllContainers()
if err != nil {
return false
}
for _, ctr := range ctrs {
if filterValue == ctr.Name() {
return true
}
}
return false
}, nil
case "ctr-number":
return func(p *adapter.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
fVint, err2 := strconv.Atoi(filterValue)
if err2 != nil {
return false
}
return len(ctrIds) == fVint
}, nil
case "ctr-status":
if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) {
return nil, errors.Errorf("%s is not a valid status", filterValue)
}
return func(p *adapter.Pod) bool {
ctr_statuses, err := p.Status()
if err != nil {
return false
}
for _, ctr_status := range ctr_statuses {
state := ctr_status.String()
if ctr_status == define.ContainerStateConfigured {
state = "created"
}
if state == filterValue {
return true
}
}
return false
}, nil
case "id":
return func(p *adapter.Pod) bool {
return strings.Contains(p.ID(), filterValue)
}, nil
case "name":
return func(p *adapter.Pod) bool {
return strings.Contains(p.Name(), filterValue)
}, nil
case "status":
if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
return nil, errors.Errorf("%s is not a valid pod status", filterValue)
}
return func(p *adapter.Pod) bool {
status, err := p.GetPodStatus()
if err != nil {
return false
}
if strings.ToLower(status) == filterValue {
return true
}
return false
}, nil
}
return nil, errors.Errorf("%s is an invalid filter", filter)
}
// generate the template based on conditions given
func genPodPsFormat(c *cliconfig.PodPsValues) string {
format := ""

View File

@ -2,9 +2,11 @@ package shared
import (
"strconv"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/util"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
@ -134,4 +136,128 @@ func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) {
return portBindings, nil
}
// GetPodsWithFilters uses the cliconfig to categorize if the latest pod is required.
func GetPodsWithFilters(r *libpod.Runtime, filters string) ([]*libpod.Pod, error) {
filterFuncs, err := GenerateFilterFunction(r, strings.Split(filters, ","))
if err != nil {
return nil, err
}
return FilterAllPodsWithFilterFunc(r, filterFuncs...)
}
// FilterAllPodsWithFilterFunc retrieves all pods
// Filters can be provided which will determine which pods are included in the
// output. Multiple filters are handled by ANDing their output, so only pods
// matching all filters are returned
func FilterAllPodsWithFilterFunc(r *libpod.Runtime, filters ...libpod.PodFilter) ([]*libpod.Pod, error) {
pods, err := r.Pods(filters...)
if err != nil {
return nil, err
}
return pods, nil
}
// GenerateFilterFunction basically gets the filters based on the input by the user
// and filter the pod list based on the criteria.
func GenerateFilterFunction(r *libpod.Runtime, filters []string) ([]libpod.PodFilter, error) {
var filterFuncs []libpod.PodFilter
for _, f := range filters {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1])
if err != nil {
return nil, errors.Wrapf(err, "invalid filter")
}
filterFuncs = append(filterFuncs, generatedFunc)
}
return filterFuncs, nil
}
func generatePodFilterFuncs(filter, filterValue string) (
func(pod *libpod.Pod) bool, error) {
switch filter {
case "ctr-ids":
return func(p *libpod.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
return util.StringInSlice(filterValue, ctrIds)
}, nil
case "ctr-names":
return func(p *libpod.Pod) bool {
ctrs, err := p.AllContainers()
if err != nil {
return false
}
for _, ctr := range ctrs {
if filterValue == ctr.Name() {
return true
}
}
return false
}, nil
case "ctr-number":
return func(p *libpod.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
fVint, err2 := strconv.Atoi(filterValue)
if err2 != nil {
return false
}
return len(ctrIds) == fVint
}, nil
case "ctr-status":
if !util.StringInSlice(filterValue,
[]string{"created", "restarting", "running", "paused",
"exited", "unknown"}) {
return nil, errors.Errorf("%s is not a valid status", filterValue)
}
return func(p *libpod.Pod) bool {
ctr_statuses, err := p.Status()
if err != nil {
return false
}
for _, ctr_status := range ctr_statuses {
state := ctr_status.String()
if ctr_status == define.ContainerStateConfigured {
state = "created"
}
if state == filterValue {
return true
}
}
return false
}, nil
case "id":
return func(p *libpod.Pod) bool {
return strings.Contains(p.ID(), filterValue)
}, nil
case "name":
return func(p *libpod.Pod) bool {
return strings.Contains(p.Name(), filterValue)
}, nil
case "status":
if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
return nil, errors.Errorf("%s is not a valid pod status", filterValue)
}
return func(p *libpod.Pod) bool {
status, err := p.GetPodStatus()
if err != nil {
return false
}
if strings.ToLower(status) == filterValue {
return true
}
return false
}, nil
}
return nil, errors.Errorf("%s is an invalid filter", filter)
}
var DefaultKernelNamespaces = "cgroup,ipc,net,uts"

View File

@ -90,18 +90,10 @@ func (r *Runtime) LookupPod(idOrName string) (*Pod, error) {
// output. Multiple filters are handled by ANDing their output, so only pods
// matching all filters are returned
func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil, define.ErrRuntimeStopped
}
pods, err := r.state.AllPods()
pods, err := r.GetAllPods()
if err != nil {
return nil, err
}
podsFiltered := make([]*Pod, 0, len(pods))
for _, pod := range pods {
include := true

View File

@ -122,19 +122,31 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) {
return &pod, err
}
// GetPodsWithFilters gets the filtered list of pods based on the filter parameters provided.
func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) {
pods, err := shared.GetPodsWithFilters(r.Runtime, filters)
if err != nil {
return nil, err
}
return r.podstoAdapterPods(pods)
}
func (r *LocalRuntime) podstoAdapterPods(pod []*libpod.Pod) ([]*Pod, error) {
var pods []*Pod
for _, i := range pod {
pods = append(pods, &Pod{i})
}
return pods, nil
}
// GetAllPods gets all pods and wraps it in an adapter pod
func (r *LocalRuntime) GetAllPods() ([]*Pod, error) {
var pods []*Pod
allPods, err := r.Runtime.GetAllPods()
if err != nil {
return nil, err
}
for _, p := range allPods {
pod := Pod{}
pod.Pod = p
pods = append(pods, &pod)
}
return pods, nil
return r.podstoAdapterPods(allPods)
}
// LookupPod gets a pod by name or id and wraps it in an adapter pod

View File

@ -10,7 +10,7 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/varlinkapi"
@ -208,6 +208,11 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) {
return pods, nil
}
// This is a empty implementation stating remoteclient not yet implemented
func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) {
return nil, define.ErrNotImplemented
}
// GetPodsByStatus returns a slice of pods filtered by a libpod status
func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) {
podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses)

View File

@ -103,7 +103,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
func Pods(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
podInspectData []*libpod.PodInspect
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@ -118,12 +117,8 @@ func Pods(w http.ResponseWriter, r *http.Request) {
return
}
if len(query.Filters) > 0 {
utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
return
}
pods, err := utils.GetPods(w, r)
pods, err := runtime.GetAllPods()
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return

View File

@ -0,0 +1,45 @@
package utils
import (
"fmt"
"net/http"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/gorilla/schema"
)
func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool
Filters map[string][]string `schema:"filters"`
Digests bool
}{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
return nil, err
}
var filters = []string{}
if _, found := r.URL.Query()["digests"]; found && query.Digests {
UnSupportedParameter("digests")
}
if len(query.Filters) > 0 {
for k, v := range query.Filters {
for _, val := range v {
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
filterFuncs, err := shared.GenerateFilterFunction(runtime, filters)
if err != nil {
return nil, err
}
return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...)
}
return runtime.GetAllPods()
}

View File

@ -76,15 +76,66 @@ var _ = Describe("Podman pods", func() {
}
Expect(StringInSlice(newpod, names)).To(BeTrue())
Expect(StringInSlice("newpod2", names)).To(BeTrue())
})
// TODO not working Because: code to list based on filter
// "not yet implemented",
// Validate list pod with filters
//filters := make(map[string][]string)
//filters["name"] = []string{newpod}
//filteredPods, err := pods.List(bt.conn, filters)
//Expect(err).To(BeNil())
//Expect(len(filteredPods)).To(BeNumerically("==", 1))
// The test validates the list pod endpoint with passing filters as the params.
It("List pods with filters", func() {
var newpod2 string = "newpod2"
bt.Podcreate(&newpod2)
_, err = bt.RunTopContainer(nil, &trueFlag, &newpod)
Expect(err).To(BeNil())
// Expected err with invalid filter params
filters := make(map[string][]string)
filters["dummy"] = []string{"dummy"}
filteredPods, err := pods.List(bt.conn, filters)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
// Expected empty response with invalid filters
filters = make(map[string][]string)
filters["name"] = []string{"dummy"}
filteredPods, err = pods.List(bt.conn, filters)
Expect(err).To(BeNil())
Expect(len(filteredPods)).To(BeNumerically("==", 0))
// Validate list pod with name filter
filters = make(map[string][]string)
filters["name"] = []string{newpod2}
filteredPods, err = pods.List(bt.conn, filters)
Expect(err).To(BeNil())
Expect(len(filteredPods)).To(BeNumerically("==", 1))
var names []string
for _, i := range filteredPods {
names = append(names, i.Config.Name)
}
Expect(StringInSlice("newpod2", names)).To(BeTrue())
// Validate list pod with id filter
filters = make(map[string][]string)
response, err := pods.Inspect(bt.conn, newpod)
id := response.Config.ID
filters["id"] = []string{id}
filteredPods, err = pods.List(bt.conn, filters)
Expect(err).To(BeNil())
Expect(len(filteredPods)).To(BeNumerically("==", 1))
names = names[:0]
for _, i := range filteredPods {
names = append(names, i.Config.Name)
}
Expect(StringInSlice("newpod", names)).To(BeTrue())
// Using multiple filters
filters["name"] = []string{newpod}
filteredPods, err = pods.List(bt.conn, filters)
Expect(err).To(BeNil())
Expect(len(filteredPods)).To(BeNumerically("==", 1))
names = names[:0]
for _, i := range filteredPods {
names = append(names, i.Config.Name)
}
Expect(StringInSlice("newpod", names)).To(BeTrue())
})
// The test validates if the exists responds