@@ -18,11 +18,9 @@ package resource
1818
1919import (
2020 "context"
21- "encoding/json"
2221
2322 kerrors "k8s.io/apimachinery/pkg/api/errors"
24- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25- "k8s.io/apimachinery/pkg/runtime"
23+ "k8s.io/apimachinery/pkg/runtime/schema"
2624 "k8s.io/apimachinery/pkg/types"
2725 "sigs.k8s.io/controller-runtime/pkg/client"
2826
@@ -50,41 +48,54 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
5048// Apply changes to the supplied object. The object will be created if it does
5149// not exist, or patched if it does. If the object does exist, it will only be
5250// patched if the passed object has the same or an empty resource version.
53- func (a * APIPatchingApplicator ) Apply (ctx context.Context , o client.Object , ao ... ApplyOption ) error {
54- m , ok := o .(metav1.Object )
55- if ! ok {
56- return errors .New ("cannot access object metadata" )
51+ 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
52+ if obj .GetName () == "" && obj .GetGenerateName () != "" {
53+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
5754 }
5855
59- if m .GetName () == "" && m .GetGenerateName () != "" {
60- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
61- }
62-
63- desired := o .DeepCopyObject ()
64-
65- err := a .client .Get (ctx , types.NamespacedName {Name : m .GetName (), Namespace : m .GetNamespace ()}, o )
56+ current := obj .DeepCopyObject ().(client.Object )
57+ err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
6658 if kerrors .IsNotFound (err ) {
6759 // TODO(negz): Apply ApplyOptions here too?
68- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
60+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
6961 }
7062 if err != nil {
7163 return errors .Wrap (err , "cannot get object" )
7264 }
7365
66+ // Note: this check would ideally not be necessary if the Apply signature
67+ // had a current object that we could us for the diff. But we have no
68+ // current and for consistency of the patch it matters that the object we
69+ // get above is the one that was originally used.
70+ if obj .GetResourceVersion () != "" && obj .GetResourceVersion () != current .GetResourceVersion () {
71+ gvr , err := groupResource (a .client , obj )
72+ if err != nil {
73+ return err
74+ }
75+ return kerrors .NewConflict (gvr , current .GetName (), errors .New ("resource version does not match" ))
76+ }
77+
7478 for _ , fn := range ao {
75- if err := fn (ctx , o , desired ); err != nil {
79+ if err := fn (ctx , current , obj ); err != nil {
7680 return err
7781 }
7882 }
7983
8084 // TODO(negz): Allow callers to override the kind of patch used.
81- return errors .Wrap (a .client .Patch (ctx , o , & patch { desired } ), "cannot patch object" )
85+ return errors .Wrap (a .client .Patch (ctx , obj , client . MergeFromWithOptions ( current , client. MergeFromWithOptimisticLock {}) ), "cannot patch object" )
8286}
8387
84- type patch struct { from runtime.Object }
85-
86- func (p * patch ) Type () types.PatchType { return types .MergePatchType }
87- func (p * patch ) Data (_ client.Object ) ([]byte , error ) { return json .Marshal (p .from ) }
88+ func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
89+ gvk , err := c .GroupVersionKindFor (o )
90+ if err != nil {
91+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group version kind of %T" , o )
92+ }
93+ m , err := c .RESTMapper ().RESTMapping (gvk .GroupKind (), gvk .Version )
94+ if err != nil {
95+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group resource of %v" , gvk )
96+ }
97+ return m .Resource .GroupResource (), nil
98+ }
8899
89100// An APIUpdatingApplicator applies changes to an object by either creating or
90101// updating it in a Kubernetes API server.
@@ -94,40 +105,37 @@ type APIUpdatingApplicator struct {
94105
95106// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
96107// object by either creating or updating it in a Kubernetes API server.
108+ //
109+ // Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
110+ // can lead to data-loss if the Golang types in this process are not up-to-date.
97111func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
98112 return & APIUpdatingApplicator {client : c }
99113}
100114
101115// Apply changes to the supplied object. The object will be created if it does
102116// not exist, or updated if it does.
103- func (a * APIUpdatingApplicator ) Apply (ctx context.Context , o client.Object , ao ... ApplyOption ) error {
104- m , ok := o .(Object )
105- if ! ok {
106- return errors .New ("cannot access object metadata" )
107- }
108-
109- if m .GetName () == "" && m .GetGenerateName () != "" {
110- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
117+ func (a * APIUpdatingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
118+ if obj .GetName () == "" && obj .GetGenerateName () != "" {
119+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
111120 }
112121
113- current := o .DeepCopyObject ().(client.Object )
114-
115- err := a .client .Get (ctx , types.NamespacedName {Name : m .GetName (), Namespace : m .GetNamespace ()}, current )
122+ current := obj .DeepCopyObject ().(client.Object )
123+ err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
116124 if kerrors .IsNotFound (err ) {
117125 // TODO(negz): Apply ApplyOptions here too?
118- return errors .Wrap (a .client .Create (ctx , m ), "cannot create object" )
126+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
119127 }
120128 if err != nil {
121129 return errors .Wrap (err , "cannot get object" )
122130 }
123131
124132 for _ , fn := range ao {
125- if err := fn (ctx , current , m ); err != nil {
133+ if err := fn (ctx , current , obj ); err != nil {
126134 return err
127135 }
128136 }
129137
130- return errors .Wrap (a .client .Update (ctx , m ), "cannot update object" )
138+ return errors .Wrap (a .client .Update (ctx , obj ), "cannot update object" )
131139}
132140
133141// An APIFinalizer adds and removes finalizers to and from a resource.
0 commit comments