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