diff --git a/libimage/filters.go b/libimage/filters.go index f9f73f527..f8ee66fb6 100644 --- a/libimage/filters.go +++ b/libimage/filters.go @@ -159,8 +159,9 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp filter = filterIntermediate(ctx, intermediate, t) case "label": - filter = filterLabel(ctx, value) - + filter = filterLabel(ctx, value, false) + case "label!": + filter = filterLabel(ctx, value, true) case "readonly": readOnly, err := r.bool(duplicate, key, value) if err != nil { @@ -309,13 +310,13 @@ func filterReferences(value string) filterFunc { } // filterLabel creates a label for matching the specified value. -func filterLabel(ctx context.Context, value string) filterFunc { +func filterLabel(ctx context.Context, value string, inverse bool) filterFunc { return func(img *Image) (bool, error) { labels, err := img.Labels(ctx) if err != nil { return false, err } - return filtersPkg.MatchLabelFilters([]string{value}, labels), nil + return filtersPkg.MatchLabelFilters([]string{value}, labels, inverse), nil } } diff --git a/libnetwork/util/filters.go b/libnetwork/util/filters.go index 58d79d25b..ed5918ad8 100644 --- a/libnetwork/util/filters.go +++ b/libnetwork/util/filters.go @@ -63,9 +63,12 @@ func createPruneFilterFuncs(key string, filterValues []string) (types.FilterFunc case "label": // matches all labels return func(net types.Network) bool { - return filters.MatchLabelFilters(filterValues, net.Labels) + return filters.MatchLabelFilters(filterValues, net.Labels, false) + }, nil + case "label!": + return func(net types.Network) bool { + return filters.MatchLabelFilters(filterValues, net.Labels, true) }, nil - case "until": until, err := filters.ComputeUntilTimestamp(filterValues) if err != nil { diff --git a/pkg/filters/filters.go b/pkg/filters/filters.go index 53650efc9..5e2b6be49 100644 --- a/pkg/filters/filters.go +++ b/pkg/filters/filters.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "path/filepath" "strings" "time" @@ -101,7 +102,7 @@ func PrepareFilters(r *http.Request) (map[string][]string, error) { } // MatchLabelFilters matches labels and returns true if they are valid -func MatchLabelFilters(filterValues []string, labels map[string]string) bool { +func MatchLabelFilters(filterValues []string, labels map[string]string, inverse bool) bool { outer: for _, filterValue := range filterValues { filterArray := strings.SplitN(filterValue, "=", 2) @@ -112,7 +113,9 @@ outer: filterValue = "" } for labelKey, labelValue := range labels { - if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { + if inverse && ((labelKey != filterKey) || !matchPattern(filterKey, labelKey)) && (filterValue == "" || labelValue == filterValue) { + continue outer + } else if (labelKey == filterKey || matchPattern(filterKey, labelKey)) && (filterValue == "" || labelValue == filterValue) { continue outer } } @@ -120,3 +123,14 @@ outer: } return true } + +func matchPattern(pattern string, value string) bool { + if strings.Contains(pattern, "*") { + filter := fmt.Sprintf("*%s*", pattern) + filter = strings.ReplaceAll(filter, string(filepath.Separator), "|") + newName := strings.ReplaceAll(value, string(filepath.Separator), "|") + match, _ := filepath.Match(filter, newName) + return match + } + return false +} diff --git a/pkg/filters/filters_test.go b/pkg/filters/filters_test.go index f43f5c8b8..82669fb7f 100644 --- a/pkg/filters/filters_test.go +++ b/pkg/filters/filters_test.go @@ -71,7 +71,7 @@ func TestMatchLabelFilters(t *testing.T) { 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 { + if got := MatchLabelFilters(tt.args.filterValues, tt.args.labels, false); got != tt.want { t.Errorf("MatchLabelFilters() = %v, want %v", got, tt.want) } })