@@ -18,12 +18,12 @@ package resource
1818
1919import (
2020 "context"
21- "encoding/json"
2221
2322 corev1 "k8s.io/api/core/v1"
2423 kerrors "k8s.io/apimachinery/pkg/api/errors"
2524 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2625 "k8s.io/apimachinery/pkg/runtime"
26+ "k8s.io/apimachinery/pkg/runtime/schema"
2727 "k8s.io/apimachinery/pkg/types"
2828 "sigs.k8s.io/controller-runtime/pkg/client"
2929
@@ -121,41 +121,54 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
121121// Apply changes to the supplied object. The object will be created if it does
122122// not exist, or patched if it does. If the object does exist, it will only be
123123// patched if the passed object has the same or an empty resource version.
124- func (a * APIPatchingApplicator ) Apply (ctx context.Context , o client.Object , ao ... ApplyOption ) error {
125- m , ok := o .(metav1.Object )
126- if ! ok {
127- return errors .New ("cannot access object metadata" )
124+ func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
125+ if obj .GetName () == "" && obj .GetGenerateName () != "" {
126+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
128127 }
129128
130- if m .GetName () == "" && m .GetGenerateName () != "" {
131- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
132- }
133-
134- desired := o .DeepCopyObject ()
135-
136- err := a .client .Get (ctx , types.NamespacedName {Name : m .GetName (), Namespace : m .GetNamespace ()}, o )
129+ current := obj .DeepCopyObject ().(client.Object )
130+ err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
137131 if kerrors .IsNotFound (err ) {
138132 // TODO(negz): Apply ApplyOptions here too?
139- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
133+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
140134 }
141135 if err != nil {
142136 return errors .Wrap (err , "cannot get object" )
143137 }
144138
139+ // Note: this check would ideally not be necessary if the Apply signature
140+ // had a current object that we could us for the diff. But we have no
141+ // current and for consistency of the patch it matter that the object we get
142+ // above is the one that was originally used.
143+ if obj .GetResourceVersion () != "" && obj .GetResourceVersion () != current .GetResourceVersion () {
144+ gvr , err := groupResource (a .client , obj )
145+ if err != nil {
146+ return err
147+ }
148+ return kerrors .NewConflict (gvr , current .GetName (), errors .New ("resource version does not match" ))
149+ }
150+
145151 for _ , fn := range ao {
146- if err := fn (ctx , o , desired ); err != nil {
152+ if err := fn (ctx , current , obj ); err != nil {
147153 return err
148154 }
149155 }
150156
151157 // TODO(negz): Allow callers to override the kind of patch used.
152- return errors .Wrap (a .client .Patch (ctx , o , & patch { desired } ), "cannot patch object" )
158+ return errors .Wrap (a .client .Patch (ctx , obj , client . MergeFromWithOptions ( current , client. MergeFromWithOptimisticLock {}) ), "cannot patch object" )
153159}
154160
155- type patch struct { from runtime.Object }
156-
157- func (p * patch ) Type () types.PatchType { return types .MergePatchType }
158- func (p * patch ) Data (_ client.Object ) ([]byte , error ) { return json .Marshal (p .from ) }
161+ func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
162+ gvk , err := c .GroupVersionKindFor (o )
163+ if err != nil {
164+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group version kind of %T" , o )
165+ }
166+ m , err := c .RESTMapper ().RESTMapping (gvk .GroupKind (), gvk .Version )
167+ if err != nil {
168+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group resource of %v" , gvk )
169+ }
170+ return m .Resource .GroupResource (), nil
171+ }
159172
160173// An APIUpdatingApplicator applies changes to an object by either creating or
161174// updating it in a Kubernetes API server.
@@ -165,6 +178,9 @@ type APIUpdatingApplicator struct {
165178
166179// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
167180// object by either creating or updating it in a Kubernetes API server.
181+ //
182+ // Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
183+ // can lead to data-loss if the Golang types in this process are not up-to-date.
168184func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
169185 return & APIUpdatingApplicator {client : c }
170186}
0 commit comments