@@ -18,14 +18,19 @@ package resource
1818
1919import (
2020 "context"
21+ "encoding/json"
2122
23+ jsonpatch "github.com/evanphx/json-patch"
2224 kerrors "k8s.io/apimachinery/pkg/api/errors"
25+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+ "k8s.io/apimachinery/pkg/runtime"
2327 "k8s.io/apimachinery/pkg/runtime/schema"
2428 "k8s.io/apimachinery/pkg/types"
2529 "sigs.k8s.io/controller-runtime/pkg/client"
2630
2731 "github.com/crossplane/crossplane-runtime/pkg/errors"
2832 "github.com/crossplane/crossplane-runtime/pkg/meta"
33+ resourceunstructured "github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
2934)
3035
3136// Error strings.
@@ -54,7 +59,7 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
5459// Apply changes to the supplied object. The object will be created if it does
5560// not exist, or patched if it does. If the object does exist, it will only be
5661// 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
62+ func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
5863 if obj .GetName () == "" && obj .GetGenerateName () != "" {
5964 return a .client .Create (ctx , obj )
6065 }
@@ -102,6 +107,41 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
102107 return m .Resource .GroupResource (), nil
103108}
104109
110+ // AdditiveMergePatchApplyOption returns an ApplyOption that makes
111+ // the Apply additive in the sense of a merge patch without null values. This is
112+ // the old behavior of the APIPatchingApplicator.
113+ //
114+ // This only works with a desired object of type *unstructured.Unstructured.
115+ //
116+ // Deprecated: replace with Server Side Apply.
117+ func AdditiveMergePatchApplyOption (_ context.Context , current , desired runtime.Object ) error {
118+ u , ok := desired .(* unstructured.Unstructured )
119+ if ! ok {
120+ uw , ok := desired .(resourceunstructured.Wrapper )
121+ if ! ok {
122+ return errors .New ("desired object is not an unstructured.Unstructured" )
123+ }
124+ u = uw .GetUnstructured ()
125+ }
126+ currentBytes , err := json .Marshal (current )
127+ if err != nil {
128+ return errors .Wrapf (err , "cannot marshal current %s" , HumanReadableReference (nil , current ))
129+ }
130+ desiredBytes , err := json .Marshal (u )
131+ if err != nil {
132+ return errors .Wrapf (err , "cannot marshal desired %s" , HumanReadableReference (nil , desired ))
133+ }
134+ mergedBytes , err := jsonpatch .MergePatch (currentBytes , desiredBytes )
135+ if err != nil {
136+ return errors .Wrapf (err , "cannot merge patch to %s" , HumanReadableReference (nil , desired ))
137+ }
138+ u .Object = nil
139+ if err = json .Unmarshal (mergedBytes , & u .Object ); err != nil {
140+ return errors .Wrapf (err , "cannot unmarshal merged patch to %s" , HumanReadableReference (nil , desired ))
141+ }
142+ return nil
143+ }
144+
105145// An APIUpdatingApplicator applies changes to an object by either creating or
106146// updating it in a Kubernetes API server.
107147type APIUpdatingApplicator struct {
0 commit comments