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