@@ -17,7 +17,11 @@ limitations under the License.
17
17
package client
18
18
19
19
import (
20
+ "fmt"
21
+
20
22
jsonpatch "github.com/evanphx/json-patch"
23
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21
25
"k8s.io/apimachinery/pkg/runtime"
22
26
"k8s.io/apimachinery/pkg/types"
23
27
"k8s.io/apimachinery/pkg/util/json"
@@ -59,8 +63,39 @@ func ConstantPatch(patchType types.PatchType, data []byte) Patch {
59
63
return RawPatch (patchType , data )
60
64
}
61
65
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
+
62
96
type mergeFromPatch struct {
63
97
from runtime.Object
98
+ opts MergeFromOptions
64
99
}
65
100
66
101
// Type implements patch.
@@ -80,12 +115,47 @@ func (s *mergeFromPatch) Data(obj runtime.Object) ([]byte, error) {
80
115
return nil , err
81
116
}
82
117
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
84
145
}
85
146
86
147
// MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
87
148
func 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 }
89
159
}
90
160
91
161
// mergePatch uses a raw merge strategy to patch the object.
0 commit comments