diff --git a/util/apiwarnings/doc.go b/util/apiwarnings/doc.go new file mode 100644 index 000000000000..9a316568aead --- /dev/null +++ b/util/apiwarnings/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package apiwarnings defines warning handlers used with API clients. +package apiwarnings diff --git a/util/apiwarnings/handler.go b/util/apiwarnings/handler.go new file mode 100644 index 000000000000..3e74ea5c05c5 --- /dev/null +++ b/util/apiwarnings/handler.go @@ -0,0 +1,51 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apiwarnings + +import ( + "regexp" + + "github.com/go-logr/logr" +) + +// DiscardMatchingHandler is a handler that discards API server warnings +// whose message matches any user-defined regular expressions, but otherwise +// logs warnings to the user-defined logger. +type DiscardMatchingHandler struct { + // Logger is used to log responses with the warning header. + Logger logr.Logger + + // Expressions is a slice of regular expressions used to discard warnings. + // If the warning message matches any expression, it is not logged. + Expressions []regexp.Regexp +} + +// HandleWarningHeader handles logging for responses from API server that are +// warnings with code being 299 and uses a logr.Logger for its logging purposes. +func (h *DiscardMatchingHandler) HandleWarningHeader(code int, _, message string) { + if code != 299 || message == "" { + return + } + + for _, exp := range h.Expressions { + if exp.MatchString(message) { + return + } + } + + h.Logger.Info(message) +} diff --git a/util/apiwarnings/handler_test.go b/util/apiwarnings/handler_test.go new file mode 100644 index 000000000000..f2d6abd6ebbc --- /dev/null +++ b/util/apiwarnings/handler_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apiwarnings + +import ( + "regexp" + "testing" + + "github.com/go-logr/logr/funcr" + . "github.com/onsi/gomega" +) + +func TestDiscardMatchingHandler(t *testing.T) { + tests := []struct { + name string + code int + message string + expressions []regexp.Regexp + wantLogged bool + }{ + { + name: "log, if no expressions are defined", + code: 299, + message: "non-matching warning", + wantLogged: true, + }, + { + name: "log, if warning does not match any expression", + code: 299, + message: "non-matching warning", + expressions: []regexp.Regexp{}, + wantLogged: true, + }, + { + name: "do not log, if warning matches at least one expression", + code: 299, + message: "matching warning", + expressions: []regexp.Regexp{ + *regexp.MustCompile("^matching.*"), + }, + wantLogged: false, + }, + { + name: "do not log, if code is not 299", + code: 0, + message: "warning", + expressions: []regexp.Regexp{}, + wantLogged: false, + }, + { + name: "do not log, if message is empty", + code: 299, + message: "", + expressions: []regexp.Regexp{}, + wantLogged: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + logged := false + h := DiscardMatchingHandler{ + Logger: funcr.New(func(_, _ string) { + logged = true + }, + funcr.Options{}, + ), + Expressions: tt.expressions, + } + h.HandleWarningHeader(tt.code, "", tt.message) + g.Expect(logged).To(Equal(tt.wantLogged)) + }) + } +} + +func TestDiscardMatchingHandler_uninitialized(t *testing.T) { + g := NewWithT(t) + h := DiscardMatchingHandler{} + g.Expect(func() { + // Together, the code and message value ensure that the handler logs the message. + h.HandleWarningHeader(299, "", "example") + }).ToNot(Panic()) +}