From 06a2b4e5e0eed2065c2273fd90f4f434610f5fb3 Mon Sep 17 00:00:00 2001 From: Yonah Dissen <47282577+yonahd@users.noreply.github.com> Date: Sun, 24 Mar 2024 19:59:39 +0200 Subject: [PATCH] Fix: exclude multi (#228) * FIX: support multi exclude * FIX: support multi exclude --------- Co-authored-by: Yonah Dissen --- cmd/kor/root.go | 4 ++-- pkg/filters/filters.go | 23 ++++++++++++++++------ pkg/filters/filters_test.go | 4 ++-- pkg/filters/options.go | 39 ++++++++++++++++++++++++++----------- pkg/kor/ingresses.go | 2 +- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/cmd/kor/root.go b/cmd/kor/root.go index f82f4f5a..de3bd229 100644 --- a/cmd/kor/root.go +++ b/cmd/kor/root.go @@ -76,10 +76,10 @@ func Execute() { } func addFilterOptionsFlag(cmd *cobra.Command, opts *filters.Options) { - cmd.PersistentFlags().StringVarP(&opts.ExcludeLabels, "exclude-labels", "l", opts.ExcludeLabels, "Selector to filter out, Example: --exclude-labels key1=value1,key2=value2. If --include-labels is set, --exclude-labels will be ignored.") + cmd.PersistentFlags().StringSliceVarP(&opts.ExcludeLabels, "exclude-labels", "l", opts.ExcludeLabels, "Selector to filter out, Example: --exclude-labels key1=value1,key2=value2. If --include-labels is set, --exclude-labels will be ignored.") cmd.PersistentFlags().StringVar(&opts.NewerThan, "newer-than", opts.NewerThan, "The maximum age of the resources to be considered unused. This flag cannot be used together with older-than flag. Example: --newer-than=1h2m") cmd.PersistentFlags().StringVar(&opts.OlderThan, "older-than", opts.OlderThan, "The minimum age of the resources to be considered unused. This flag cannot be used together with newer-than flag. Example: --older-than=1h2m") - cmd.PersistentFlags().StringVar(&opts.IncludeLabels, "include-labels", opts.IncludeLabels, "Selector to filter in, Example: --include-labels key1=value1,key2=value2.") + cmd.PersistentFlags().StringVar(&opts.IncludeLabels, "include-labels", opts.IncludeLabels, "Selector to filter in, Example: --include-labels key1=value1.(currently supports one label)") cmd.PersistentFlags().StringSliceVar(&opts.ExcludeNamespaces, "exclude-namespaces", opts.ExcludeNamespaces, "Namespaces to be excluded, split by commas. Example: --exclude-namespace ns1,ns2,ns3. If --include-namespace is set, --exclude-namespaces will be ignored.") cmd.PersistentFlags().StringSliceVarP(&opts.IncludeNamespaces, "include-namespaces", "n", opts.IncludeNamespaces, "Namespaces to run on, split by commas. Example: --include-namespace ns1,ns2,ns3. ") } diff --git a/pkg/filters/filters.go b/pkg/filters/filters.go index d8eca569..f310e61e 100644 --- a/pkg/filters/filters.go +++ b/pkg/filters/filters.go @@ -46,17 +46,28 @@ func AgeFilter(object runtime.Object, opts *Options) bool { } // HasExcludedLabel parses the excluded selector into a label selector object -func HasExcludedLabel(resourcelabels map[string]string, excludeSelector string) (bool, error) { - if excludeSelector == "" { +func HasExcludedLabel(resourcelabels map[string]string, excludeSelector []string) (bool, error) { + excludes := make([]labels.Selector, 0) + + if len(excludeSelector) == 0 { return false, nil } - exclude, err := labels.Parse(excludeSelector) - if err != nil { - return false, err + + for _, labelStr := range excludeSelector { + exclude, err := labels.Parse(labelStr) + if err != nil { + return false, err + } + excludes = append(excludes, exclude) } labelSet := labels.Set(resourcelabels) - return exclude.Matches(labelSet), nil + for _, exclude := range excludes { + if exclude.Matches(labelSet) { + return true, nil + } + } + return false, nil } // HasIncludedAge checks if a resource has an age that matches the included criteria specified by the filter options diff --git a/pkg/filters/filters_test.go b/pkg/filters/filters_test.go index 4e3ae032..37b495c6 100644 --- a/pkg/filters/filters_test.go +++ b/pkg/filters/filters_test.go @@ -32,7 +32,7 @@ func TestLabelFilter(t *testing.T) { args: args{ object: node, opts: &Options{ - ExcludeLabels: "foo=barbar", + ExcludeLabels: []string{"foo=barbar"}, }, }, want: false, @@ -42,7 +42,7 @@ func TestLabelFilter(t *testing.T) { args: args{ object: node, opts: &Options{ - ExcludeLabels: "foo=bar", + ExcludeLabels: []string{"foo=bar"}, }, }, want: true, diff --git a/pkg/filters/options.go b/pkg/filters/options.go index 4e3d3a3b..5ef9b20c 100644 --- a/pkg/filters/options.go +++ b/pkg/filters/options.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "strings" "sync" "time" @@ -29,7 +30,7 @@ type Options struct { NewerThan string // ExcludeLabels is a label selector to exclude resources with matching labels // IncludeLabels conflicts with it, and when setting IncludeLabels, ExcludeLabels is ignored and set to empty - ExcludeLabels string + ExcludeLabels []string // IncludeLabels is a label selector to include resources with matching labels IncludeLabels string // ExcludeNamespaces is a namespace selector to exclude resources in matching namespaces @@ -45,19 +46,35 @@ type Options struct { // NewFilterOptions returns a new FilterOptions instance with default values func NewFilterOptions() *Options { return &Options{ - OlderThan: "", - NewerThan: "", - ExcludeLabels: "", + OlderThan: "", + NewerThan: "", } } +func parseLabels(labelsStr string) (labels.Set, error) { + labelMap := map[string]string{} + + labelPairs := strings.Split(labelsStr, ",") + + for _, pair := range labelPairs { + parts := strings.SplitN(pair, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid label format: %s", pair) + } + labelMap[parts[0]] = parts[1] + } + + return labels.Set(labelMap), nil +} + // Validate makes sure provided values for FilterOptions are valid func (o *Options) Validate() error { - if _, err := labels.Parse(o.ExcludeLabels); err != nil { - return err - } - if _, err := labels.Parse(o.IncludeLabels); err != nil { - return err + + // Parse and validate the labels + for _, labelStr := range o.ExcludeLabels { + if _, err := parseLabels(labelStr); err != nil { + return err + } } // Parse the older-than flag value into a time.Duration value @@ -145,9 +162,9 @@ func (o *Options) Namespaces(clientset kubernetes.Interface) []string { func (o *Options) modifyLabels() { if o.IncludeLabels != "" { - if o.ExcludeLabels != "" { + if len(o.ExcludeLabels) > 0 { fmt.Fprintf(os.Stderr, "Exclude labels can't be used together with include labels. Ignoring --exclude-label(-l) flag\n") } - o.ExcludeLabels = "" + o.ExcludeLabels = nil } } diff --git a/pkg/kor/ingresses.go b/pkg/kor/ingresses.go index b91e30c9..431a22f9 100644 --- a/pkg/kor/ingresses.go +++ b/pkg/kor/ingresses.go @@ -68,7 +68,7 @@ func retrieveUsedIngress(clientset kubernetes.Interface, namespace string, filte } func retrieveIngressNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, []string, error) { - ingresses, err := clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) + ingresses, err := clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels}) if err != nil { return nil, nil, err }