Skip to content

Commit 1b63ad4

Browse files
committed
pkg/resource: output progress logs in applicators
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent 79f1338 commit 1b63ad4

File tree

4 files changed

+122
-7
lines changed

4 files changed

+122
-7
lines changed

pkg/logging/helpers.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2023 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package logging
18+
19+
import (
20+
"fmt"
21+
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
)
24+
25+
// ForResource returns logging values for a resource.
26+
func ForResource(object client.Object) []string {
27+
ret := make([]string, 0, 10)
28+
gvk := object.GetObjectKind().GroupVersionKind()
29+
if gvk.Kind == "" {
30+
gvk.Kind = fmt.Sprintf("%T", object) // best effort for native Go types
31+
}
32+
ret = append(ret,
33+
"name", object.GetName(),
34+
"kind", gvk.Kind,
35+
"version", gvk.Version,
36+
"group", gvk.Group,
37+
)
38+
if ns := object.GetNamespace(); ns != "" {
39+
ret = append(ret, "namespace", ns)
40+
}
41+
42+
return ret
43+
}

pkg/resource/api.go

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
4849
type 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.
5456
func 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.
6171
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
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

97128
func 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.
142173
type 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.
151183
func 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.
157197
func (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

pkg/resource/providerconfig.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
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

@@ -123,13 +124,22 @@ func (fn TrackerFn) Track(ctx context.Context, mg Managed) error {
123124
// A ProviderConfigUsageTracker tracks usages of a ProviderConfig by creating or
124125
// updating the appropriate ProviderConfigUsage.
125126
type ProviderConfigUsageTracker struct {
126-
c Applicator
127-
of ProviderConfigUsage
127+
c Applicator
128+
of ProviderConfigUsage
129+
log logging.Logger
130+
client client.Client
128131
}
129132

130133
// NewProviderConfigUsageTracker creates a ProviderConfigUsageTracker.
131134
func NewProviderConfigUsageTracker(c client.Client, of ProviderConfigUsage) *ProviderConfigUsageTracker {
132-
return &ProviderConfigUsageTracker{c: NewAPIUpdatingApplicator(c), of: of}
135+
return &ProviderConfigUsageTracker{c: NewAPIUpdatingApplicator(c), of: of, log: logging.NewNopLogger(), client: c}
136+
}
137+
138+
// WithLogger adds a logger to the ProviderConfigUsageTracker.
139+
func (u *ProviderConfigUsageTracker) WithLogger(l logging.Logger) *ProviderConfigUsageTracker {
140+
u.log = l
141+
u.c = NewAPIUpdatingApplicator(u.client).WithLogger(l)
142+
return u
133143
}
134144

135145
// Track that the supplied Managed resource is using the ProviderConfig it

pkg/resource/providerconfig_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
2929
"github.com/crossplane/crossplane-runtime/pkg/errors"
30+
"github.com/crossplane/crossplane-runtime/pkg/logging"
3031
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
3132
"github.com/crossplane/crossplane-runtime/pkg/test"
3233
)
@@ -317,7 +318,7 @@ func TestTrack(t *testing.T) {
317318

318319
for name, tc := range cases {
319320
t.Run(name, func(t *testing.T) {
320-
ut := &ProviderConfigUsageTracker{c: tc.fields.c, of: tc.fields.of}
321+
ut := &ProviderConfigUsageTracker{c: tc.fields.c, of: tc.fields.of, log: logging.NewNopLogger()}
321322
got := ut.Track(tc.args.ctx, tc.args.mg)
322323
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
323324
t.Errorf("\n%s\nut.Track(...): -want error, +got error:\n%s\n", tc.reason, diff)

0 commit comments

Comments
 (0)