istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/krt/filter.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package krt 16 17 import ( 18 "fmt" 19 "strings" 20 21 "k8s.io/apimachinery/pkg/types" 22 23 "istio.io/istio/pkg/config/labels" 24 "istio.io/istio/pkg/util/smallset" 25 ) 26 27 type filter struct { 28 keys smallset.Set[string] 29 30 // selectsNonEmpty is like selects, but it treats an empty selector as not matching 31 selectsNonEmpty map[string]string 32 selects map[string]string 33 labels map[string]string 34 generic func(any) bool 35 36 listFromIndex func() any 37 indexMatches func(any) bool 38 } 39 40 func (f *filter) String() string { 41 attrs := []string{} 42 if !f.keys.IsNil() { 43 attrs = append(attrs, "keys="+f.keys.String()) 44 } 45 if f.selectsNonEmpty != nil { 46 attrs = append(attrs, fmt.Sprintf("selectsNonEmpty=%v", f.selectsNonEmpty)) 47 } 48 if f.selects != nil { 49 attrs = append(attrs, fmt.Sprintf("selects=%v", f.selects)) 50 } 51 if f.labels != nil { 52 attrs = append(attrs, fmt.Sprintf("labels=%v", f.labels)) 53 } 54 if f.generic != nil { 55 attrs = append(attrs, "generic") 56 } 57 res := strings.Join(attrs, ",") 58 return fmt.Sprintf("{%s}", res) 59 } 60 61 // FilterObjectName selects a Kubernetes object by name. 62 func FilterObjectName(name types.NamespacedName) FetchOption { 63 return func(h *dependency) { 64 // Translate to a key lookup 65 h.filter.keys = smallset.New(keyFunc(name.Name, name.Namespace)) 66 } 67 } 68 69 func FilterKey(k string) FetchOption { 70 return func(h *dependency) { 71 h.filter.keys = smallset.New(k) 72 } 73 } 74 75 func FilterKeys(k ...string) FetchOption { 76 return func(h *dependency) { 77 h.filter.keys = smallset.New(k...) 78 } 79 } 80 81 // FilterSelects only includes objects that select this label. If the selector is empty, it is a match. 82 func FilterSelects(lbls map[string]string) FetchOption { 83 return func(h *dependency) { 84 h.filter.selects = lbls 85 } 86 } 87 88 // FilterIndex selects only objects matching a key in an index. 89 func FilterIndex[I any, K comparable](idx *Index[I, K], k K) FetchOption { 90 return func(h *dependency) { 91 // Index is used to pre-filter on the List, and also to match in Matches. Provide type-erased methods for both 92 h.filter.listFromIndex = func() any { 93 return idx.Lookup(k) 94 } 95 h.filter.indexMatches = func(a any) bool { 96 return idx.objectHasKey(a.(I), k) 97 } 98 } 99 } 100 101 // FilterSelectsNonEmpty only includes objects that select this label. If the selector is empty, it is not a match. 102 func FilterSelectsNonEmpty(lbls map[string]string) FetchOption { 103 return func(h *dependency) { 104 h.filter.selectsNonEmpty = lbls 105 } 106 } 107 108 func FilterLabel(lbls map[string]string) FetchOption { 109 return func(h *dependency) { 110 h.filter.labels = lbls 111 } 112 } 113 114 func FilterGeneric(f func(any) bool) FetchOption { 115 return func(h *dependency) { 116 h.filter.generic = f 117 } 118 } 119 120 func (f *filter) Matches(object any, forList bool) bool { 121 // Check each of our defined filters to see if the object matches 122 // This function is called very often and is important to keep fast 123 // Cheaper checks should come earlier to avoid additional work and short circuit early 124 125 // If we are listing, we already did this. Do not redundantly check. 126 if !forList { 127 // First, lookup directly by key. This is cheap 128 // an empty set will match none 129 if !f.keys.IsNil() && !f.keys.Contains(string(GetKey[any](object))) { 130 if log.DebugEnabled() { 131 log.Debugf("no match key: %q vs %q", f.keys, string(GetKey[any](object))) 132 } 133 return false 134 } 135 // Index is also cheap, and often used to filter namespaces out. Make sure we do this early 136 if f.indexMatches != nil && !f.indexMatches(object) { 137 if log.DebugEnabled() { 138 log.Debugf("no match index") 139 } 140 return false 141 } 142 } 143 144 // Rest is expensive 145 if f.selects != nil && !labels.Instance(getLabelSelector(object)).SubsetOf(f.selects) { 146 if log.DebugEnabled() { 147 log.Debugf("no match selects: %q vs %q", f.selects, getLabelSelector(object)) 148 } 149 return false 150 } 151 if f.selectsNonEmpty != nil && !labels.Instance(getLabelSelector(object)).Match(f.selectsNonEmpty) { 152 if log.DebugEnabled() { 153 log.Debugf("no match selectsNonEmpty: %q vs %q", f.selectsNonEmpty, getLabelSelector(object)) 154 } 155 return false 156 } 157 if f.labels != nil && !labels.Instance(f.labels).SubsetOf(getLabels(object)) { 158 if log.DebugEnabled() { 159 log.Debugf("no match labels: %q vs %q", f.labels, getLabels(object)) 160 } 161 return false 162 } 163 if f.generic != nil && !f.generic(object) { 164 if log.DebugEnabled() { 165 log.Debugf("no match generic") 166 } 167 return false 168 } 169 return true 170 }