@@ -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,35 @@ 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 with different API versions.
72
+ type MergeFromWithOptimisticLock struct {}
73
+
74
+ func (m MergeFromWithOptimisticLock ) ApplyToMergeFrom (in * MergeFromOptions ) {
75
+ in .OptimisticLock = true
76
+ }
77
+
78
+ // MergeFromOption is some configuration that modifies options for a merge-from patch data.
79
+ type MergeFromOption interface {
80
+ // ApplyToMergeFrom applies this configuration to the given patch options.
81
+ ApplyToMergeFrom (* MergeFromOptions )
82
+ }
83
+
84
+ // MergeFromOptions contains options to generate a merge-from patch data.
85
+ type MergeFromOptions struct {
86
+ // OptimisticLock, when true, includes `metadata.resourceVersion` into the final
87
+ // patch data. If the `resourceVersion` field doesn't match what's stored,
88
+ // the operation results in a conflict and clients will need to try again.
89
+ OptimisticLock bool
90
+ }
91
+
62
92
type mergeFromPatch struct {
63
93
from runtime.Object
94
+ opts MergeFromOptions
64
95
}
65
96
66
97
// Type implements patch.
@@ -80,12 +111,47 @@ func (s *mergeFromPatch) Data(obj runtime.Object) ([]byte, error) {
80
111
return nil , err
81
112
}
82
113
83
- return jsonpatch .CreateMergePatch (originalJSON , modifiedJSON )
114
+ data , err := jsonpatch .CreateMergePatch (originalJSON , modifiedJSON )
115
+ if err != nil {
116
+ return nil , err
117
+ }
118
+
119
+ if s .opts .OptimisticLock {
120
+ dataMap := map [string ]interface {}{}
121
+ if err := json .Unmarshal (data , & dataMap ); err != nil {
122
+ return nil , err
123
+ }
124
+ fromMeta , ok := s .from .(metav1.Object )
125
+ if ! ok {
126
+ return nil , fmt .Errorf ("cannot use OptimisticLock, from object %q is not a valid metav1.Object" , s .from )
127
+ }
128
+ resourceVersion := fromMeta .GetResourceVersion ()
129
+ if len (resourceVersion ) == 0 {
130
+ return nil , fmt .Errorf ("cannot use OptimisticLock, from object %q does not have any resource version we can use" , s .from )
131
+ }
132
+ u := & unstructured.Unstructured {Object : dataMap }
133
+ u .SetResourceVersion (resourceVersion )
134
+ data , err = json .Marshal (u )
135
+ if err != nil {
136
+ return nil , err
137
+ }
138
+ }
139
+
140
+ return data , nil
84
141
}
85
142
86
143
// MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
87
144
func MergeFrom (obj runtime.Object ) Patch {
88
- return & mergeFromPatch {obj }
145
+ return & mergeFromPatch {from : obj }
146
+ }
147
+
148
+ // MergeFromWithOptions creates a Patch that patches using the merge-patch strategy with the given object as base.
149
+ func MergeFromWithOptions (obj runtime.Object , opts ... MergeFromOption ) Patch {
150
+ options := & MergeFromOptions {}
151
+ for _ , opt := range opts {
152
+ opt .ApplyToMergeFrom (options )
153
+ }
154
+ return & mergeFromPatch {from : obj , opts : * options }
89
155
}
90
156
91
157
// mergePatch uses a raw merge strategy to patch the object.
0 commit comments