mirror of
https://github.com/containers/podman.git
synced 2025-06-29 23:22:40 +08:00
Unification of label filter across list/prune endpoints
Signed-off-by: Jakub Guzik <jakubmguzik@gmail.com>
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/pkg/inspect"
|
"github.com/containers/podman/v3/pkg/inspect"
|
||||||
|
"github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -78,23 +79,14 @@ func ReadOnlyFilter(readOnly bool) ResultFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LabelFilter allows you to filter by images labels key and/or value
|
// LabelFilter allows you to filter by images labels key and/or value
|
||||||
func LabelFilter(ctx context.Context, labelfilter string) ResultFilter {
|
func LabelFilter(ctx context.Context, filter string) ResultFilter {
|
||||||
// We need to handle both label=key and label=key=value
|
// We need to handle both label=key and label=key=value
|
||||||
return func(i *Image) bool {
|
return func(i *Image) bool {
|
||||||
var value string
|
|
||||||
splitFilter := strings.SplitN(labelfilter, "=", 2)
|
|
||||||
key := splitFilter[0]
|
|
||||||
if len(splitFilter) > 1 {
|
|
||||||
value = splitFilter[1]
|
|
||||||
}
|
|
||||||
labels, err := i.Labels(ctx)
|
labels, err := i.Labels(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(strings.TrimSpace(labels[key])) > 0 && len(strings.TrimSpace(value)) == 0 {
|
return util.MatchLabelFilters([]string{filter}, labels)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return labels[key] == value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/containers/podman/v3/libpod/events"
|
"github.com/containers/podman/v3/libpod/events"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities/reports"
|
"github.com/containers/podman/v3/pkg/domain/entities/reports"
|
||||||
"github.com/containers/podman/v3/pkg/timetype"
|
"github.com/containers/podman/v3/pkg/timetype"
|
||||||
|
"github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -16,24 +17,12 @@ import (
|
|||||||
func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
|
func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
|
||||||
switch filter {
|
switch filter {
|
||||||
case "label":
|
case "label":
|
||||||
var filterArray = strings.SplitN(filterValue, "=", 2)
|
|
||||||
var filterKey = filterArray[0]
|
|
||||||
if len(filterArray) > 1 {
|
|
||||||
filterValue = filterArray[1]
|
|
||||||
} else {
|
|
||||||
filterValue = ""
|
|
||||||
}
|
|
||||||
return func(i *Image) bool {
|
return func(i *Image) bool {
|
||||||
labels, err := i.Labels(context.Background())
|
labels, err := i.Labels(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for labelKey, labelValue := range labels {
|
return util.MatchLabelFilters([]string{filterValue}, labels)
|
||||||
if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
case "until":
|
case "until":
|
||||||
|
@ -225,7 +225,7 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri
|
|||||||
|
|
||||||
case "label":
|
case "label":
|
||||||
// matches all labels
|
// matches all labels
|
||||||
result = matchPruneLabelFilters(netconf, filterValues)
|
result = util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf))
|
||||||
|
|
||||||
case "driver":
|
case "driver":
|
||||||
// matches only for the DefaultNetworkDriver
|
// matches only for the DefaultNetworkDriver
|
||||||
@ -260,7 +260,7 @@ func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigLis
|
|||||||
for key, filterValues := range f {
|
for key, filterValues := range f {
|
||||||
switch strings.ToLower(key) {
|
switch strings.ToLower(key) {
|
||||||
case "label":
|
case "label":
|
||||||
return matchPruneLabelFilters(netconf, filterValues), nil
|
return util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)), nil
|
||||||
case "until":
|
case "until":
|
||||||
until, err := util.ComputeUntilTimestamp(key, filterValues)
|
until, err := util.ComputeUntilTimestamp(key, filterValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -280,29 +280,6 @@ func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigLis
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchPruneLabelFilters(netconf *libcni.NetworkConfigList, filterValues []string) bool {
|
|
||||||
labels := GetNetworkLabels(netconf)
|
|
||||||
result := true
|
|
||||||
outer:
|
|
||||||
for _, filterValue := range filterValues {
|
|
||||||
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) {
|
|
||||||
result = true
|
|
||||||
continue outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) {
|
func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) {
|
||||||
networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name)
|
networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,27 +23,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
|
|||||||
case "label":
|
case "label":
|
||||||
// we have to match that all given labels exits on that container
|
// we have to match that all given labels exits on that container
|
||||||
return func(c *libpod.Container) bool {
|
return func(c *libpod.Container) bool {
|
||||||
labels := c.Labels()
|
return util.MatchLabelFilters(filterValues, c.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
|
}, nil
|
||||||
case "name":
|
case "name":
|
||||||
// we only have to match one name
|
// we only have to match one name
|
||||||
|
@ -114,26 +114,7 @@ func GeneratePodFilterFunc(filter string, filterValues []string) (
|
|||||||
case "label":
|
case "label":
|
||||||
return func(p *libpod.Pod) bool {
|
return func(p *libpod.Pod) bool {
|
||||||
labels := p.Labels()
|
labels := p.Labels()
|
||||||
for _, filterValue := range filterValues {
|
return util.MatchLabelFilters(filterValues, labels)
|
||||||
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
|
}, nil
|
||||||
case "network":
|
case "network":
|
||||||
return func(p *libpod.Pod) bool {
|
return func(p *libpod.Pod) bool {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/libpod"
|
"github.com/containers/podman/v3/libpod"
|
||||||
|
"github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,21 +30,9 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) {
|
|||||||
return v.Scope() == scopeVal
|
return v.Scope() == scopeVal
|
||||||
})
|
})
|
||||||
case "label":
|
case "label":
|
||||||
filterArray := strings.SplitN(val, "=", 2)
|
filter := val
|
||||||
filterKey := filterArray[0]
|
|
||||||
var filterVal string
|
|
||||||
if len(filterArray) > 1 {
|
|
||||||
filterVal = filterArray[1]
|
|
||||||
} else {
|
|
||||||
filterVal = ""
|
|
||||||
}
|
|
||||||
vf = append(vf, func(v *libpod.Volume) bool {
|
vf = append(vf, func(v *libpod.Volume) bool {
|
||||||
for labelKey, labelValue := range v.Labels() {
|
return util.MatchLabelFilters([]string{filter}, v.Labels())
|
||||||
if labelKey == filterKey && (filterVal == "" || labelValue == filterVal) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
case "opt":
|
case "opt":
|
||||||
filterArray := strings.SplitN(val, "=", 2)
|
filterArray := strings.SplitN(val, "=", 2)
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ComputeUntilTimestamp extracts unitil timestamp from filters
|
// ComputeUntilTimestamp extracts until timestamp from filters
|
||||||
func ComputeUntilTimestamp(filter string, filterValues []string) (time.Time, error) {
|
func ComputeUntilTimestamp(filter string, filterValues []string) (time.Time, error) {
|
||||||
invalid := time.Time{}
|
invalid := time.Time{}
|
||||||
if len(filterValues) != 1 {
|
if len(filterValues) != 1 {
|
||||||
@ -93,3 +93,24 @@ func PrepareFilters(r *http.Request) (*map[string][]string, error) {
|
|||||||
}
|
}
|
||||||
return &filterMap, nil
|
return &filterMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchLabelFilters matches labels and returs true if they are valid
|
||||||
|
func MatchLabelFilters(filterValues []string, labels map[string]string) bool {
|
||||||
|
outer:
|
||||||
|
for _, filterValue := range filterValues {
|
||||||
|
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) {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
77
pkg/util/filters_test.go
Normal file
77
pkg/util/filters_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMatchLabelFilters(t *testing.T) {
|
||||||
|
testLabels := map[string]string{
|
||||||
|
"label1": "",
|
||||||
|
"label2": "test",
|
||||||
|
"label3": "",
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
filterValues []string
|
||||||
|
labels map[string]string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Match when all filters the same as labels",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label1", "label3", "label2=test"},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Match when filter value not provided in args",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label2"},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Match when no filter value is given",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label2="},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Do not match when filter value differs",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label2=differs"},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Do not match when filter value not listed in labels",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label1=xyz"},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Do not match when one from many not ok",
|
||||||
|
args: args{
|
||||||
|
filterValues: []string{"label1=xyz", "invalid=valid"},
|
||||||
|
labels: testLabels,
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := MatchLabelFilters(tt.args.filterValues, tt.args.labels); got != tt.want {
|
||||||
|
t.Errorf("MatchLabelFilters() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user