Skip to content

Commit fb8f1b2

Browse files
author
Kevin Wiesmüller
committed
add extended remove method for TypedValue
to support recursive removal of fields
1 parent 877aee0 commit fb8f1b2

File tree

9 files changed

+602
-32
lines changed

9 files changed

+602
-32
lines changed

fieldpath/element.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ type PathElement struct {
4747
Index *int
4848
}
4949

50+
// NewValuePathElement creates a PathElement from the provided value.Value
51+
func NewValuePathElement(v value.Value) PathElement {
52+
return PathElement{Value: &v}
53+
}
54+
55+
// NewIndexPathElement creates a PathElement with the given index
56+
func NewIndexPathElement(index int) PathElement {
57+
return PathElement{Index: &index}
58+
}
59+
5060
// Less provides an order for path elements.
5161
func (e PathElement) Less(rhs PathElement) bool {
5262
return e.Compare(rhs) < 0

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module sigs.k8s.io/structured-merge-diff/v3
22

3-
require gopkg.in/yaml.v2 v2.2.1
3+
require gopkg.in/yaml.v2 v2.2.2
44

55
require (
66
github.com/google/gofuzz v1.0.0
77
github.com/json-iterator/go v1.1.6
88
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
99
github.com/modern-go/reflect2 v1.0.1 // indirect
10+
github.com/stretchr/testify v1.5.0 // indirect
1011
)
1112

1213
go 1.13

go.sum

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
1010
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
1111
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1212
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13+
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
1314
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
14-
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
15-
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
15+
github.com/stretchr/testify v1.5.0 h1:DMOzIV76tmoDNE9pX6RSN0aDtCYeCg5VueieJaAo1uw=
16+
github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
1617
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1718
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18-
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
19-
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
20-
sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA=
21-
sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
19+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
20+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

internal/fixture/state.go

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
130130

131131
// Update the current state with the passed in object
132132
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manager string) error {
133-
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
133+
tv, err := s.parseConfig(version, obj)
134134
if err != nil {
135135
return err
136136
}
@@ -159,7 +159,7 @@ func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion,
159159

160160
// Apply the passed in object to the current state
161161
func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manager string, force bool) error {
162-
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
162+
tv, err := s.parseConfig(version, obj)
163163
if err != nil {
164164
return err
165165
}
@@ -184,6 +184,10 @@ func (s *State) CompareLive(obj typed.YAMLObject, version fieldpath.APIVersion)
184184
return live.Compare(tv)
185185
}
186186

187+
func (s *State) parseConfig(apiVersion fieldpath.APIVersion, obj typed.YAMLObject) (*typed.TypedValue, error) {
188+
return s.Parser.Type(string(apiVersion)).FromYAML(FixTabsOrDie(obj))
189+
}
190+
187191
// dummyConverter doesn't convert, it just returns the same exact object, as long as a version is provided.
188192
type dummyConverter struct{}
189193

@@ -471,12 +475,12 @@ func (tc TestCase) TestWithConverter(parser Parser, converter merge.Converter) e
471475

472476
// If LastObject was specified, compare it with LiveState
473477
if tc.Object != typed.YAMLObject("") {
474-
comparison, err := state.CompareLive(tc.Object, tc.APIVersion)
475-
if err != nil {
476-
return fmt.Errorf("failed to compare live with config: %v", err)
478+
expect := ExpectState{
479+
APIVersion: tc.APIVersion, Object: tc.Object,
477480
}
478-
if !comparison.IsSame() {
479-
return fmt.Errorf("expected live and config to be the same:\n%v\nConfig: %v\n", comparison, value.ToString(state.Live.AsValue()))
481+
482+
if err := expect.run(&state); err != nil {
483+
return err
480484
}
481485
}
482486

@@ -505,3 +509,44 @@ func (tc TestCase) TestWithConverter(parser Parser, converter merge.Converter) e
505509

506510
return nil
507511
}
512+
513+
// PrintState is an Operation printing the current state to help with debugging tests
514+
type PrintState struct{}
515+
516+
var _ Operation = PrintState{}
517+
518+
func (op PrintState) run(s *State) error {
519+
fmt.Println(value.ToString(s.Live.AsValue()))
520+
return nil
521+
}
522+
523+
func (op PrintState) preprocess(_ Parser) (Operation, error) {
524+
return op, nil
525+
}
526+
527+
// ExpectState is an Operation comparing the current state to the defined config to help with debugging tests
528+
type ExpectState struct {
529+
APIVersion fieldpath.APIVersion
530+
Object typed.YAMLObject
531+
}
532+
533+
var _ Operation = ExpectState{}
534+
535+
func (op ExpectState) run(state *State) error {
536+
comparison, err := state.CompareLive(op.Object, op.APIVersion)
537+
if err != nil {
538+
return fmt.Errorf("failed to compare live with config: %v", err)
539+
}
540+
if !comparison.IsSame() {
541+
config, err := state.parseConfig(op.APIVersion, op.Object)
542+
if err != nil {
543+
return fmt.Errorf("failed to parse config: %w", err)
544+
}
545+
return fmt.Errorf("expected live and config to be the same:\n%v\nConfig: %v\nLive: %v\n", comparison, value.ToString(config.AsValue()), value.ToString(state.Live.AsValue()))
546+
}
547+
return nil
548+
}
549+
550+
func (op ExpectState) preprocess(parser Parser) (Operation, error) {
551+
return op, nil
552+
}

merge/multiple_appliers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ func TestMultipleAppliersNestedType(t *testing.T) {
670670
},
671671
Object: `
672672
mapOfMapsRecursive:
673-
a:
673+
a: {}
674674
c:
675675
d:
676676
e:
@@ -788,7 +788,7 @@ func TestMultipleAppliersDeducedType(t *testing.T) {
788788
},
789789
},
790790
Object: `
791-
a:
791+
a: {}
792792
c:
793793
d:
794794
e:

merge/update.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFiel
213213
return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
214214
}
215215

