github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/jsonmergepatch/patch.go (about)

     1  /*
     2  Copyright 2017 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 jsonmergepatch
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  
    23  	"github.com/evanphx/json-patch"
    24  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/json"
    25  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/mergepatch"
    26  )
    27  
    28  // Create a 3-way merge patch based-on JSON merge patch.
    29  // Calculate addition-and-change patch between current and modified.
    30  // Calculate deletion patch between original and modified.
    31  func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
    32  	if len(original) == 0 {
    33  		original = []byte(`{}`)
    34  	}
    35  	if len(modified) == 0 {
    36  		modified = []byte(`{}`)
    37  	}
    38  	if len(current) == 0 {
    39  		current = []byte(`{}`)
    40  	}
    41  
    42  	addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	// Only keep addition and changes
    47  	addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	// Only keep deletion
    57  	deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	if hasConflicts {
    67  		return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj))
    68  	}
    69  	patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	var patchMap map[string]interface{}
    75  	err = json.Unmarshal(patch, &patchMap)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch)
    78  	}
    79  	meetPreconditions, err := meetPreconditions(patchMap, fns...)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	if !meetPreconditions {
    84  		return nil, mergepatch.NewErrPreconditionFailed(patchMap)
    85  	}
    86  
    87  	return patch, nil
    88  }
    89  
    90  // keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean.
    91  // It returns a filtered object and its corresponding json-encoded byte array.
    92  // It is a wrapper of func keepOrDeleteNullInObj
    93  func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
    94  	var patchMap map[string]interface{}
    95  	err := json.Unmarshal(patch, &patchMap)
    96  	if err != nil {
    97  		return nil, nil, err
    98  	}
    99  	filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	o, err := json.Marshal(filteredMap)
   104  	return o, filteredMap, err
   105  }
   106  
   107  // keepOrDeleteNullInObj will keep only the null value and delete all the others,
   108  // if keepNull is true. Otherwise, it will delete all the null value and keep the others.
   109  func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
   110  	filteredMap := make(map[string]interface{})
   111  	var err error
   112  	for key, val := range m {
   113  		switch {
   114  		case keepNull && val == nil:
   115  			filteredMap[key] = nil
   116  		case val != nil:
   117  			switch typedVal := val.(type) {
   118  			case map[string]interface{}:
   119  				// Explicitly-set empty maps are treated as values instead of empty patches
   120  				if len(typedVal) == 0 {
   121  					if !keepNull {
   122  						filteredMap[key] = typedVal
   123  					}
   124  					continue
   125  				}
   126  
   127  				var filteredSubMap map[string]interface{}
   128  				filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull)
   129  				if err != nil {
   130  					return nil, err
   131  				}
   132  
   133  				// If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key
   134  				// should not be set
   135  				if len(filteredSubMap) != 0 {
   136  					filteredMap[key] = filteredSubMap
   137  				}
   138  
   139  			case []interface{}, string, float64, bool, int64, nil:
   140  				// Lists are always replaced in Json, no need to check each entry in the list.
   141  				if !keepNull {
   142  					filteredMap[key] = val
   143  				}
   144  			default:
   145  				return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
   146  			}
   147  		}
   148  	}
   149  	return filteredMap, nil
   150  }
   151  
   152  func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) {
   153  	// Apply the preconditions to the patch, and return an error if any of them fail.
   154  	for _, fn := range fns {
   155  		if !fn(patchObj) {
   156  			return false, fmt.Errorf("precondition failed for: %v", patchObj)
   157  		}
   158  	}
   159  	return true, nil
   160  }