Skip to content

Commit d3d351f

Browse files
author
Antoine Pelisse
committed
Integrate unions with Update/Apply workflows
1 parent 047e817 commit d3d351f

File tree

3 files changed

+203
-7
lines changed

3 files changed

+203
-7
lines changed

internal/fixture/state.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manag
9292
if err != nil {
9393
return err
9494
}
95-
managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
95+
newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
9696
if err != nil {
9797
return err
9898
}
99-
s.Live = tv
99+
s.Live = newObj
100100
s.Managers = managers
101101

102102
return nil

merge/union_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package merge_test
18+
19+
import (
20+
"testing"
21+
22+
"sigs.k8s.io/structured-merge-diff/fieldpath"
23+
. "sigs.k8s.io/structured-merge-diff/internal/fixture"
24+
"sigs.k8s.io/structured-merge-diff/merge"
25+
"sigs.k8s.io/structured-merge-diff/typed"
26+
)
27+
28+
var unionFieldsParser = func() typed.ParseableType {
29+
parser, err := typed.NewParser(`types:
30+
- name: unionFields
31+
struct:
32+
fields:
33+
- name: numeric
34+
type:
35+
scalar: numeric
36+
- name: string
37+
type:
38+
scalar: string
39+
- name: type
40+
type:
41+
scalar: string
42+
union:
43+
discriminator: type
44+
fields:
45+
- fieldName: numeric
46+
discriminatedBy: Numeric
47+
- fieldName: string
48+
discriminatedBy: String`)
49+
if err != nil {
50+
panic(err)
51+
}
52+
return parser.Type("unionFields")
53+
}()
54+
55+
func TestUnion(t *testing.T) {
56+
tests := map[string]TestCase{
57+
"union_apply_owns_discriminator": {
58+
Ops: []Operation{
59+
Apply{
60+
Manager: "default",
61+
APIVersion: "v1",
62+
Object: `
63+
numeric: 1
64+
`,
65+
},
66+
},
67+
Object: `
68+
numeric: 1
69+
type: Numeric
70+
`,
71+
Managed: fieldpath.ManagedFields{
72+
"default": &fieldpath.VersionedSet{
73+
Set: _NS(
74+
_P("numeric"), _P("type"),
75+
),
76+
APIVersion: "v1",
77+
},
78+
},
79+
},
80+
"union_apply_without_discriminator_conflict": {
81+
Ops: []Operation{
82+
Update{
83+
Manager: "controller",
84+
APIVersion: "v1",
85+
Object: `
86+
string: "some string"
87+
`,
88+
},
89+
Apply{
90+
Manager: "default",
91+
APIVersion: "v1",
92+
Object: `
93+
numeric: 1
94+
`,
95+
Conflicts: merge.Conflicts{
96+
merge.Conflict{Manager: "controller", Path: _P("type")},
97+
},
98+
},
99+
},
100+
Object: `
101+
string: "some string"
102+
type: String
103+
`,
104+
Managed: fieldpath.ManagedFields{
105+
"controller": &fieldpath.VersionedSet{
106+
Set: _NS(
107+
_P("string"), _P("type"),
108+
),
109+
APIVersion: "v1",
110+
},
111+
},
112+
},
113+
"union_apply_with_null_value": {
114+
Ops: []Operation{
115+
Apply{
116+
Manager: "default",
117+
APIVersion: "v1",
118+
Object: `
119+
type: Numeric
120+
string: null
121+
numeric: 1
122+
`,
123+
},
124+
},
125+
},
126+
}
127+
128+
for name, test := range tests {
129+
t.Run(name, func(t *testing.T) {
130+
if err := test.Test(unionFieldsParser); err != nil {
131+
t.Fatal(err)
132+
}
133+
})
134+
}
135+
}
136+
137+
func TestUnionErrors(t *testing.T) {
138+
tests := map[string]TestCase{
139+
"union_apply_two": {
140+
Ops: []Operation{
141+
Apply{
142+
Manager: "default",
143+
APIVersion: "v1",
144+
Object: `
145+
numeric: 1
146+
string: "some string"
147+
`,
148+
},
149+
},
150+
},
151+
"union_apply_two_and_discriminator": {
152+
Ops: []Operation{
153+
Apply{
154+
Manager: "default",
155+
APIVersion: "v1",
156+
Object: `
157+
type: Numeric
158+
string: "some string"
159+
numeric: 1
160+
`,
161+
},
162+
},
163+
},
164+
"union_apply_wrong_discriminator": {
165+
Ops: []Operation{
166+
Apply{
167+
Manager: "default",
168+
APIVersion: "v1",
169+
Object: `
170+
type: Numeric
171+
string: "some string"
172+
`,
173+
},
174+
},
175+
},
176+
}
177+
178+
for name, test := range tests {
179+
t.Run(name, func(t *testing.T) {
180+
if test.Test(unionFieldsParser) == nil {
181+
t.Fatal("Should fail")
182+
}
183+
})
184+
}
185+
}

merge/update.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,19 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
119119
// that you intend to persist (after applying the patch if this is for a
120120
// PATCH call), and liveObject must be the original object (empty if
121121
// this is a CREATE call).
122-
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
123-
var err error
122+
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
123+
newObject, err := liveObject.NormalizeUnions(newObject)
124+
if err != nil {
125+
return nil, fieldpath.ManagedFields{}, err
126+
}
124127
managers = shallowCopyManagers(managers)
125128
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
126129
if err != nil {
127-
return fieldpath.ManagedFields{}, err
130+
return nil, fieldpath.ManagedFields{}, err
128131
}
129132
compare, err := liveObject.Compare(newObject)
130133
if err != nil {
131-
return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
134+
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
132135
}
133136
if _, ok := managers[manager]; !ok {
134137
managers[manager] = &fieldpath.VersionedSet{
@@ -140,18 +143,26 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
140143
if managers[manager].Set.Empty() {
141144
delete(managers, manager)
142145
}
143-
return managers, nil
146+
return newObject, managers, nil
144147
}
145148

146149
// Apply should be called when Apply is run, given the current object as
147150
// well as the configuration that is applied. This will merge the object
148151
// and return it.
149152
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
150153
managers = shallowCopyManagers(managers)
154+
configObject, err := configObject.Empty().NormalizeUnions(configObject)
155+
if err != nil {
156+
return nil, fieldpath.ManagedFields{}, err
157+
}
151158
newObject, err := liveObject.Merge(configObject)
152159
if err != nil {
153160
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
154161
}
162+
newObject, err = liveObject.NormalizeUnions(newObject)
163+
if err != nil {
164+
return nil, fieldpath.ManagedFields{}, err
165+
}
155166
lastSet := managers[manager]
156167
set, err := configObject.ToFieldSet()
157168
if err != nil {

0 commit comments

Comments
 (0)