216+
// pruned is the value *after* pruning
216217
pruned := convertedMerged.RemoveItems(lastSet.Set())
217218
pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
218219
if err != nil {
@@ -261,7 +262,11 @@ func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFie
261262
if err != nil {
262263
return nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
263264
}
264-
pruned = merged.RemoveItems(mergedSet.Difference(prunedSet.Union(managed)))
265+
266+
newOrManaged := prunedSet.Union(managed)
267+
oldAndUnmanaged := mergedSet.Difference(newOrManaged)
268+
// a value containing only new or previously managed fields
269+
pruned = merged.RemoveItems(oldAndUnmanaged)
265270
}
266271
return pruned, nil
267272
}
@@ -285,5 +290,9 @@ func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet
285290
if err != nil {
286291
return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
287292
}
288-
return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(lastSet.Set())), nil
293+
294+
oldOrUnmanaged := mergedSet.Difference(prunedSet)
295+
onlyOld := oldOrUnmanaged.Intersection(lastSet.Set())
296+
297+
return merged.RemoveItems(onlyOld), nil
289298
}

typed/remove.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ type removingWalker struct {
2525
schema *schema.Schema
2626
toRemove *fieldpath.Set
2727
allocator value.Allocator
28+
onlyItems bool
2829
}
2930

30-
func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) value.Value {
31+
func removeWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef, onlyItems bool) value.Value {
3132
w := &removingWalker{
3233
value: val,
3334
schema: schema,
3435
toRemove: toRemove,
3536
allocator: value.NewFreelistAllocator(),
37+
onlyItems: onlyItems,
3638
}
3739
resolveSchema(schema, typeRef, val, w)
3840
return value.NewValueInterface(w.out)
@@ -46,8 +48,10 @@ func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors {
4648
func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
4749
l := w.value.AsListUsing(w.allocator)
4850
defer w.allocator.Free(l)
49-
// If list is null, empty, or atomic just return
50-
if l == nil || l.Length() == 0 || t.ElementRelationship == schema.Atomic {
51+
52+
// If list is null or empty just return
53+
if l == nil || l.Length() == 0 {
54+
w.out = w.value.Unstructured()
5155
return nil
5256
}
5357

@@ -63,13 +67,11 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
6367
continue
6468
}
6569
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
66-
item = removeItemsWithSchema(item, subset, w.schema, t.ElementType)
70+
item = removeWithSchema(item, subset, w.schema, t.ElementType, w.onlyItems)
6771
}
6872
newItems = append(newItems, item.Unstructured())
6973
}
70-
if len(newItems) > 0 {
71-
w.out = newItems
72-
}
74+
w.out = newItems
7375
return nil
7476
}
7577

@@ -78,8 +80,9 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
7880
if m != nil {
7981
defer w.allocator.Free(m)
8082
}
81-
// If map is null, empty, or atomic just return
82-
if m == nil || m.Empty() || t.ElementRelationship == schema.Atomic {
83+
// If map is null or empty just return
84+
if m == nil || m.Empty() {
85+
w.out = w.value.Unstructured()
8386
return nil
8487
}
8588

@@ -92,6 +95,11 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
9295
m.Iterate(func(k string, val value.Value) bool {
9396
pe := fieldpath.PathElement{FieldName: &k}
9497
path, _ := fieldpath.MakePath(pe)
98+
99+
if !w.onlyItems && w.toRemove.Has(path) {
100+
return true
101+
}
102+
95103
fieldType := t.ElementType
96104
if ft, ok := fieldTypes[k]; ok {
97105
fieldType = ft
@@ -101,13 +109,11 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
101109
}
102110
}
103111
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
104-
val = removeItemsWithSchema(val, subset, w.schema, fieldType)
112+
val = removeWithSchema(val, subset, w.schema, fieldType, w.onlyItems)
105113
}
106114
newMap[k] = val.Unstructured()
107115
return true
108116
})
109-
if len(newMap) > 0 {
110-
w.out = newMap
111-
}
117+
w.out = newMap
112118
return nil
113119
}

0 commit comments

Comments
 (0)