Skip to content

Commit ccf7a06

Browse files
authored
Merge pull request kubernetes-sigs#265 from jpbetz/reset-filter
Accept filter instead of exclude mask for ignored fields
2 parents b499124 + bda634e commit ccf7a06

File tree

6 files changed

+589
-51
lines changed

6 files changed

+589
-51
lines changed

fieldpath/set.go

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package fieldpath
1818

1919
import (
20+
"fmt"
21+
"sigs.k8s.io/structured-merge-diff/v4/value"
2022
"sort"
2123
"strings"
2224

@@ -136,6 +138,198 @@ func (s *Set) EnsureNamedFieldsAreMembers(sc *schema.Schema, tr schema.TypeRef)
136138
}
137139
}
138140

141+
// MakePrefixMatcherOrDie is the same as PrefixMatcher except it panics if parts can't be
142+
// turned into a SetMatcher.
143+
func MakePrefixMatcherOrDie(parts ...interface{}) *SetMatcher {
144+
result, err := PrefixMatcher(parts...)
145+
if err != nil {
146+
panic(err)
147+
}
148+
return result
149+
}
150+
151+
// PrefixMatcher creates a SetMatcher that matches all field paths prefixed by the given list of matcher path parts.
152+
// The matcher parts may any of:
153+
//
154+
// - PathElementMatcher - for wildcards, `MatchAnyPathElement()` can be used as well.
155+
// - PathElement - for any path element
156+
// - value.FieldList - for listMap keys
157+
// - value.Value - for scalar list elements
158+
// - string - For field names
159+
// - int - for array indices
160+
func PrefixMatcher(parts ...interface{}) (*SetMatcher, error) {
161+
current := MatchAnySet() // match all field path suffixes
162+
for i := len(parts) - 1; i >= 0; i-- {
163+
part := parts[i]
164+
var pattern PathElementMatcher
165+
switch t := part.(type) {
166+
case PathElementMatcher:
167+
// any path matcher, including wildcard
168+
pattern = t
169+
case PathElement:
170+
// any path element
171+
pattern = PathElementMatcher{PathElement: t}
172+
case *value.FieldList:
173+
// a listMap key
174+
if len(*t) == 0 {
175+
return nil, fmt.Errorf("associative list key type path elements must have at least one key (got zero)")
176+
}
177+
pattern = PathElementMatcher{PathElement: PathElement{Key: t}}
178+
case value.Value:
179+
// a scalar or set-type list element
180+
pattern = PathElementMatcher{PathElement: PathElement{Value: &t}}
181+
case string:
182+
// a plain field name
183+
pattern = PathElementMatcher{PathElement: PathElement{FieldName: &t}}
184+
case int:
185+
// a plain list index
186+
pattern = PathElementMatcher{PathElement: PathElement{Index: &t}}
187+
default:
188+
return nil, fmt.Errorf("unexpected type %T", t)
189+
}
190+
current = &SetMatcher{
191+
members: []*SetMemberMatcher{{
192+
Path: pattern,
193+
Child: current,
194+
}},
195+
}
196+
}
197+
return current, nil
198+
}
199+
200+
// MatchAnyPathElement returns a PathElementMatcher that matches any path element.
201+
func MatchAnyPathElement() PathElementMatcher {
202+
return PathElementMatcher{Wildcard: true}
203+
}
204+
205+
// MatchAnySet returns a SetMatcher that matches any set.
206+
func MatchAnySet() *SetMatcher {
207+
return &SetMatcher{wildcard: true}
208+
}
209+
210+
// NewSetMatcher returns a new SetMatcher.
211+
// Wildcard members take precedent over non-wildcard members;
212+
// all non-wildcard members are ignored if there is a wildcard members.
213+
func NewSetMatcher(wildcard bool, members ...*SetMemberMatcher) *SetMatcher {
214+
sort.Sort(sortedMemberMatcher(members))
215+
return &SetMatcher{wildcard: wildcard, members: members}
216+
}
217+
218+
// SetMatcher defines a matcher that matches fields in a Set.
219+
// SetMatcher is structured much like a Set but with wildcard support.
220+
type SetMatcher struct {
221+
// wildcard indicates that all members and children are included in the match.
222+
// If set, the members field is ignored.
223+
wildcard bool
224+
// members provides patterns to match the members of a Set.
225+
// Wildcard members are sorted before non-wildcards and take precedent over
226+
// non-wildcard members.
227+
members sortedMemberMatcher
228+
}
229+
230+
type sortedMemberMatcher []*SetMemberMatcher
231+
232+
func (s sortedMemberMatcher) Len() int { return len(s) }
233+
func (s sortedMemberMatcher) Less(i, j int) bool { return s[i].Path.Less(s[j].Path) }
234+
func (s sortedMemberMatcher) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
235+
func (s sortedMemberMatcher) Find(p PathElementMatcher) (location int, ok bool) {
236+
return sort.Find(len(s), func(i int) int {
237+
return s[i].Path.Compare(p)
238+
})
239+
}
240+
241+
// Merge merges s and s2 and returns a SetMatcher that matches all field paths matched by either s or s2.
242+
// During the merge, members of s and s2 with the same PathElementMatcher merged into a single member
243+
// with the children of each merged by calling this function recursively.
244+
func (s *SetMatcher) Merge(s2 *SetMatcher) *SetMatcher {
245+
if s.wildcard || s2.wildcard {
246+
return NewSetMatcher(true)
247+
}
248+
merged := make(sortedMemberMatcher, len(s.members), len(s.members)+len(s2.members))
249+
copy(merged, s.members)
250+
for _, m := range s2.members {
251+
if i, ok := s.members.Find(m.Path); ok {
252+
// since merged is a shallow copy, do not modify elements in place
253+
merged[i] = &SetMemberMatcher{
254+
Path: merged[i].Path,
255+
Child: merged[i].Child.Merge(m.Child),
256+
}
257+
} else {
258+
merged = append(merged, m)
259+
}
260+
}
261+
return NewSetMatcher(false, merged...) // sort happens here
262+
}
263+
264+
// SetMemberMatcher defines a matcher that matches the members of a Set.
265+
// SetMemberMatcher is structured much like the elements of a SetNodeMap, but
266+
// with wildcard support.
267+
type SetMemberMatcher struct {
268+
// Path provides a matcher to match members of a Set.
269+
// If Path is a wildcard, all members of a Set are included in the match.
270+
// Otherwise, if any Path is Equal to a member of a Set, that member is
271+
// included in the match and the children of that member are matched
272+
// against the Child matcher.
273+
Path PathElementMatcher
274+
275+
// Child provides a matcher to use for the children of matched members of a Set.
276+
Child *SetMatcher
277+
}
278+
279+
// PathElementMatcher defined a path matcher for a PathElement.
280+
type PathElementMatcher struct {
281+
// Wildcard indicates that all PathElements are matched by this matcher.
282+
// If set, PathElement is ignored.
283+
Wildcard bool
284+
285+
// PathElement indicates that a PathElement is matched if it is Equal
286+
// to this PathElement.
287+
PathElement
288+
}
289+
290+
func (p PathElementMatcher) Equals(p2 PathElementMatcher) bool {
291+
return p.Wildcard != p2.Wildcard && p.PathElement.Equals(p2.PathElement)
292+
}
293+
294+
func (p PathElementMatcher) Less(p2 PathElementMatcher) bool {
295+
if p.Wildcard && !p2.Wildcard {
296+
return true
297+
} else if p2.Wildcard {
298+
return false
299+
}
300+
return p.PathElement.Less(p2.PathElement)
301+
}
302+
303+
func (p PathElementMatcher) Compare(p2 PathElementMatcher) int {
304+
if p.Wildcard && !p2.Wildcard {
305+
return -1
306+
} else if p2.Wildcard {
307+
return 1
308+
}
309+
return p.PathElement.Compare(p2.PathElement)
310+
}
311+
312+
// FilterIncludeMatches returns a Set with only the field paths that match.
313+
func (s *Set) FilterIncludeMatches(pattern *SetMatcher) *Set {
314+
if pattern.wildcard {
315+
return s
316+
}
317+
318+
members := PathElementSet{}
319+
for _, m := range s.Members.members {
320+
for _, pm := range pattern.members {
321+
if pm.Path.Wildcard || pm.Path.PathElement.Equals(m) {
322+
members.Insert(m)
323+
break
324+
}
325+
}
326+
}
327+
return &Set{
328+
Members: members,
329+
Children: *s.Children.FilterIncludeMatches(pattern),
330+
}
331+
}
332+
139333
// Size returns the number of members of the set.
140334
func (s *Set) Size() int {
141335
return s.Members.Size() + s.Children.Size()
@@ -476,6 +670,33 @@ func (s *SetNodeMap) EnsureNamedFieldsAreMembers(sc *schema.Schema, tr schema.Ty
476670
}
477671
}
478672

