Skip to content

Commit 94c515d

Browse files
committed
Deprecate options.WarningHandler
- Deprecation and future removal does not change the default behavior, which is to de-duplicate and surface warnings. - Uses config.WarningHandler to override default behavior. Describes this in the client.New godoc, adds an example test to demonstrate it, and a unit test to verify it.
1 parent 6785442 commit 94c515d

File tree

3 files changed

+105
-11
lines changed

3 files changed

+105
-11
lines changed

pkg/client/client.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ type Options struct {
5252

5353
// WarningHandler is used to configure the warning handler responsible for
5454
// surfacing and handling warnings messages sent by the API server.
55+
//
56+
// Deprecated: This is deprecated and will be removed in a future release.
57+
// Deprecation and future removal does not change the default behavior, which is to
58+
// de-duplicate and surface warnings. For custom behavior, pass config.WarningHandler
59+
// to New().
5560
WarningHandler WarningHandlerOptions
5661

5762
// DryRun instructs the client to only perform dry run requests.
@@ -61,6 +66,8 @@ type Options struct {
6166
// WarningHandlerOptions are options for configuring a
6267
// warning handler for the client which is responsible
6368
// for surfacing API Server warnings.
69+
//
70+
// Deprecated: This is deprecated and will be removed in a future release.
6471
type WarningHandlerOptions struct {
6572
// SuppressWarnings decides if the warnings from the
6673
// API server are suppressed or surfaced in the client.
@@ -91,6 +98,11 @@ type NewClientFunc func(config *rest.Config, options Options) (Client, error)
9198

9299
// New returns a new Client using the provided config and Options.
93100
//
101+
// By default, the client surfaces warnings returned by the server. To
102+
// suppress warnings, set config.WarningHandler = rest.NoWarnings{}. To
103+
// define custom behavior, implement the rest.WarningHandler interface.
104+
// For consistent log formatting, use pkg/log.Log in the implementation.
105+
//
94106
// The client's read behavior is determined by Options.Cache.
95107
// If either Options.Cache or Options.Cache.Reader is nil,
96108
// the client reads directly from the API server.
@@ -124,15 +136,19 @@ func newClient(config *rest.Config, options Options) (*client, error) {
124136
config.UserAgent = rest.DefaultKubernetesUserAgent()
125137
}
126138

127-
// By default, we de-duplicate and surface warnings.
128-
config.WarningHandler = log.NewKubeAPIWarningLogger(
129-
log.Log.WithName("KubeAPIWarningLogger"),
130-
log.KubeAPIWarningLoggerOptions{
131-
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
132-
},
133-
)
134-
if options.WarningHandler.SuppressWarnings {
135-
config.WarningHandler = rest.NoWarnings{}
139+
if config.WarningHandler == nil {
140+
// By default, we de-duplicate and surface warnings.
141+
config.WarningHandler = log.NewKubeAPIWarningLogger(
142+
log.Log.WithName("KubeAPIWarningLogger"),
143+
log.KubeAPIWarningLoggerOptions{
144+
// When we remove WarningHandlerOptions, the below will always be set to true.
145+
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
146+
},
147+
)
148+
// When we remove WarningHandlerOptions, we will remove the below condition.
149+
if options.WarningHandler.SuppressWarnings {
150+
config.WarningHandler = rest.NoWarnings{}
151+
}
136152
}
137153

138154
// Use the rest HTTP client for the provided config if unset

pkg/client/client_test.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package client_test
1818

1919
import (
2020
"bufio"
21+
"bytes"
2122
"context"
2223
"encoding/json"
2324
"errors"
@@ -43,6 +44,7 @@ import (
4344
"k8s.io/apimachinery/pkg/runtime/schema"
4445
"k8s.io/apimachinery/pkg/types"
4546
kscheme "k8s.io/client-go/kubernetes/scheme"
47+
"k8s.io/client-go/rest"
4648
"k8s.io/utils/ptr"
4749

4850
"sigs.k8s.io/controller-runtime/examples/crd/pkg"
@@ -229,7 +231,64 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
229231
})
230232

231233
Describe("WarningHandler", func() {
232-
It("should log warnings when warning suppression is disabled", func() {
234+
It("should log warnings with config.WarningHandler, if one is defined", func() {
235+
cache := &fakeReader{}
236+
237+
testCfg := rest.CopyConfig(cfg)
238+
239+
var testLog bytes.Buffer
240+
testCfg.WarningHandler = rest.NewWarningWriter(&testLog, rest.WarningWriterOptions{})
241+
242+
cl, err := client.New(testCfg, client.Options{Cache: &client.CacheOptions{Reader: cache, DisableFor: []client.Object{&corev1.Namespace{}}}})
243+
Expect(err).NotTo(HaveOccurred())
244+
Expect(cl).NotTo(BeNil())
245+
246+
tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "wh-defined"}}
247+
tns, err = clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
248+
Expect(err).NotTo(HaveOccurred())
249+
Expect(tns).NotTo(BeNil())
250+
defer deleteNamespace(ctx, tns)
251+
252+
toCreate := &pkg.ChaosPod{
253+
ObjectMeta: metav1.ObjectMeta{
254+
Name: "example",
255+
Namespace: tns.Name,
256+
},
257+
// The ChaosPod CRD does not define Status, so the field is unknown to the API server,
258+
// but field validation is not strict by default, so the API server returns a warning,
259+
// and we need a warning to check whether suppression works.
260+
Status: pkg.ChaosPodStatus{},
261+
}
262+
err = cl.Create(ctx, toCreate)
263+
Expect(err).NotTo(HaveOccurred())
264+
Expect(cl).NotTo(BeNil())
265+
266+
scannerTestLog := bufio.NewScanner(&testLog)
267+
for scannerTestLog.Scan() {
268+
line := scannerTestLog.Text()
269+
if strings.Contains(
270+
line,
271+
"unknown field \"status\"",
272+
) {
273+
return
274+
}
275+
}
276+
defer Fail("expected to find one API server warning logged the config.WarningHandler")
277+
278+
scanner := bufio.NewScanner(&log)
279+
for scanner.Scan() {
280+
line := scanner.Text()
281+
if strings.Contains(
282+
line,
283+
"unknown field \"status\"",
284+
) {
285+
defer Fail("expected to find zero API server warnings in the client log")
286+
break
287+
}
288+
}
289+
})
290+
291+
It("(deprecated behavior) should log warnings when warning suppression is disabled", func() {
233292
cache := &fakeReader{}
234293
cl, err := client.New(cfg, client.Options{
235294
WarningHandler: client.WarningHandlerOptions{SuppressWarnings: false}, Cache: &client.CacheOptions{Reader: cache, DisableFor: []client.Object{&corev1.Namespace{}}},
@@ -270,7 +329,7 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
270329
defer Fail("expected to find one API server warning in the client log")
271330
})
272331

273-
It("should not log warnings when warning suppression is enabled", func() {
332+
It("(deprecated behavior) should not log warnings when warning suppression is enabled", func() {
274333
cache := &fakeReader{}
275334
cl, err := client.New(cfg, client.Options{
276335
WarningHandler: client.WarningHandlerOptions{SuppressWarnings: true}, Cache: &client.CacheOptions{Reader: cache, DisableFor: []client.Object{&corev1.Namespace{}}},

pkg/client/example_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/apimachinery/pkg/runtime/schema"
3030
"k8s.io/apimachinery/pkg/types"
3131
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
32+
"k8s.io/client-go/rest"
3233

3334
"sigs.k8s.io/controller-runtime/pkg/client"
3435
"sigs.k8s.io/controller-runtime/pkg/client/config"
@@ -56,6 +57,24 @@ func ExampleNew() {
5657
}
5758
}
5859

60+
func ExampleNew_suppress_warnings() {
61+
cfg := config.GetConfigOrDie()
62+
cfg.WarningHandler = rest.NoWarnings{}
63+
cl, err := client.New(cfg, client.Options{})
64+
if err != nil {
65+
fmt.Println("failed to create client")
66+
os.Exit(1)
67+
}
68+
69+
podList := &corev1.PodList{}
70+
71+
err = cl.List(context.Background(), podList, client.InNamespace("default"))
72+
if err != nil {
73+
fmt.Printf("failed to list pods in namespace default: %v\n", err)
74+
os.Exit(1)
75+
}
76+
}
77+
5978
// This example shows how to use the client with typed and unstructured objects to retrieve an object.
6079
func ExampleClient_get() {
6180
// Using a typed object.

0 commit comments

Comments
 (0)