mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	- Add DetectConflictor interface on Element level. Implemented it for particular elements. - If Options.FailOnConflict is enabled, Merge will detect conflict by invoking doConflictDecect for particular element, returning ConflictError with details. - Add tests, including use case examples and illustration. For example: list, map, and complicated combination.
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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 strategy
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 
 | |
| 	"k8s.io/kubernetes/pkg/kubectl/apply"
 | |
| )
 | |
| 
 | |
| func createMergeStrategy(options Options, strategic *delegatingStrategy) mergeStrategy {
 | |
| 	return mergeStrategy{
 | |
| 		strategic,
 | |
| 		options,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // mergeStrategy merges the values in an Element into a single Result
 | |
| type mergeStrategy struct {
 | |
| 	strategic *delegatingStrategy
 | |
| 	options   Options
 | |
| }
 | |
| 
 | |
| // MergeList merges the lists in a ListElement into a single Result
 | |
| func (v mergeStrategy) MergeList(e apply.ListElement) (apply.Result, error) {
 | |
| 	// No merge logic if adding or deleting a field
 | |
| 	if result, done := v.doAddOrDelete(e); done {
 | |
| 		return result, nil
 | |
| 	}
 | |
| 	// Detect conflict in ListElement
 | |
| 	if err := v.doConflictDetect(e); err != nil {
 | |
| 		return apply.Result{}, err
 | |
| 	}
 | |
| 	// Merge each item in the list and append it to the list
 | |
| 	merged := []interface{}{}
 | |
| 	for _, value := range e.Values {
 | |
| 		// Recursively merge the list element before adding the value to the list
 | |
| 		m, err := value.Merge(v.strategic)
 | |
| 		if err != nil {
 | |
| 			return apply.Result{}, err
 | |
| 		}
 | |
| 
 | |
| 		switch m.Operation {
 | |
| 		case apply.SET:
 | |
| 			// Keep the list item value
 | |
| 			merged = append(merged, m.MergedResult)
 | |
| 		case apply.DROP:
 | |
| 			// Drop the list item value
 | |
| 		default:
 | |
| 			panic(fmt.Errorf("Unexpected result operation type %+v", m))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(merged) == 0 {
 | |
| 		// If the list is empty, return a nil entry
 | |
| 		return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
 | |
| 	}
 | |
| 	// Return the merged list, and tell the caller to keep it
 | |
| 	return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
 | |
| }
 | |
| 
 | |
| // MergeMap merges the maps in a MapElement into a single Result
 | |
| func (v mergeStrategy) MergeMap(e apply.MapElement) (apply.Result, error) {
 | |
| 	// No merge logic if adding or deleting a field
 | |
| 	if result, done := v.doAddOrDelete(e); done {
 | |
| 		return result, nil
 | |
| 	}
 | |
| 	// Detect conflict in MapElement
 | |
| 	if err := v.doConflictDetect(e); err != nil {
 | |
| 		return apply.Result{}, err
 | |
| 	}
 | |
| 	return v.doMergeMap(e.GetValues())
 | |
| }
 | |
| 
 | |
| // MergeMap merges the type instances in a TypeElement into a single Result
 | |
| func (v mergeStrategy) MergeType(e apply.TypeElement) (apply.Result, error) {
 | |
| 	// No merge logic if adding or deleting a field
 | |
| 	if result, done := v.doAddOrDelete(e); done {
 | |
| 		return result, nil
 | |
| 	}
 | |
| 	// Detect conflict in TypeElement
 | |
| 	if err := v.doConflictDetect(e); err != nil {
 | |
| 		return apply.Result{}, err
 | |
| 	}
 | |
| 	return v.doMergeMap(e.GetValues())
 | |
| }
 | |
| 
 | |
| // do merges a recorded, local and remote map into a new object
 | |
| func (v mergeStrategy) doMergeMap(e map[string]apply.Element) (apply.Result, error) {
 | |
| 
 | |
| 	// Merge each item in the list
 | |
| 	merged := map[string]interface{}{}
 | |
| 	for key, value := range e {
 | |
| 		// Recursively merge the map element before adding the value to the map
 | |
| 		result, err := value.Merge(v.strategic)
 | |
| 		if err != nil {
 | |
| 			return apply.Result{}, err
 | |
| 		}
 | |
| 
 | |
| 		switch result.Operation {
 | |
| 		case apply.SET:
 | |
| 			// Keep the map item value
 | |
| 			merged[key] = result.MergedResult
 | |
| 		case apply.DROP:
 | |
| 			// Drop the map item value
 | |
| 		default:
 | |
| 			panic(fmt.Errorf("Unexpected result operation type %+v", result))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Return the merged map, and tell the caller to keep it
 | |
| 	if len(merged) == 0 {
 | |
| 		// Special case the empty map to set the field value to nil, but keep the field key
 | |
| 		// This is how the tests expect the structures to look when parsed from yaml
 | |
| 		return apply.Result{Operation: apply.SET, MergedResult: nil}, nil
 | |
| 	}
 | |
| 	return apply.Result{Operation: apply.SET, MergedResult: merged}, nil
 | |
| }
 | |
| 
 | |
| func (v mergeStrategy) doAddOrDelete(e apply.Element) (apply.Result, bool) {
 | |
| 	if apply.IsAdd(e) {
 | |
| 		return apply.Result{Operation: apply.SET, MergedResult: e.GetLocal()}, true
 | |
| 	}
 | |
| 
 | |
| 	// Delete the List
 | |
| 	if apply.IsDrop(e) {
 | |
| 		return apply.Result{Operation: apply.DROP}, true
 | |
| 	}
 | |
| 
 | |
| 	return apply.Result{}, false
 | |
| }
 | |
| 
 | |
| // MergePrimitive returns and error.  Primitive elements can't be merged, only replaced.
 | |
| func (v mergeStrategy) MergePrimitive(diff apply.PrimitiveElement) (apply.Result, error) {
 | |
| 	return apply.Result{}, fmt.Errorf("Cannot merge primitive element %v", diff.Name)
 | |
| }
 | |
| 
 | |
| // MergeEmpty returns an empty result
 | |
| func (v mergeStrategy) MergeEmpty(diff apply.EmptyElement) (apply.Result, error) {
 | |
| 	return apply.Result{Operation: apply.SET}, nil
 | |
| }
 | |
| 
 | |
| // doConflictDetect returns error if element has conflict
 | |
| func (v mergeStrategy) doConflictDetect(e apply.Element) error {
 | |
| 	return v.strategic.doConflictDetect(e)
 | |
| }
 | |
| 
 | |
| var _ apply.Strategy = &mergeStrategy{}
 |