@@ -17,10 +17,14 @@ limitations under the License.
1717package resource
1818
1919import (
20+ "bytes"
2021 "context"
2122
23+ jsonpatch "github.com/evanphx/json-patch"
2224 kerrors "k8s.io/apimachinery/pkg/api/errors"
25+ "k8s.io/apimachinery/pkg/runtime"
2326 "k8s.io/apimachinery/pkg/runtime/schema"
27+ "k8s.io/apimachinery/pkg/runtime/serializer/json"
2428 "k8s.io/apimachinery/pkg/types"
2529 "sigs.k8s.io/controller-runtime/pkg/client"
2630
@@ -54,7 +58,7 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
5458// Apply changes to the supplied object. The object will be created if it does
5559// not exist, or patched if it does. If the object does exist, it will only be
5660// patched if the passed object has the same or an empty resource version.
57- func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error { //nolint:gocyclo // the logic here is crucial and deserves to stay in one method
61+ func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
5862 if obj .GetName () == "" && obj .GetGenerateName () != "" {
5963 return a .client .Create (ctx , obj )
6064 }
@@ -102,6 +106,50 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
102106 return m .Resource .GroupResource (), nil
103107}
104108
109+ var emptyScheme = runtime .NewScheme () // no need to recognize any types
110+ var jsonSerializer = json .NewSerializerWithOptions (json .DefaultMetaFactory , emptyScheme , emptyScheme , json.SerializerOptions {})
111+
112+ // AdditiveMergePatchApplyOption returns an ApplyOption that makes
113+ // the Apply additive in the sense of a merge patch without null values. This is
114+ // the old behavior of the APIPatchingApplicator.
115+ //
116+ // This only works with a desired object of type *unstructured.Unstructured.
117+ //
118+ // Deprecated: replace with Server Side Apply.
119+ func AdditiveMergePatchApplyOption (_ context.Context , current , desired runtime.Object ) error {
120+ // set GVK uniformly to the desired object to make serializer happy
121+ currentGVK , desiredGVK := current .GetObjectKind ().GroupVersionKind (), desired .GetObjectKind ().GroupVersionKind ()
122+ if ! desiredGVK .Empty () && currentGVK != desiredGVK {
123+ return errors .Errorf ("cannot apply %v to %v" , desired .GetObjectKind ().GroupVersionKind (), current .GetObjectKind ().GroupVersionKind ())
124+ }
125+ desired .GetObjectKind ().SetGroupVersionKind (currentGVK )
126+
127+ // merge `desired` additively with `current`
128+ var currentBytes , desiredBytes bytes.Buffer
129+ if err := jsonSerializer .Encode (current , & currentBytes ); err != nil {
130+ return errors .Wrapf (err , "cannot marshal current %s" , HumanReadableReference (nil , current ))
131+ }
132+ if err := jsonSerializer .Encode (desired , & desiredBytes ); err != nil {
133+ return errors .Wrapf (err , "cannot marshal desired %s" , HumanReadableReference (nil , desired ))
134+ }
135+ mergedBytes , err := jsonpatch .MergePatch (currentBytes .Bytes (), desiredBytes .Bytes ())
136+ if err != nil {
137+ return errors .Wrapf (err , "cannot merge patch to %s" , HumanReadableReference (nil , desired ))
138+ }
139+
140+ // write merged object back to `desired`
141+ if _ , _ , err := jsonSerializer .Decode (mergedBytes , nil , desired ); err != nil {
142+ return errors .Wrapf (err , "cannot unmarshal merged patch to %s" , HumanReadableReference (nil , desired ))
143+ }
144+
145+ // restore empty GVK for typed objects
146+ if _ , isUnstructured := desired .(runtime.Unstructured ); ! isUnstructured {
147+ desired .GetObjectKind ().SetGroupVersionKind (schema.GroupVersionKind {})
148+ }
149+
150+ return nil
151+ }
152+
105153// An APIUpdatingApplicator applies changes to an object by either creating or
106154// updating it in a Kubernetes API server.
107155type APIUpdatingApplicator struct {
0 commit comments