@@ -17,7 +17,11 @@ limitations under the License.
1717package client
1818
1919import (
20+ "fmt"
21+
2022 jsonpatch "github.com/evanphx/json-patch"
23+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2125 "k8s.io/apimachinery/pkg/runtime"
2226 "k8s.io/apimachinery/pkg/types"
2327 "k8s.io/apimachinery/pkg/util/json"
@@ -59,8 +63,39 @@ func ConstantPatch(patchType types.PatchType, data []byte) Patch {
5963 return RawPatch (patchType , data )
6064}
6165
66+ // MergeFromWithOptimisticLock can be used if clients want to make sure a patch
67+ // is being applied to the latest resource version of an object.
68+ //
69+ // The behavior is similar to what an Update would do, without the need to send the
70+ // whole object. Usually this method is useful if you might have multiple clients
71+ // acting on the same object and the same API version, but with different versions of the Go structs.
72+ //
73+ // For example, an "older" copy of a Widget that has fields A and B, and a "newer" copy with A, B, and C.
74+ // Sending an update using the older struct definition results in C being dropped, whereas using a patch does not.
75+ type MergeFromWithOptimisticLock struct {}
76+
77+ // ApplyToMergeFrom applies this configuration to the given patch options.
78+ func (m MergeFromWithOptimisticLock ) ApplyToMergeFrom (in * MergeFromOptions ) {
79+ in .OptimisticLock = true
80+ }
81+
82+ // MergeFromOption is some configuration that modifies options for a merge-from patch data.
83+ type MergeFromOption interface {
84+ // ApplyToMergeFrom applies this configuration to the given patch options.
85+ ApplyToMergeFrom (* MergeFromOptions )
86+ }
87+
88+ // MergeFromOptions contains options to generate a merge-from patch data.
89+ type MergeFromOptions struct {
90+ // OptimisticLock, when true, includes `metadata.resourceVersion` into the final
91+ // patch data. If the `resourceVersion` field doesn't match what's stored,
92+ // the operation results in a conflict and clients will need to try again.
93+ OptimisticLock bool
94+ }
95+
6296type mergeFromPatch struct {
6397 from runtime.Object
98+ opts MergeFromOptions
6499}
65100
66101// Type implements patch.
@@ -80,12 +115,47 @@ func (s *mergeFromPatch) Data(obj runtime.Object) ([]byte, error) {
80115 return nil , err
81116 }
82117
83- return jsonpatch .CreateMergePatch (originalJSON , modifiedJSON )
118+ data , err := jsonpatch .CreateMergePatch (originalJSON , modifiedJSON )
119+ if err != nil {
120+ return nil , err
121+ }
122+
123+ if s .opts .OptimisticLock {
124+ dataMap := map [string ]interface {}{}
125+ if err := json .Unmarshal (data , & dataMap ); err != nil {
126+ return nil , err
127+ }
128+ fromMeta , ok := s .from .(metav1.Object )
129+ if ! ok {
130+ return nil , fmt .Errorf ("cannot use OptimisticLock, from object %q is not a valid metav1.Object" , s .from )
131+ }
132+ resourceVersion := fromMeta .GetResourceVersion ()
133+ if len (resourceVersion ) == 0 {
134+ return nil , fmt .Errorf ("cannot use OptimisticLock, from object %q does not have any resource version we can use" , s .from )
135+ }
136+ u := & unstructured.Unstructured {Object : dataMap }
137+ u .SetResourceVersion (resourceVersion )
138+ data , err = json .Marshal (u )
139+ if err != nil {
140+ return nil , err
141+ }
142+ }
143+
144+ return data , nil
84145}
85146
86147// MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
87148func MergeFrom (obj runtime.Object ) Patch {
88- return & mergeFromPatch {obj }
149+ return & mergeFromPatch {from : obj }
150+ }
151+
152+ // MergeFromWithOptions creates a Patch that patches using the merge-patch strategy with the given object as base.
153+ func MergeFromWithOptions (obj runtime.Object , opts ... MergeFromOption ) Patch {
154+ options := & MergeFromOptions {}
155+ for _ , opt := range opts {
156+ opt .ApplyToMergeFrom (options )
157+ }
158+ return & mergeFromPatch {from : obj , opts : * options }
89159}
90160
91161// mergePatch uses a raw merge strategy to patch the object.
0 commit comments