Skip to content

Commit 72cf25b

Browse files
author
Kevin Wiesmueller
committed
add ignoredFields to update
1 parent aac1071 commit 72cf25b

File tree

7 files changed

+284
-24
lines changed

7 files changed

+284
-24
lines changed

fieldpath/set.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ func (s *Set) Has(p Path) bool {
128128
}
129129
}
130130

131+
// HasPartial returns true if PathElement of p is a member of the set.
132+
// Example:
133+
// For a set containing .obj
134+
// any path starting with .obj (like .obj.a) is part of the set.
135+
func (s *Set) HasPartial(p Path) bool {
136+
if len(p) == 0 {
137+
// No one owns "the entire object"
138+
return false
139+
}
140+
for {
141+
if len(p) == 0 {
142+
return false
143+
}
144+
if s.Members.Has(p[0]) {
145+
return true
146+
}
147+
var ok bool
148+
s, ok = s.Children.Get(p[0])
149+
if !ok {
150+
return false
151+
}
152+
p = p[1:]
153+
}
154+
}
155+
131156
// Equals returns true if s and s2 have exactly the same members.
132157
func (s *Set) Equals(s2 *Set) bool {
133158
return s.Members.Equals(&s2.Members) && s.Children.Equals(&s2.Children)

fieldpath/set_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,72 @@ func TestSetInsertHas(t *testing.T) {
193193
}
194194
}
195195

196+
func TestSetInsertHasPartial(t *testing.T) {
197+
s1 := NewSet(
198+
MakePathOrDie("foo", 0, "bar", "baz"),
199+
MakePathOrDie("foo", 0, "bar"),
200+
MakePathOrDie("foo", 0),
201+
MakePathOrDie("foo", 1, "bar", "baz"),
202+
MakePathOrDie("foo", 1, "bar"),
203+
MakePathOrDie("qux", KeyByFields("name", "first")),
204+
MakePathOrDie("qux", KeyByFields("name", "first"), "bar"),
205+
MakePathOrDie("qux", KeyByFields("name", "second"), "bar"),
206+
MakePathOrDie("canonicalOrder", KeyByFields(
207+
"a", "a",
208+
"b", "a",
209+
"c", "a",
210+
"d", "a",
211+
"e", "a",
212+
"f", "a",
213+
)),
214+
MakePathOrDie("bar"),
215+
)
216+
217+
table := []struct {
218+
set *Set
219+
check Path
220+
expectMembership bool
221+
}{
222+
{s1, MakePathOrDie("qux", KeyByFields("name", "second")), false},
223+
{s1, MakePathOrDie("qux", KeyByFields("name", "second"), "bar"), true},
224+
{s1, MakePathOrDie("qux", KeyByFields("name", "first")), true},
225+
{s1, MakePathOrDie("xuq", KeyByFields("name", "first")), false},
226+
{s1, MakePathOrDie("foo", 0), true},
227+
{s1, MakePathOrDie("foo", 0, "bar"), true},
228+
{s1, MakePathOrDie("foo", 0, "bar", "baz"), true},
229+
{s1, MakePathOrDie("foo", 1), false},
230+
{s1, MakePathOrDie("foo", 1, "bar"), true},
231+
{s1, MakePathOrDie("foo", 1, "bar", "baz"), true},
232+
{s1, MakePathOrDie("foo", 1, "bay"), false},
233+
{s1, MakePathOrDie("canonicalOrder", KeyByFields(
234+
"f", "a",
235+
"e", "a",
236+
"d", "a",
237+
"c", "a",
238+
"b", "a",
239+
"a", "a",
240+
)), true},
241+
{s1, MakePathOrDie("bar", KeyByFields("name", "second")), true},
242+
{s1, MakePathOrDie("bar", KeyByFields("name", "second"), "bar"), true},
243+
{s1, MakePathOrDie("bar", KeyByFields("name", "first")), true},
244+
{s1, MakePathOrDie("ba", KeyByFields("name", "first")), false},
245+
}
246+
247+
for _, tt := range table {
248+
got := tt.set.HasPartial(tt.check)
249+
if e, a := tt.expectMembership, got; e != a {
250+
t.Errorf("%v: wanted %v, got %v", tt.check.String(), e, a)
251+
}
252+
}
253+
254+
if NewSet().HasPartial(Path{}) {
255+
t.Errorf("empty set should not include the empty path")
256+
}
257+
if NewSet(Path{}).HasPartial(Path{}) {
258+
t.Errorf("empty set should not include the empty path")
259+
}
260+
}
261+
196262
func TestSetString(t *testing.T) {
197263
p := MakePathOrDie("foo", KeyByFields("name", "first"))
198264
s1 := NewSet(p)

internal/fixture/state.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (s *State) checkInit(version fieldpath.APIVersion) error {
109109
return nil
110110
}
111111

112-
func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string) error {
112+
func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string) error {
113113
err := s.checkInit(version)
114114
if err != nil {
115115
return err
@@ -118,7 +118,7 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
118118
if err != nil {
119119
return err
120120
}
121-
newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
121+
newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, ignored, manager)
122122
if err != nil {
123123
return err
124124
}
@@ -129,12 +129,12 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
129129
}
130130

