github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/operatorclient/patch.go (about)

     1  package operatorclient
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	appsv1 "k8s.io/api/apps/v1"
     8  	v1 "k8s.io/api/core/v1"
     9  	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
    10  	v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    11  	"k8s.io/apimachinery/pkg/api/meta"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/runtime"
    14  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    15  )
    16  
    17  // UpdateFunction defines a function that updates an object in an Update* function. The function
    18  // provides the current instance of the object retrieved from the apiserver. The function should
    19  // return the updated object to be applied.
    20  type UpdateFunction func(current metav1.Object) (metav1.Object, error)
    21  
    22  // Update returns a default UpdateFunction implementation that passes its argument through to the
    23  // Update* function directly, ignoring the current object.
    24  //
    25  // Example usage:
    26  //
    27  // client.UpdateDaemonSet(namespace, name, types.Update(obj))
    28  func Update(obj metav1.Object) UpdateFunction {
    29  	return func(_ metav1.Object) (metav1.Object, error) {
    30  		return obj, nil
    31  	}
    32  }
    33  
    34  // PatchFunction defines a function that is used to provide patch objects for a 3-way merge. The
    35  // function provides the current instance of the object retrieved from the apiserver. The function
    36  // should return the "original" and "modified" objects (in that order) for 3-way patch computation.
    37  type PatchFunction func(current metav1.Object) (metav1.Object, metav1.Object, error)
    38  
    39  // Patch returns a default PatchFunction implementation that passes its arguments through to the
    40  // patcher directly, ignoring the current object.
    41  //
    42  // Example usage:
    43  //
    44  // client.PatchDaemonSet(namespace, name, types.Patch(original, current))
    45  func Patch(original metav1.Object, modified metav1.Object) PatchFunction {
    46  	return func(_ metav1.Object) (metav1.Object, metav1.Object, error) {
    47  		return original, modified, nil
    48  	}
    49  }
    50  
    51  // updateToPatch wraps an UpdateFunction as a PatchFunction.
    52  func updateToPatch(f UpdateFunction) PatchFunction {
    53  	return func(obj metav1.Object) (metav1.Object, metav1.Object, error) {
    54  		obj, err := f(obj)
    55  		return nil, obj, err
    56  	}
    57  }
    58  
    59  func createPatch(original, modified runtime.Object) ([]byte, error) {
    60  	originalData, err := json.Marshal(original)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	modifiedData, err := json.Marshal(modified)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, original)
    69  }
    70  
    71  func createThreeWayMergePatchPreservingCommands(original, modified, current runtime.Object) ([]byte, error) {
    72  	var datastruct runtime.Object
    73  	switch {
    74  	case original != nil:
    75  		datastruct = original
    76  	case modified != nil:
    77  		datastruct = modified
    78  	case current != nil:
    79  		datastruct = current
    80  	default:
    81  		return nil, nil // A 3-way merge of `nil`s is `nil`.
    82  	}
    83  	patchMeta, err := strategicpatch.NewPatchMetaFromStruct(datastruct)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	// Create normalized clones of objects.
    89  	original, err = cloneAndNormalizeObject(original)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	modified, err = cloneAndNormalizeObject(modified)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	current, err = cloneAndNormalizeObject(current)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	// Perform 3-way merge of annotations and labels.
   102  	if err := mergeAnnotationsAndLabels(original, modified, current); err != nil {
   103  		return nil, err
   104  	}
   105  	// Construct 3-way JSON merge patch.
   106  	originalData, err := json.Marshal(original)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	modifiedData, err := json.Marshal(modified)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	currentData, err := json.Marshal(current)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return strategicpatch.CreateThreeWayMergePatch(originalData, modifiedData, currentData, patchMeta, false /* overwrite */)
   119  }
   120  
   121  func cloneAndNormalizeObject(obj runtime.Object) (runtime.Object, error) {
   122  	if obj == nil {
   123  		return obj, nil
   124  	}
   125  
   126  	// Clone the object since it will be modified.
   127  	obj = obj.DeepCopyObject()
   128  	switch obj := obj.(type) {
   129  	case *appsv1.DaemonSet:
   130  		if obj != nil {
   131  			// These are only extracted from current; should not be considered for diffs.
   132  			obj.ObjectMeta.ResourceVersion = ""
   133  			obj.ObjectMeta.CreationTimestamp = metav1.Time{}
   134  			obj.Status = appsv1.DaemonSetStatus{}
   135  		}
   136  	case *appsv1.Deployment:
   137  		if obj != nil {
   138  			// These are only extracted from current; should not be considered for diffs.
   139  			obj.ObjectMeta.ResourceVersion = ""
   140  			obj.ObjectMeta.CreationTimestamp = metav1.Time{}
   141  			obj.Status = appsv1.DeploymentStatus{}
   142  		}
   143  	case *v1.Service:
   144  		if obj != nil {
   145  			// These are only extracted from current; should not be considered for diffs.
   146  			obj.ObjectMeta.ResourceVersion = ""
   147  			obj.ObjectMeta.CreationTimestamp = metav1.Time{}
   148  			obj.Status = v1.ServiceStatus{}
   149  			// ClusterIP for service is immutable, so cannot patch.
   150  			obj.Spec.ClusterIP = ""
   151  		}
   152  	case *extensionsv1beta1.Ingress:
   153  		if obj != nil {
   154  			// These are only extracted from current; should not be considered for diffs.
   155  			obj.ObjectMeta.ResourceVersion = ""
   156  			obj.ObjectMeta.CreationTimestamp = metav1.Time{}
   157  			obj.Status = extensionsv1beta1.IngressStatus{}
   158  		}
   159  	case *v1beta1ext.CustomResourceDefinition:
   160  		if obj != nil {
   161  			// These are only extracted from current; should not be considered for diffs.
   162  			obj.ObjectMeta.ResourceVersion = ""
   163  			obj.ObjectMeta.CreationTimestamp = metav1.Time{}
   164  			obj.ObjectMeta.SelfLink = ""
   165  			obj.ObjectMeta.UID = ""
   166  			obj.Status = v1beta1ext.CustomResourceDefinitionStatus{}
   167  		}
   168  	default:
   169  		return nil, fmt.Errorf("unhandled type: %T", obj)
   170  	}
   171  	return obj, nil
   172  }
   173  
   174  // mergeAnnotationsAndLabels performs a 3-way merge of all annotations and labels using custom
   175  // 3-way merge logic defined in mergeMaps() below.
   176  func mergeAnnotationsAndLabels(original, modified, current runtime.Object) error {
   177  	if original == nil || modified == nil || current == nil {
   178  		return nil
   179  	}
   180  
   181  	accessor := meta.NewAccessor()
   182  	if err := mergeMaps(original, modified, current, accessor.Annotations, accessor.SetAnnotations); err != nil {
   183  		return err
   184  	}
   185  	if err := mergeMaps(original, modified, current, accessor.Labels, accessor.SetLabels); err != nil {
   186  		return err
   187  	}
   188  
   189  	switch current := current.(type) {
   190  	case *appsv1.DaemonSet:
   191  		getter := func(obj runtime.Object) (map[string]string, error) {
   192  			return obj.(*appsv1.DaemonSet).Spec.Template.Annotations, nil
   193  		}
   194  		setter := func(obj runtime.Object, val map[string]string) error {
   195  			obj.(*appsv1.DaemonSet).Spec.Template.Annotations = val
   196  			return nil
   197  		}
   198  		if err := mergeMaps(original, modified, current, getter, setter); err != nil {
   199  			return err
   200  		}
   201  		getter = func(obj runtime.Object) (map[string]string, error) {
   202  			return obj.(*appsv1.DaemonSet).Spec.Template.Labels, nil
   203  		}
   204  		setter = func(obj runtime.Object, val map[string]string) error {
   205  			obj.(*appsv1.DaemonSet).Spec.Template.Labels = val
   206  			return nil
   207  		}
   208  		if err := mergeMaps(original, modified, current, getter, setter); err != nil {
   209  			return err
   210  		}
   211  	case *appsv1.Deployment:
   212  		getter := func(obj runtime.Object) (map[string]string, error) {
   213  			return obj.(*appsv1.Deployment).Spec.Template.Annotations, nil
   214  		}
   215  		setter := func(obj runtime.Object, val map[string]string) error {
   216  			obj.(*appsv1.Deployment).Spec.Template.Annotations = val
   217  			return nil
   218  		}
   219  		if err := mergeMaps(original, modified, current, getter, setter); err != nil {
   220  			return err
   221  		}
   222  		getter = func(obj runtime.Object) (map[string]string, error) {
   223  			return obj.(*appsv1.Deployment).Spec.Template.Labels, nil
   224  		}
   225  		setter = func(obj runtime.Object, val map[string]string) error {
   226  			obj.(*appsv1.Deployment).Spec.Template.Labels = val
   227  			return nil
   228  		}
   229  		if err := mergeMaps(original, modified, current, getter, setter); err != nil {
   230  			return err
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  // mergeMaps creates a patch using createThreeWayMapPatch and if the patch is non-empty applies
   237  // the patch to the input. The getter and setter are used to access the map inside the given
   238  // objects.
   239  func mergeMaps(original, modified, current runtime.Object, getter func(runtime.Object) (map[string]string, error), setter func(runtime.Object, map[string]string) error) error {
   240  	originalMap, err := getter(original)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	modifiedMap, err := getter(modified)
   245  	if err != nil {
   246  		return err
   247  	}
   248  	currentMap, err := getter(current)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	patch, err := createThreeWayMapPatch(originalMap, modifiedMap, currentMap)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	if len(patch) == 0 {
   258  		return nil // nothing to apply.
   259  	}
   260  	modifiedMap = applyMapPatch(originalMap, currentMap, patch)
   261  
   262  	if err := setter(original, originalMap); err != nil {
   263  		return err
   264  	}
   265  	if err := setter(modified, modifiedMap); err != nil {
   266  		return err
   267  	}
   268  	return setter(current, currentMap)
   269  }
   270  
   271  // applyMapPatch creates a copy of current and applies the three-way map patch to it.
   272  func applyMapPatch(original, current map[string]string, patch map[string]interface{}) map[string]string {
   273  	merged := make(map[string]string, len(current))
   274  	for k, v := range current {
   275  		merged[k] = v
   276  	}
   277  	for k, v := range patch {
   278  		if v == nil {
   279  			delete(merged, k)
   280  		} else {
   281  			merged[k] = v.(string)
   282  			if _, ok := current[k]; !ok {
   283  				// If we are re-adding something that may have already been in original then ensure it is
   284  				// removed from `original` to avoid a conflict in upstream patch code.
   285  				delete(original, k)
   286  			}
   287  		}
   288  	}
   289  	return merged
   290  }
   291  
   292  // createThreeWayMapPatch constructs a 3-way patch between original, modified, and current. The
   293  // patch contains only keys that are added, keys that are removed (with their values set to nil) or
   294  // keys whose values are modified. Returns an error if there is a conflict for any key.
   295  //
   296  // The behavior is defined as follows:
   297  //
   298  // - If an item is present in modified, ensure it exists in current.
   299  // - If an item is present in original and removed in modified, remove it from current.
   300  // - If an item is present only in current, leave it as-is.
   301  //
   302  // This effectively "enforces" that all items present in modified are present in current, and all
   303  // items deleted from original => modified are deleted in current.
   304  //
   305  // The following will cause a conflict:
   306  //
   307  // (1) An item was deleted from original => modified but modified from original => current.
   308  // (2) An item was modified differently from original => modified and original => current.
   309  func createThreeWayMapPatch(original, modified, current map[string]string) (map[string]interface{}, error) {
   310  	// Create union of keys.
   311  	keys := make(map[string]struct{})
   312  	for k := range original {
   313  		keys[k] = struct{}{}
   314  	}
   315  	for k := range modified {
   316  		keys[k] = struct{}{}
   317  	}
   318  	for k := range current {
   319  		keys[k] = struct{}{}
   320  	}
   321  
   322  	// Create patch according to rules.
   323  	patch := make(map[string]interface{})
   324  	for k := range keys {
   325  		oVal, oOk := original[k]
   326  		mVal, mOk := modified[k]
   327  		cVal, cOk := current[k]
   328  
   329  		switch {
   330  		case oOk && mOk && cOk:
   331  			// present in all three.
   332  			if mVal != cVal {
   333  				if oVal != cVal {
   334  					// conflict type 2: changed to different values in modified and current.
   335  					return nil, fmt.Errorf("conflict at key %v: original = %v, modified = %v, current = %v", k, oVal, mVal, cVal)
   336  				}
   337  				patch[k] = mVal
   338  			}
   339  		case !oOk && mOk && cOk:
   340  			// added in modified and current.
   341  			if mVal != cVal {
   342  				// conflict type 2: added different values in modified and current.
   343  				return nil, fmt.Errorf("conflict at key %v: original = <absent>, modified = %v, current = %v", k, mVal, cVal)
   344  			}
   345  		case oOk && !mOk && cOk:
   346  			// deleted in modified.
   347  			if oVal != cVal {
   348  				// conflict type 1: changed from original to current, removed in modified.
   349  				return nil, fmt.Errorf("conflict at key %v, original = %v, modified = <absent>, current = %v", k, oVal, cVal)
   350  			}
   351  			patch[k] = nil
   352  		case oOk && mOk && !cOk:
   353  			// deleted in current.
   354  			patch[k] = mVal
   355  		case !oOk && !mOk && cOk:
   356  			// only exists in current.
   357  		case !oOk && mOk && !cOk:
   358  			// added in modified.
   359  			patch[k] = mVal
   360  		case oOk && !mOk && !cOk:
   361  			// deleted in both modified and current.
   362  		case !oOk && !mOk && !cOk:
   363  			// unreachable.
   364  		}
   365  	}
   366  	return patch, nil
   367  }