@@ -18,11 +18,11 @@ package resource
1818
1919import (
2020 "context"
21- "encoding/json"
2221
2322 kerrors "k8s.io/apimachinery/pkg/api/errors"
2423 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2524 "k8s.io/apimachinery/pkg/runtime"
25+ "k8s.io/apimachinery/pkg/runtime/schema"
2626 "k8s.io/apimachinery/pkg/types"
2727 "sigs.k8s.io/controller-runtime/pkg/client"
2828
@@ -50,41 +50,54 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
5050// Apply changes to the supplied object. The object will be created if it does
5151// not exist, or patched if it does. If the object does exist, it will only be
5252// 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" )
53+ func (a * APIPatchingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
54+ if obj .GetName () == "" && obj .GetGenerateName () != "" {
55+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
5756 }
5857
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 )
58+ current := obj .DeepCopyObject ().(client.Object )
59+ err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
6660 if kerrors .IsNotFound (err ) {
6761 // TODO(negz): Apply ApplyOptions here too?
68- return errors .Wrap (a .client .Create (ctx , o ), "cannot create object" )
62+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
6963 }
7064 if err != nil {
7165 return errors .Wrap (err , "cannot get object" )
7266 }
7367
68+ // Note: this check would ideally not be necessary if the Apply signature
69+ // had a current object that we could us for the diff. But we have no
70+ // current and for consistency of the patch it matter that the object we get
71+ // above is the one that was originally used.
72+ if obj .GetResourceVersion () != "" && obj .GetResourceVersion () != current .GetResourceVersion () {
73+ gvr , err := groupResource (a .client , obj )
74+ if err != nil {
75+ return err
76+ }
77+ return kerrors .NewConflict (gvr , current .GetName (), errors .New ("resource version does not match" ))
78+ }
79+
7480 for _ , fn := range ao {
75- if err := fn (ctx , o , desired ); err != nil {
81+ if err := fn (ctx , current , obj ); err != nil {
7682 return err
7783 }
7884 }
7985
8086 // 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" )
87+ return errors .Wrap (a .client .Patch (ctx , obj , client . MergeFromWithOptions ( current , client. MergeFromWithOptimisticLock {}) ), "cannot patch object" )
8288}
8389
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 ) }
90+ func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
91+ gvk , err := c .GroupVersionKindFor (o )
92+ if err != nil {
93+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group version kind of %T" , o )
94+ }
95+ m , err := c .RESTMapper ().RESTMapping (gvk .GroupKind (), gvk .Version )
96+ if err != nil {
97+ return schema.GroupResource {}, errors .Wrapf (err , "cannot determine group resource of %v" , gvk )
98+ }
99+ return m .Resource .GroupResource (), nil
100+ }
88101
89102// An APIUpdatingApplicator applies changes to an object by either creating or
90103// updating it in a Kubernetes API server.
@@ -94,40 +107,37 @@ type APIUpdatingApplicator struct {
94107
95108// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
96109// object by either creating or updating it in a Kubernetes API server.
110+ //
111+ // Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
112+ // can lead to data-loss if the Golang types in this process are not up-to-date.
97113func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
98114 return & APIUpdatingApplicator {client : c }
99115}
100116
101117// Apply changes to the supplied object. The object will be created if it does
102118// 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" )
119+ func (a * APIUpdatingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
120+ if obj .GetName () == "" && obj .GetGenerateName () != "" {
121+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
111122 }
112123
113- current := o .DeepCopyObject ().(client.Object )
114-
115- err := a .client .Get (ctx , types.NamespacedName {Name : m .GetName (), Namespace : m .GetNamespace ()}, current )
124+ current := obj .DeepCopyObject ().(client.Object )
125+ err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
116126 if kerrors .IsNotFound (err ) {
117127 // TODO(negz): Apply ApplyOptions here too?
118- return errors .Wrap (a .client .Create (ctx , m ), "cannot create object" )
128+ return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
119129 }
120130 if err != nil {
121131 return errors .Wrap (err , "cannot get object" )
122132 }
123133
124134 for _ , fn := range ao {
125- if err := fn (ctx , current , m ); err != nil {
135+ if err := fn (ctx , current , obj ); err != nil {
126136 return err
127137 }
128138 }
129139
130- return errors .Wrap (a .client .Update (ctx , m ), "cannot update object" )
140+ return errors .Wrap (a .client .Update (ctx , obj ), "cannot update object" )
131141}
132142
133143// An APIFinalizer adds and removes finalizers to and from a resource.
0 commit comments