@@ -29,6 +29,7 @@ import (
2929 "sigs.k8s.io/controller-runtime/pkg/client"
3030
3131 "github.com/crossplane/crossplane-runtime/pkg/errors"
32+ "github.com/crossplane/crossplane-runtime/pkg/logging"
3233 "github.com/crossplane/crossplane-runtime/pkg/meta"
3334)
3435
@@ -47,26 +48,39 @@ const (
4748// patching it in a Kubernetes API server.
4849type APIPatchingApplicator struct {
4950 client client.Client
51+ log logging.Logger
5052}
5153
5254// NewAPIPatchingApplicator returns an Applicator that applies changes to an
5355// object by either creating or patching it in a Kubernetes API server.
5456func NewAPIPatchingApplicator (c client.Client ) * APIPatchingApplicator {
55- return & APIPatchingApplicator {client : c }
57+ return & APIPatchingApplicator {client : c , log : logging .NewNopLogger ()}
58+ }
59+
60+ // WithLogger sets the logger on the APIPatchingApplicator. The logger logs
61+ // client operations including diffs of objects that are patched. Diffs of
62+ // secrets are redacted.
63+ func (a * APIPatchingApplicator ) WithLogger (l logging.Logger ) * APIPatchingApplicator {
64+ a .log = l
65+ return a
5666}
5767
5868// Apply changes to the supplied object. The object will be created if it does
5969// not exist, or patched if it does. If the object does exist, it will only be
6070// patched if the passed object has the same or an empty resource version.
6171func (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
72+ log := a .log .WithValues (logging .ForResource (obj ))
73+
6274 if obj .GetName () == "" && obj .GetGenerateName () != "" {
75+ log .Info ("creating object" )
6376 return a .client .Create (ctx , obj )
6477 }
6578
6679 current := obj .DeepCopyObject ().(client.Object )
6780 err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
6881 if kerrors .IsNotFound (err ) {
6982 // TODO(negz): Apply ApplyOptions here too?
83+ log .Info ("creating object" )
7084 return a .client .Create (ctx , obj )
7185 }
7286 if err != nil {
@@ -91,7 +105,24 @@ func (a *APIPatchingApplicator) Apply(ctx context.Context, obj client.Object, ao
91105 }
92106 }
93107
94- return a .client .Patch (ctx , obj , client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {}))
108+ // log diff
109+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
110+ patchBytes , err := patch .Data (obj )
111+ if err != nil {
112+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
113+ }
114+ if len (patchBytes ) == 0 {
115+ return nil
116+ }
117+ secretGVK := schema.GroupVersionKind {Group : "v1" , Version : "Secret" , Kind : "Secret" }
118+ if obj .GetObjectKind ().GroupVersionKind () == secretGVK {
119+ // TODO(sttts): be more clever and only redact the secret data
120+ log .WithValues ("diff" , "**REDACTED**" ).Info ("patching object" )
121+ } else {
122+ log .WithValues ("diff" , string (patchBytes )).Info ("patching object" )
123+ }
124+
125+ return a .client .Patch (ctx , obj , client .RawPatch (patch .Type (), patchBytes ))
95126}
96127
97128func groupResource (c client.Client , o client.Object ) (schema.GroupResource , error ) {
@@ -141,6 +172,7 @@ func AdditiveMergePatchApplyOption(_ context.Context, current, desired runtime.O
141172// updating it in a Kubernetes API server.
142173type APIUpdatingApplicator struct {
143174 client client.Client
175+ log logging.Logger
144176}
145177
146178// NewAPIUpdatingApplicator returns an Applicator that applies changes to an
@@ -149,20 +181,32 @@ type APIUpdatingApplicator struct {
149181// Deprecated: Use NewAPIPatchingApplicator instead. The updating applicator
150182// can lead to data-loss if the Golang types in this process are not up-to-date.
151183func NewAPIUpdatingApplicator (c client.Client ) * APIUpdatingApplicator {
152- return & APIUpdatingApplicator {client : c }
184+ return & APIUpdatingApplicator {client : c , log : logging .NewNopLogger ()}
185+ }
186+
187+ // WithLogger sets the logger on the APIUpdatingApplicator. The logger logs
188+ // client operations including diffs of objects that are patched. Diffs of
189+ // secrets are redacted.
190+ func (a * APIUpdatingApplicator ) WithLogger (l logging.Logger ) * APIUpdatingApplicator {
191+ a .log = l
192+ return a
153193}
154194
155195// Apply changes to the supplied object. The object will be created if it does
156196// not exist, or updated if it does.
157197func (a * APIUpdatingApplicator ) Apply (ctx context.Context , obj client.Object , ao ... ApplyOption ) error {
198+ log := a .log .WithValues (logging .ForResource (obj ))
199+
158200 if obj .GetName () == "" && obj .GetGenerateName () != "" {
201+ log .Info ("creating object" )
159202 return a .client .Create (ctx , obj )
160203 }
161204
162205 current := obj .DeepCopyObject ().(client.Object )
163206 err := a .client .Get (ctx , types.NamespacedName {Name : obj .GetName (), Namespace : obj .GetNamespace ()}, current )
164207 if kerrors .IsNotFound (err ) {
165208 // TODO(negz): Apply ApplyOptions here too?
209+ log .Info ("creating object" )
166210 return a .client .Create (ctx , obj )
167211 }
168212 if err != nil {
@@ -175,6 +219,23 @@ func (a *APIUpdatingApplicator) Apply(ctx context.Context, obj client.Object, ao
175219 }
176220 }
177221
222+ // log diff
223+ patch := client .MergeFromWithOptions (current , client.MergeFromWithOptimisticLock {})
224+ patchBytes , err := patch .Data (obj )
225+ if err != nil {
226+ return errors .Wrapf (err , "failed to diff %s" , HumanReadableReference (a .client , obj ))
227+ }
228+ if len (patchBytes ) == 0 {
229+ return nil
230+ }
231+ secretGVK := schema.GroupVersionKind {Group : "v1" , Version : "Secret" , Kind : "Secret" }
232+ if obj .GetObjectKind ().GroupVersionKind () == secretGVK {
233+ // TODO(sttts): be more clever and only redact the secret data
234+ log .WithValues ("diff" , "**REDACTED**" ).Info ("patching object" )
235+ } else {
236+ log .WithValues ("diff" , string (patchBytes )).Info ("patching object" )
237+ }
238+
178239 return a .client .Update (ctx , obj )
179240}
180241
0 commit comments