@@ -32,24 +32,30 @@ import (
3232// Error strings.
3333const (
3434 errUpdateObject = "cannot update object"
35+
36+ // taken from k8s.io/apiserver. Not crucial to match, but for uniformity it
37+ // better should.
38+ // TODO(sttts): import from k8s.io/apiserver/pkg/registry/generic/registry when
39+ // kube has updated otel dependencies post-1.28.
40+ errOptimisticLock = "the object has been modified; please apply your changes to the latest version and try again"
3541)
3642
3743// An APIPatchingApplicator applies changes to an object by either creating or
3844// patching it in a Kubernetes API server.
3945type APIPatchingApplicator struct {
40- client client.Client
41- optionalLog logging. Logger // can be nil
46+ client client.Client
47+ log logging. Logger
4248}
4349
4450// NewAPIPatchingApplicator returns an Applicator that applies changes to an
4551// object by either creating or patching it in a Kubernetes API server.
4652func NewAPIPatchingApplicator (c client.Client ) * APIPatchingApplicator {
47- return & APIPatchingApplicator {client : c }
53+ return & APIPatchingApplicator {client : c , log : logging . NewNopLogger () }
4854}
4955
5056// WithLogger sets the logger on the APIPatchingApplicator.
5157func (a * APIPatchingApplicator ) WithLogger (l logging.Logger ) * APIPatchingApplicator {
52- a .optionalLog = l
58+ a .log = l
5359 return a
5460}
5561
@@ -58,17 +64,19 @@ func (a *APIPatchingApplicator) WithLogger(l logging.Logger) *APIPatchingApplica
5864// patched if the passed object has the same or an empty resource version.
5965func (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
6066 if obj .GetName () == "" && obj .GetGenerateName () != "" {
61- return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
67+ log := a .log .WithValues (logging .ForResource (obj ))
68+ log .Info ("creating object" )
69+ return a .client .Create (ctx , obj )
6270 }
6371
6472 current := obj .DeepCopyObject ().(client.Object )
6573 err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
6674 if kerrors .IsNotFound (err ) {
6775 // TODO(negz): Apply ApplyOptions here too?
68- return errors . Wrap ( a .client .Create (ctx , obj ), "cannot create object" )
76+ return a .client .Create (ctx , obj )
6977 }
7078 if err != nil {
71- return errors . Wrap ( err , "cannot get object" )
79+ return err
7280 }
7381
7482 // Note: this check would ideally not be necessary if the Apply signature
@@ -80,21 +88,25 @@ func (a *APIPatchingApplicator) Apply(ctx context.Context, obj client.Object, ao
8088 if err != nil {
8189 return err
8290 }
83- return kerrors .NewConflict (gvr , current .GetName (), errors .New ("resource version does not match" ))
91+ return kerrors .NewConflict (gvr , current .GetName (), errors .New (errOptimisticLock ))
8492 }
8593
8694 for _ , fn := range ao {
8795 if err := fn (ctx , current , obj ); err != nil {
88- return err
96+ return errors . Wrapf ( err , "apply option failed for %s" , HumanReadableReference ( a . client , obj ))
8997 }
9098 }
9199
92- if err := LogDiff (a .optionalLog , current , obj ); err != nil {
93- return err
100+ // log diff
101+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
102+ patchBytes , err := patch .Data (obj )
103+ if err != nil {
104+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
94105 }
106+ log := a .log .WithValues (logging .ForResource (obj ))
107+ log .WithValues ("diff" , string (patchBytes )).Info ("patching object" )
95108
96- // TODO(negz): Allow callers to override the kind of patch used.
97- return errors .Wrap (a .client .Patch (ctx , obj , client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})), "cannot patch object" )
109+ return a .client .Patch (ctx , obj , client .RawPatch (patch .Type (), patchBytes ))
98110}
99111
100112func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
@@ -112,8 +124,8 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
112124// An APIUpdatingApplicator applies changes to an object by either creating or
113125// updating it in a Kubernetes API server.
114126type APIUpdatingApplicator struct {
115- client client.Client
116- optionalLog logging. Logger // can be nil
127+ client client.Client
128+ log logging. Logger
117129}
118130
119131// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
@@ -122,43 +134,50 @@ type APIUpdatingApplicator struct {
122134// Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
123135// can lead to data-loss if the Golang types in this process are not up-to-date.
124136func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
125- return & APIUpdatingApplicator {client : c }
137+ return & APIUpdatingApplicator {client : c , log : logging . NewNopLogger () }
126138}
127139
128140// WithLogger sets the logger on the APIUpdatingApplicator.
129141func (a * APIUpdatingApplicator ) WithLogger (l logging.Logger ) * APIUpdatingApplicator {
130- a .optionalLog = l
142+ a .log = l
131143 return a
132144}
133145
134146// Apply changes to the supplied object. The object will be created if it does
135147// not exist, or updated if it does.
136148func (a * APIUpdatingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
137149 if obj .GetName () == "" && obj .GetGenerateName () != "" {
138- return errors .Wrap (a .client .Create (ctx , obj ), "cannot create object" )
150+ log := a .log .WithValues (logging .ForResource (obj ))
151+ log .Info ("creating object" )
152+ return a .client .Create (ctx , obj )
139153 }
140154
141155 current := obj .DeepCopyObject ().(client.Object )
142156 err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
143157 if kerrors .IsNotFound (err ) {
144158 // TODO(negz): Apply ApplyOptions here too?
145- return errors . Wrap ( a .client .Create (ctx , obj ), "cannot create object" )
159+ return a .client .Create (ctx , obj )
146160 }
147161 if err != nil {
148- return errors . Wrap ( err , "cannot get object" )
162+ return err
149163 }
150164
151165 for _ , fn := range ao {
152166 if err := fn (ctx , current , obj ); err != nil {
153- return err
167+ return errors . Wrapf ( err , "apply option failed for %s" , HumanReadableReference ( a . client , obj ))
154168 }
155169 }
156170
157- if err := LogDiff (a .optionalLog , current , obj ); err != nil {
158- return err
171+ // log diff
172+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
173+ patchBytes , err := patch .Data (obj )
174+ if err != nil {
175+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
159176 }
177+ log := a .log .WithValues (logging .ForResource (obj ))
178+ log .WithValues ("diff" , string (patchBytes )).Info ("updating object" )
160179
161- return errors . Wrap ( a .client .Update (ctx , obj ), "cannot update object" )
180+ return a .client .Update (ctx , obj )
162181}
163182
164183// An APIFinalizer adds and removes finalizers to and from a resource.
0 commit comments