673+
// FilterIncludeMatches returns a SetNodeMap with only the field paths that match the matcher.
674+
func (s *SetNodeMap) FilterIncludeMatches(pattern *SetMatcher) *SetNodeMap {
675+
if pattern.wildcard {
676+
return s
677+
}
678+
679+
var out sortedSetNode
680+
for _, member := range s.members {
681+
for _, c := range pattern.members {
682+
if c.Path.Wildcard || c.Path.PathElement.Equals(member.pathElement) {
683+
childSet := member.set.FilterIncludeMatches(c.Child)
684+
if childSet.Size() > 0 {
685+
out = append(out, setNode{
686+
pathElement: member.pathElement,
687+
set: childSet,
688+
})
689+
}
690+
break
691+
}
692+
}
693+
}
694+
695+
return &SetNodeMap{
696+
members: out,
697+
}
698+
}
699+
479700
// Iterate calls f for each PathElement in the set.
480701
func (s *SetNodeMap) Iterate(f func(PathElement)) {
481702
for _, n := range s.members {
@@ -503,3 +724,59 @@ func (s *SetNodeMap) Leaves() *SetNodeMap {
503724
}
504725
return out
505726
}
727+
728+
// Filter defines an interface for excluding field paths from a set.
729+
// NewExcludeSetFilter can be used to create a filter that removes
730+
// specific field paths and all of their children.
731+
// NewIncludeMatcherFilter can be used to create a filter that removes all fields except
732+
// the fields that match a field path matcher. PrefixMatcher and MakePrefixMatcherOrDie
733+
// can be used to define field path patterns.
734+
type Filter interface {
735+
// Filter returns a filtered copy of the set.
736+
Filter(*Set) *Set
737+
}
738+
739+
// NewExcludeSetFilter returns a filter that removes field paths in the exclude set.
740+
func NewExcludeSetFilter(exclude *Set) Filter {
741+
return excludeFilter{exclude}
742+
}
743+
744+
// NewExcludeFilterSetMap converts a map of APIVersion to exclude set to a map of APIVersion to exclude filters.
745+
func NewExcludeFilterSetMap(resetFields map[APIVersion]*Set) map[APIVersion]Filter {
746+
result := make(map[APIVersion]Filter)
747+
for k, v := range resetFields {
748+
result[k] = excludeFilter{v}
749+
}
750+
return result
751+
}
752+
753+
type excludeFilter struct {
754+
excludeSet *Set
755+
}
756+
757+
func (t excludeFilter) Filter(set *Set) *Set {
758+
return set.RecursiveDifference(t.excludeSet)
759+
}
760+
761+
// NewIncludeMatcherFilter returns a filter that only includes field paths that match.
762+
// If no matchers are provided, the filter includes all field paths.
763+
// PrefixMatcher and MakePrefixMatcherOrDie can help create basic matcher.
764+
func NewIncludeMatcherFilter(matchers ...*SetMatcher) Filter {
765+
if len(matchers) == 0 {
766+
return includeMatcherFilter{&SetMatcher{wildcard: true}}
767+
}
768+
matcher := matchers[0]
769+
for i := 1; i < len(matchers); i++ {
770+
matcher = matcher.Merge(matchers[i])
771+
}
772+
773+
return includeMatcherFilter{matcher}
774+
}
775+
776+
type includeMatcherFilter struct {
777+
matcher *SetMatcher
778+
}
779+
780+
func (pf includeMatcherFilter) Filter(set *Set) *Set {
781+
return set.FilterIncludeMatches(pf.matcher)
782+
}

0 commit comments

Comments
 (0)