...

Source file src/sigs.k8s.io/controller-runtime/pkg/client/patch.go

Documentation: sigs.k8s.io/controller-runtime/pkg/client

     1  /*
     2  Copyright 2018 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 client
    18  
    19  import (
    20  	"fmt"
    21  
    22  	jsonpatch "github.com/evanphx/json-patch/v5"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	"k8s.io/apimachinery/pkg/util/json"
    25  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    26  )
    27  
    28  var (
    29  	// Apply uses server-side apply to patch the given object.
    30  	Apply Patch = applyPatch{}
    31  
    32  	// Merge uses the raw object as a merge patch, without modifications.
    33  	// Use MergeFrom if you wish to compute a diff instead.
    34  	Merge Patch = mergePatch{}
    35  )
    36  
    37  type patch struct {
    38  	patchType types.PatchType
    39  	data      []byte
    40  }
    41  
    42  // Type implements Patch.
    43  func (s *patch) Type() types.PatchType {
    44  	return s.patchType
    45  }
    46  
    47  // Data implements Patch.
    48  func (s *patch) Data(obj Object) ([]byte, error) {
    49  	return s.data, nil
    50  }
    51  
    52  // RawPatch constructs a new Patch with the given PatchType and data.
    53  func RawPatch(patchType types.PatchType, data []byte) Patch {
    54  	return &patch{patchType, data}
    55  }
    56  
    57  // MergeFromWithOptimisticLock can be used if clients want to make sure a patch
    58  // is being applied to the latest resource version of an object.
    59  //
    60  // The behavior is similar to what an Update would do, without the need to send the
    61  // whole object. Usually this method is useful if you might have multiple clients
    62  // acting on the same object and the same API version, but with different versions of the Go structs.
    63  //
    64  // For example, an "older" copy of a Widget that has fields A and B, and a "newer" copy with A, B, and C.
    65  // Sending an update using the older struct definition results in C being dropped, whereas using a patch does not.
    66  type MergeFromWithOptimisticLock struct{}
    67  
    68  // ApplyToMergeFrom applies this configuration to the given patch options.
    69  func (m MergeFromWithOptimisticLock) ApplyToMergeFrom(in *MergeFromOptions) {
    70  	in.OptimisticLock = true
    71  }
    72  
    73  // MergeFromOption is some configuration that modifies options for a merge-from patch data.
    74  type MergeFromOption interface {
    75  	// ApplyToMergeFrom applies this configuration to the given patch options.
    76  	ApplyToMergeFrom(*MergeFromOptions)
    77  }
    78  
    79  // MergeFromOptions contains options to generate a merge-from patch data.
    80  type MergeFromOptions struct {
    81  	// OptimisticLock, when true, includes `metadata.resourceVersion` into the final
    82  	// patch data. If the `resourceVersion` field doesn't match what's stored,
    83  	// the operation results in a conflict and clients will need to try again.
    84  	OptimisticLock bool
    85  }
    86  
    87  type mergeFromPatch struct {
    88  	patchType   types.PatchType
    89  	createPatch func(originalJSON, modifiedJSON []byte, dataStruct interface{}) ([]byte, error)
    90  	from        Object
    91  	opts        MergeFromOptions
    92  }
    93  
    94  // Type implements Patch.
    95  func (s *mergeFromPatch) Type() types.PatchType {
    96  	return s.patchType
    97  }
    98  
    99  // Data implements Patch.
   100  func (s *mergeFromPatch) Data(obj Object) ([]byte, error) {
   101  	original := s.from
   102  	modified := obj
   103  
   104  	if s.opts.OptimisticLock {
   105  		version := original.GetResourceVersion()
   106  		if len(version) == 0 {
   107  			return nil, fmt.Errorf("cannot use OptimisticLock, object %q does not have any resource version we can use", original)
   108  		}
   109  
   110  		original = original.DeepCopyObject().(Object)
   111  		original.SetResourceVersion("")
   112  
   113  		modified = modified.DeepCopyObject().(Object)
   114  		modified.SetResourceVersion(version)
   115  	}
   116  
   117  	originalJSON, err := json.Marshal(original)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	modifiedJSON, err := json.Marshal(modified)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	data, err := s.createPatch(originalJSON, modifiedJSON, obj)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	return data, nil
   133  }
   134  
   135  func createMergePatch(originalJSON, modifiedJSON []byte, _ interface{}) ([]byte, error) {
   136  	return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
   137  }
   138  
   139  func createStrategicMergePatch(originalJSON, modifiedJSON []byte, dataStruct interface{}) ([]byte, error) {
   140  	return strategicpatch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, dataStruct)
   141  }
   142  
   143  // MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
   144  // The difference between MergeFrom and StrategicMergeFrom lays in the handling of modified list fields.
   145  // When using MergeFrom, existing lists will be completely replaced by new lists.
   146  // When using StrategicMergeFrom, the list field's `patchStrategy` is respected if specified in the API type,
   147  // e.g. the existing list is not replaced completely but rather merged with the new one using the list's `patchMergeKey`.
   148  // See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ for more details on
   149  // the difference between merge-patch and strategic-merge-patch.
   150  func MergeFrom(obj Object) Patch {
   151  	return &mergeFromPatch{patchType: types.MergePatchType, createPatch: createMergePatch, from: obj}
   152  }
   153  
   154  // MergeFromWithOptions creates a Patch that patches using the merge-patch strategy with the given object as base.
   155  // See MergeFrom for more details.
   156  func MergeFromWithOptions(obj Object, opts ...MergeFromOption) Patch {
   157  	options := &MergeFromOptions{}
   158  	for _, opt := range opts {
   159  		opt.ApplyToMergeFrom(options)
   160  	}
   161  	return &mergeFromPatch{patchType: types.MergePatchType, createPatch: createMergePatch, from: obj, opts: *options}
   162  }
   163  
   164  // StrategicMergeFrom creates a Patch that patches using the strategic-merge-patch strategy with the given object as base.
   165  // The difference between MergeFrom and StrategicMergeFrom lays in the handling of modified list fields.
   166  // When using MergeFrom, existing lists will be completely replaced by new lists.
   167  // When using StrategicMergeFrom, the list field's `patchStrategy` is respected if specified in the API type,
   168  // e.g. the existing list is not replaced completely but rather merged with the new one using the list's `patchMergeKey`.
   169  // See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ for more details on
   170  // the difference between merge-patch and strategic-merge-patch.
   171  // Please note, that CRDs don't support strategic-merge-patch, see
   172  // https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility
   173  func StrategicMergeFrom(obj Object, opts ...MergeFromOption) Patch {
   174  	options := &MergeFromOptions{}
   175  	for _, opt := range opts {
   176  		opt.ApplyToMergeFrom(options)
   177  	}
   178  	return &mergeFromPatch{patchType: types.StrategicMergePatchType, createPatch: createStrategicMergePatch, from: obj, opts: *options}
   179  }
   180  
   181  // mergePatch uses a raw merge strategy to patch the object.
   182  type mergePatch struct{}
   183  
   184  // Type implements Patch.
   185  func (p mergePatch) Type() types.PatchType {
   186  	return types.MergePatchType
   187  }
   188  
   189  // Data implements Patch.
   190  func (p mergePatch) Data(obj Object) ([]byte, error) {
   191  	// NB(directxman12): we might technically want to be using an actual encoder
   192  	// here (in case some more performant encoder is introduced) but this is
   193  	// correct and sufficient for our uses (it's what the JSON serializer in
   194  	// client-go does, more-or-less).
   195  	return json.Marshal(obj)
   196  }
   197  
   198  // applyPatch uses server-side apply to patch the object.
   199  type applyPatch struct{}
   200  
   201  // Type implements Patch.
   202  func (p applyPatch) Type() types.PatchType {
   203  	return types.ApplyPatchType
   204  }
   205  
   206  // Data implements Patch.
   207  func (p applyPatch) Data(obj Object) ([]byte, error) {
   208  	// NB(directxman12): we might technically want to be using an actual encoder
   209  	// here (in case some more performant encoder is introduced) but this is
   210  	// correct and sufficient for our uses (it's what the JSON serializer in
   211  	// client-go does, more-or-less).
   212  	return json.Marshal(obj)
   213  }
   214  

View as plain text