forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
element.go
423 lines (356 loc) · 12.1 KB
/
element.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"fmt"
)
// Element contains the record, local, and remote value for a field in an object
// and metadata about the field read from openapi.
// Calling Merge on an element will apply the passed in strategy to Element -
// e.g. either replacing the whole element with the local copy or merging each
// of the recorded, local and remote fields of the element.
type Element interface {
// FieldMeta specifies which merge strategy to use for this element
FieldMeta
// Merge merges the recorded, local and remote values in the element using the Strategy
// provided as an argument. Calls the type specific method on the Strategy - following the
// "Accept" method from the "Visitor" pattern.
// e.g. Merge on a ListElement will call Strategy.MergeList(self)
// Returns the Result of the merged elements
Merge(Strategy) (Result, error)
// HasRecorded returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasRecorded() bool
// GetRecorded returns the field value from the recorded source of the object
GetRecorded() interface{}
// HasLocal returns true if the field was explicitly
// present in the recorded source. This is to differentiate between
// undefined and set to null
HasLocal() bool
// GetLocal returns the field value from the local source of the object
GetLocal() interface{}
// HasRemote returns true if the field was explicitly
// present in the remote source. This is to differentiate between
// undefined and set to null
HasRemote() bool
// GetRemote returns the field value from the remote source of the object
GetRemote() interface{}
}
// FieldMeta defines the strategy used to apply a Patch for an element
type FieldMeta interface {
// GetFieldMergeType returns the type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
// TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct
// Address this in a follow up in the PR to introduce retainkeys strategy
GetFieldMergeType() string
// GetFieldMergeKeys returns the merge key to use when the MergeType is "merge" and underlying type is a list
GetFieldMergeKeys() MergeKeys
// GetFieldType returns the openapi field type - e.g. primitive, array, map, type, reference
GetFieldType() string
}
// FieldMetaImpl implements FieldMeta
type FieldMetaImpl struct {
// The type of merge strategy to use for this field
// maybe "merge", "replace" or "retainkeys"
// TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct
// Address this in a follow up in the PR to introduce retainkeys strategy
MergeType string
// The merge key to use when the MergeType is "merge" and underlying type is a list
MergeKeys MergeKeys
// The openapi type of the field - "list", "primitive", "map"
Type string
// Name contains of the field
Name string
}
// GetFieldMergeType implements FieldMeta.GetFieldMergeType
func (s FieldMetaImpl) GetFieldMergeType() string {
return s.MergeType
}
// GetFieldMergeKeys implements FieldMeta.GetFieldMergeKeys
func (s FieldMetaImpl) GetFieldMergeKeys() MergeKeys {
return s.MergeKeys
}
// GetFieldType implements FieldMeta.GetFieldType
func (s FieldMetaImpl) GetFieldType() string {
return s.Type
}
// MergeKeyValue records the value of the mergekey for an item in a list
type MergeKeyValue map[string]string
// Equal returns true if the MergeKeyValues share the same value,
// representing the same item in a list
func (v MergeKeyValue) Equal(o MergeKeyValue) bool {
if len(v) != len(o) {
return false
}
for key, v1 := range v {
if v2, found := o[key]; !found || v1 != v2 {
return false
}
}
return true
}
// MergeKeys is the set of fields on an object that uniquely identify
// and is used when merging lists to identify the "same" object
// independent of the ordering of the objects
type MergeKeys []string
// GetMergeKeyValue parses the MergeKeyValue from an item in a list
func (mk MergeKeys) GetMergeKeyValue(i interface{}) (MergeKeyValue, error) {
result := MergeKeyValue{}
if len(mk) <= 0 {
return result, fmt.Errorf("merge key must have at least 1 value to merge")
}
m, ok := i.(map[string]interface{})
if !ok {
return result, fmt.Errorf("cannot use mergekey %v for primitive item in list %v", mk, i)
}
for _, field := range mk {
if value, found := m[field]; !found {
result[field] = ""
} else {
result[field] = fmt.Sprintf("%v", value)
}
}
return result, nil
}
type source int
const (
recorded source = iota
local
remote
)
// CombinedPrimitiveSlice implements a slice of primitives
type CombinedPrimitiveSlice struct {
Items []*PrimitiveListItem
}
// PrimitiveListItem represents a single value in a slice of primitives
type PrimitiveListItem struct {
// Value is the value of the primitive, should match recorded, local and remote
Value interface{}
RawElementData
}
// Contains returns true if the slice contains the l
func (s *CombinedPrimitiveSlice) lookup(l interface{}) *PrimitiveListItem {
val := fmt.Sprintf("%v", l)
for _, i := range s.Items {
if fmt.Sprintf("%v", i.Value) == val {
return i
}
}
return nil
}
func (s *CombinedPrimitiveSlice) upsert(l interface{}) *PrimitiveListItem {
// Return the item if it exists
if item := s.lookup(l); item != nil {
return item
}
// Otherwise create a new item and append to the list
item := &PrimitiveListItem{
Value: l,
}
s.Items = append(s.Items, item)
return item
}
// UpsertRecorded adds l to the slice. If there is already a value of l in the
// slice for either the local or remote, set on that value as the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedPrimitiveSlice) UpsertRecorded(l interface{}) {
v := s.upsert(l)
v.recorded = l
v.recordedSet = true
}
// UpsertLocal adds l to the slice. If there is already a value of l in the
// slice for either the recorded or remote, set on that value as the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedPrimitiveSlice) UpsertLocal(l interface{}) {
v := s.upsert(l)
v.local = l
v.localSet = true
}
// UpsertRemote adds l to the slice. If there is already a value of l in the
// slice for either the local or recorded, set on that value as the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedPrimitiveSlice) UpsertRemote(l interface{}) {
v := s.upsert(l)
v.remote = l
v.remoteSet = true
}
// ListItem represents a single value in a slice of maps or types
type ListItem struct {
// KeyValue is the merge key value of the item
KeyValue MergeKeyValue
// RawElementData contains the field values
RawElementData
}
// CombinedMapSlice is a slice of maps or types with merge keys
type CombinedMapSlice struct {
Items []*ListItem
}
// Lookup returns the ListItem matching the merge key, or nil if not found.
func (s *CombinedMapSlice) lookup(v MergeKeyValue) *ListItem {
for _, i := range s.Items {
if i.KeyValue.Equal(v) {
return i
}
}
return nil
}
func (s *CombinedMapSlice) upsert(key MergeKeys, l interface{}) (*ListItem, error) {
// Get the identity of the item
val, err := key.GetMergeKeyValue(l)
if err != nil {
return nil, err
}
// Return the item if it exists
if item := s.lookup(val); item != nil {
return item, nil
}
// Otherwise create a new item and append to the list
item := &ListItem{
KeyValue: val,
}
s.Items = append(s.Items, item)
return item, nil
}
// UpsertRecorded adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the local or remote, set l the recorded value
// Otherwise append a new item to the list with the recorded value.
func (s *CombinedMapSlice) UpsertRecorded(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.recorded = l
item.recordedSet = true
return nil
}
// UpsertLocal adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or remote, set l the local value
// Otherwise append a new item to the list with the local value.
func (s *CombinedMapSlice) UpsertLocal(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.local = l
item.localSet = true
return nil
}
// UpsertRemote adds l to the slice. If there is already a value of l sharing
// l's merge key in the slice for either the recorded or local, set l the remote value
// Otherwise append a new item to the list with the remote value.
func (s *CombinedMapSlice) UpsertRemote(key MergeKeys, l interface{}) error {
item, err := s.upsert(key, l)
if err != nil {
return err
}
item.remote = l
item.remoteSet = true
return nil
}
// IsDrop returns true if the field represented by e should be dropped from the merged object
func IsDrop(e Element) bool {
// Specified in the last value recorded value and since deleted from the local
removed := e.HasRecorded() && !e.HasLocal()
// Specified locally and explicitly set to null
setToNil := e.HasLocal() && e.GetLocal() == nil
return removed || setToNil
}
// IsAdd returns true if the field represented by e should have the local value directly
// added to the merged object instead of merging the recorded, local and remote values
func IsAdd(e Element) bool {
// If it isn't already present in the remote value and is present in the local value
return e.HasLocal() && !e.HasRemote()
}
// NewRawElementData returns a new RawElementData, setting IsSet to true for
// non-nil values, and leaving IsSet false for nil values.
// Note: use this only when you want a nil-value to be considered "unspecified"
// (ignore) and not "unset" (deleted).
func NewRawElementData(recorded, local, remote interface{}) RawElementData {
data := RawElementData{}
if recorded != nil {
data.SetRecorded(recorded)
}
if local != nil {
data.SetLocal(local)
}
if remote != nil {
data.SetRemote(remote)
}
return data
}
// RawElementData contains the raw recorded, local and remote data
// and metadata about whethere or not each was set
type RawElementData struct {
HasElementData
recorded interface{}
local interface{}
remote interface{}
}
// SetRecorded sets the recorded value
func (b *RawElementData) SetRecorded(value interface{}) {
b.recorded = value
b.recordedSet = true
}
// SetLocal sets the recorded value
func (b *RawElementData) SetLocal(value interface{}) {
b.local = value
b.localSet = true
}
// SetRemote sets the recorded value
func (b *RawElementData) SetRemote(value interface{}) {
b.remote = value
b.remoteSet = true
}
// GetRecorded implements Element.GetRecorded
func (b RawElementData) GetRecorded() interface{} {
// https://golang.org/doc/faq#nil_error
if b.recorded == nil {
return nil
}
return b.recorded
}
// GetLocal implements Element.GetLocal
func (b RawElementData) GetLocal() interface{} {
// https://golang.org/doc/faq#nil_error
if b.local == nil {
return nil
}
return b.local
}
// GetRemote implements Element.GetRemote
func (b RawElementData) GetRemote() interface{} {
// https://golang.org/doc/faq#nil_error
if b.remote == nil {
return nil
}
return b.remote
}
// HasElementData contains whether a field was set in the recorded, local and remote sources
type HasElementData struct {
recordedSet bool
localSet bool
remoteSet bool
}
// HasRecorded implements Element.HasRecorded
func (e HasElementData) HasRecorded() bool {
return e.recordedSet
}
// HasLocal implements Element.HasLocal
func (e HasElementData) HasLocal() bool {
return e.localSet
}
// HasRemote implements Element.HasRemote
func (e HasElementData) HasRemote() bool {
return e.remoteSet
}