131131
// Update the current state with the passed in object
132-
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manager string) error {
132+
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string) error {
133133
tv, err := s.parseConfig(version, obj)
134134
if err != nil {
135135
return err
136136
}
137-
return s.UpdateObject(tv, version, manager)
137+
return s.UpdateObject(tv, version, ignored, manager)
138138
}
139139

140140
func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string, force bool) error {
@@ -348,15 +348,16 @@ func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
348348
// Update is a type of operation. It is a controller type of
349349
// update. Errors are passed along.
350350
type Update struct {
351-
Manager string
352-
APIVersion fieldpath.APIVersion
353-
Object typed.YAMLObject
351+
Manager string
352+
APIVersion fieldpath.APIVersion
353+
Object typed.YAMLObject
354+
IgnoredFields *fieldpath.Set
354355
}
355356

356357
var _ Operation = &Update{}
357358

358359
func (u Update) run(state *State) error {
359-
return state.Update(u.Object, u.APIVersion, u.Manager)
360+
return state.Update(u.Object, u.APIVersion, u.IgnoredFields, u.Manager)
360361
}
361362

362363
func (u Update) preprocess(parser Parser) (Operation, error) {
@@ -365,24 +366,26 @@ func (u Update) preprocess(parser Parser) (Operation, error) {
365366
return nil, err
366367
}
367368
return UpdateObject{
368-
Manager: u.Manager,
369-
APIVersion: u.APIVersion,
370-
Object: tv,
369+
Manager: u.Manager,
370+
APIVersion: u.APIVersion,
371+
Object: tv,
372+
IgnoredFields: u.IgnoredFields,
371373
}, nil
372374
}
373375

374376
// UpdateObject is a type of operation. It is a controller type of
375377
// update. Errors are passed along.
376378
type UpdateObject struct {
377-
Manager string
378-
APIVersion fieldpath.APIVersion
379-
Object *typed.TypedValue
379+
Manager string
380+
APIVersion fieldpath.APIVersion
381+
Object *typed.TypedValue
382+
IgnoredFields *fieldpath.Set
380383
}
381384

382385
var _ Operation = &Update{}
383386

384387
func (u UpdateObject) run(state *State) error {
385-
return state.UpdateObject(u.Object, u.APIVersion, u.Manager)
388+
return state.UpdateObject(u.Object, u.APIVersion, u.IgnoredFields, u.Manager)
386389
}
387390

388391
func (f UpdateObject) preprocess(parser Parser) (Operation, error) {
@@ -550,3 +553,30 @@ func (op ExpectState) run(state *State) error {
550553
func (op ExpectState) preprocess(parser Parser) (Operation, error) {
551554
return op, nil
552555
}
556+
557+
// ExpectManagedFields is an Operation checking if the manager owns the defined fields in the current state
558+
// If the Fields are nil, it won't be an error if the manager is missing
559+
type ExpectManagedFields struct {
560+
Manager string
561+
Fields *fieldpath.Set
562+
}
563+
564+
var _ Operation = ExpectManagedFields{}
565+
566+
func (op ExpectManagedFields) run(state *State) error {
567+
manager, ok := state.Managers[op.Manager]
568+
if !ok {
569+
if op.Fields == nil {
570+
return nil
571+
}
572+
return fmt.Errorf("manager not found: %s", op.Manager)
573+
}
574+
if diff := manager.Set().Difference(op.Fields); !diff.Empty() {
575+
return fmt.Errorf("unexpected managedFields for %s: \n%v", op.Manager, diff)
576+
}
577+
return nil
578+
}
579+
580+
func (op ExpectManagedFields) preprocess(parser Parser) (Operation, error) {
581+
return op, nil
582+
}

merge/ignore_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package merge_test
2+
3+
import (
4+
"testing"
5+
6+
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
7+
. "sigs.k8s.io/structured-merge-diff/v3/internal/fixture"
8+
)
9+
10+
func TestIgnoredFields(t *testing.T) {
11+
tests := map[string]TestCase{
12+
"do_not_own_ignored": {
13+
APIVersion: "v1",
14+
Ops: []Operation{
15+
Update{
16+
Manager: "default",
17+
APIVersion: "v1",
18+
Object: `
19+
numeric: 1
20+
string: "some string"
21+
`,
22+
IgnoredFields: fieldpath.NewSet(
23+
fieldpath.MakePathOrDie("string"),
24+
),
25+
},
26+
ExpectState{
27+
APIVersion: "v1",
28+
Object: `
29+
numeric: 1
30+
string: "some string"
31+
`,
32+
},
33+
ExpectManagedFields{
34+
Manager: "default",
35+
Fields: fieldpath.NewSet(
36+
fieldpath.MakePathOrDie("numeric"),
37+
),
38+
},
39+
},
40+
},
41+
"do_not_steal_ignored": {
42+
APIVersion: "v1",
43+
Ops: []Operation{
44+
Update{
45+
Manager: "default",
46+
APIVersion: "v1",
47+
Object: `
48+
numeric: 1
49+
string: "some string"
50+
`,
51+
},
52+
ExpectState{
53+
APIVersion: "v1",
54+
Object: `
55+
numeric: 1
56+
string: "some string"
57+
`,
58+
},
59+
ExpectManagedFields{
60+
Manager: "default",
61+
Fields: fieldpath.NewSet(
62+
fieldpath.MakePathOrDie("numeric"),
63+
fieldpath.MakePathOrDie("string"),
64+
),
65+
},
66+
Update{
67+
Manager: "default2",
68+
APIVersion: "v1",
69+
Object: `
70+
numeric: 1
71+
string: "no string"
72+
`,
73+
IgnoredFields: fieldpath.NewSet(fieldpath.MakePathOrDie("string")),
74+
},
75+
ExpectState{
76+
APIVersion: "v1",
77+
Object: `
78+
numeric: 1
79+
string: "no string"
80+
`,
81+
},
82+
ExpectManagedFields{
83+
Manager: "default2",
84+
Fields: nil,
85+
},
86+
},
87+
},
88+
"do_not_own_deep_ignored": {
89+
APIVersion: "v1",
90+
Ops: []Operation{
91+
Update{
92+
Manager: "default",
93+
APIVersion: "v1",
94+
Object: `{"numeric": 1, "obj": {"string": "foo", "numeric": 2}}`,
95+
IgnoredFields: fieldpath.NewSet(
96+
fieldpath.MakePathOrDie("obj"),
97+
),
98+
},
99+
ExpectState{
100+
APIVersion: "v1",
101+
Object: `{"numeric": 1, "obj": {"string": "foo", "numeric": 2}}`,
102+
},
103+
ExpectManagedFields{
104+
Manager: "default",
105+
Fields: fieldpath.NewSet(
106+
fieldpath.MakePathOrDie("numeric"),
107+
),
108+
},
109+
},
110+
},
111+
}
112+
113+
for name, test := range tests {
114+
t.Run(name, func(t *testing.T) {
115+
if err := test.Test(DeducedParser); err != nil {
116+
t.Fatal("Should fail:", err)
117+
}
118+
})
119+
}
120+
}

merge/obsolete_versions_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,16 @@ func TestObsoleteVersions(t *testing.T) {
5656
Parser: DeducedParser,
5757
}
5858

59-
if err := state.Update(typed.YAMLObject(`{"v1": 0}`), fieldpath.APIVersion("v1"), "v1"); err != nil {
59+
if err := state.Update(typed.YAMLObject(`{"v1": 0}`), fieldpath.APIVersion("v1"), nil, "v1"); err != nil {
6060
t.Fatalf("Failed to apply: %v", err)
6161
}
62-
if err := state.Update(typed.YAMLObject(`{"v1": 0, "v2": 0}`), fieldpath.APIVersion("v2"), "v2"); err != nil {
62+
if err := state.Update(typed.YAMLObject(`{"v1": 0, "v2": 0}`), fieldpath.APIVersion("v2"), nil, "v2"); err != nil {
6363
t.Fatalf("Failed to apply: %v", err)
6464
}
6565
// Remove v1, add v3 instead.
6666
converter.AcceptedVersions = []fieldpath.APIVersion{"v2", "v3"}
6767

68-
if err := state.Update(typed.YAMLObject(`{"v1": 0, "v2": 0, "v3": 0}`), fieldpath.APIVersion("v3"), "v3"); err != nil {
68+
if err := state.Update(typed.YAMLObject(`{"v1": 0, "v2": 0, "v3": 0}`), fieldpath.APIVersion("v3"), nil, "v3"); err != nil {
6969
t.Fatalf("Failed to apply: %v", err)
7070
}
7171

0 commit comments

Comments
 (0)