Skip to content

Commit 274d7e2

Browse files
authored
✨ clusterctl: Suppress "finalizer name" API warnings in "move" command (#11173)
* ✨ util: Warning handler that discards a default set of messages * ✨ clusterctl: Suppress API warnings in "move" command * fixup! ✨ util: Warning handler that discards a default set of messages Move all handlers to one file, expressions to another * fixup! ✨ clusterctl: Suppress API warnings in "move" command Allow user to choose which warnings to hide * fixup! fixup! ✨ util: Warning handler that discards a default set of messages Make return value easier to consume * fixup! fixup! ✨ clusterctl: Suppress API warnings in "move" command Add LogAllHandler for consistent prefix, and DiscardAllHandler for convenience
1 parent 36d1add commit 274d7e2

File tree

4 files changed

+191
-1
lines changed

4 files changed

+191
-1
lines changed

cmd/clusterctl/cmd/move.go

+58-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ package cmd
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
"github.com/pkg/errors"
2324
"github.com/spf13/cobra"
25+
"k8s.io/client-go/rest"
2426

2527
"sigs.k8s.io/cluster-api/cmd/clusterctl/client"
28+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
29+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
30+
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
31+
"sigs.k8s.io/cluster-api/util/apiwarnings"
2632
)
2733

2834
type moveOptions struct {
@@ -34,6 +40,7 @@ type moveOptions struct {
3440
fromDirectory string
3541
toDirectory string
3642
dryRun bool
43+
hideAPIWarnings string
3744
}
3845

3946
var mo = &moveOptions{}
@@ -80,6 +87,8 @@ func init() {
8087
"Write Cluster API objects and all dependencies from a management cluster to directory.")
8188
moveCmd.Flags().StringVar(&mo.fromDirectory, "from-directory", "",
8289
"Read Cluster API objects and all dependencies from a directory into a management cluster.")
90+
moveCmd.Flags().StringVar(&mo.hideAPIWarnings, "hide-api-warnings", "default",
91+
"Set of API server warnings to hide. Valid sets are \"default\" (includes metadata.finalizer warnings), \"all\" , and \"none\".")
8392

8493
moveCmd.MarkFlagsMutuallyExclusive("to-directory", "to-kubeconfig")
8594
moveCmd.MarkFlagsMutuallyExclusive("from-directory", "to-directory")
@@ -98,7 +107,55 @@ func runMove() error {
98107
return errors.New("please specify a target cluster using the --to-kubeconfig flag when not using --dry-run, --to-directory or --from-directory")
99108
}
100109

101-
c, err := client.New(ctx, cfgFile)
110+
configClient, err := config.New(ctx, cfgFile)
111+
if err != nil {
112+
return err
113+
}
114+
115+
clientOptions := []client.Option{}
116+
117+
var warningHandler rest.WarningHandler
118+
switch mo.hideAPIWarnings {
119+
case "all":
120+
// Hide all warnings.
121+
warningHandler = apiwarnings.DiscardAllHandler
122+
case "default":
123+
// Hide only the default set of warnings.
124+
warningHandler = apiwarnings.DefaultHandler(logf.Log.WithName("API Server Warning"))
125+
case "none":
126+
// Hide no warnings.
127+
warningHandler = apiwarnings.LogAllHandler(logf.Log.WithName("API Server Warning"))
128+
default:
129+
return fmt.Errorf(
130+
"set of API warnings %q is unknown; choose \"default\", \"all\", or \"none\"",
131+
mo.hideAPIWarnings,
132+
)
133+
}
134+
135+
if warningHandler != nil {
136+
clientOptions = append(clientOptions,
137+
client.InjectClusterClientFactory(
138+
func(input client.ClusterClientFactoryInput) (cluster.Client, error) {
139+
return cluster.New(
140+
cluster.Kubeconfig(input.Kubeconfig),
141+
configClient,
142+
cluster.InjectYamlProcessor(input.Processor),
143+
cluster.InjectProxy(
144+
cluster.NewProxy(
145+
cluster.Kubeconfig(input.Kubeconfig),
146+
cluster.InjectWarningHandler(
147+
warningHandler,
148+
),
149+
)),
150+
), nil
151+
},
152+
),
153+
// Ensure that the same configClient used by both the client constructor, and the cluster client factory.
154+
client.InjectConfig(configClient),
155+
)
156+
}
157+
158+
c, err := client.New(ctx, cfgFile, clientOptions...)
102159
if err != nil {
103160
return err
104161
}

util/apiwarnings/expressions.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2024 The Kubernetes 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 apiwarnings
18+
19+
import (
20+
"fmt"
21+
"regexp"
22+
)
23+
24+
// DomainQualifiedFinalizerWarning is a regular expression that matches a
25+
// warning that the API server returns when a finalizer is not domain-qualified.
26+
func DomainQualifiedFinalizerWarning(domain string) regexp.Regexp {
27+
return *regexp.MustCompile(
28+
fmt.Sprintf("^metadata.finalizers:.*%s.*prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers$", domain),
29+
)
30+
}

util/apiwarnings/handler.go

+23
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"regexp"
2121

2222
"github.com/go-logr/logr"
23+
"k8s.io/client-go/rest"
24+
25+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2326
)
2427

2528
// DiscardMatchingHandler is a handler that discards API server warnings
@@ -49,3 +52,23 @@ func (h *DiscardMatchingHandler) HandleWarningHeader(code int, _, message string
4952

5053
h.Logger.Info(message)
5154
}
55+
56+
// DefaultHandler is a handler that discards warnings that are the result of
57+
// decisions made by the Cluster API project, but cannot be immediately
58+
// addressed, and are therefore not helpful to the user.
59+
func DefaultHandler(l logr.Logger) *DiscardMatchingHandler {
60+
return &DiscardMatchingHandler{
61+
Logger: l,
62+
Expressions: []regexp.Regexp{
63+
DomainQualifiedFinalizerWarning(clusterv1.GroupVersion.Group),
64+
},
65+
}
66+
}
67+
68+
// DiscardAllHandler is a handler that discards all warnings.
69+
var DiscardAllHandler = rest.NoWarnings{}
70+
71+
// LogAllHandler is a handler that logs all warnings.
72+
func LogAllHandler(l logr.Logger) rest.WarningHandler {
73+
return &DiscardMatchingHandler{Logger: l}
74+
}

util/apiwarnings/handler_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,83 @@ func TestDiscardMatchingHandler_uninitialized(t *testing.T) {
9595
h.HandleWarningHeader(299, "", "example")
9696
}).ToNot(Panic())
9797
}
98+
99+
func TestDefaultHandler(t *testing.T) {
100+
tests := []struct {
101+
name string
102+
code int
103+
message string
104+
wantLogged bool
105+
}{
106+
{
107+
name: "log, if warning does not match any expression",
108+
code: 299,
109+
message: `metadata.finalizers: "foo.example.com": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
110+
wantLogged: true,
111+
},
112+
{
113+
name: "do not log, if warning matches at least one expression",
114+
code: 299,
115+
message: `metadata.finalizers: "dockermachine.infrastructure.cluster.x-k8s.io": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
116+
wantLogged: false,
117+
},
118+
}
119+
for _, tt := range tests {
120+
t.Run(tt.name, func(t *testing.T) {
121+
g := NewWithT(t)
122+
logged := false
123+
h := DefaultHandler(
124+
funcr.New(func(_, _ string) {
125+
logged = true
126+
},
127+
funcr.Options{},
128+
),
129+
)
130+
h.HandleWarningHeader(tt.code, "", tt.message)
131+
g.Expect(logged).To(Equal(tt.wantLogged))
132+
})
133+
}
134+
}
135+
136+
func TestLogAllHandler(t *testing.T) {
137+
tests := []struct {
138+
name string
139+
code int
140+
message string
141+
wantLogged bool
142+
}{
143+
{
144+
name: "log, if code is 299, and message is not empty",
145+
code: 299,
146+
message: "warning",
147+
wantLogged: true,
148+
},
149+
{
150+
name: "do not log, if code is not 299",
151+
code: 0,
152+
message: "warning",
153+
wantLogged: false,
154+
},
155+
{
156+
name: "do not log, if message is empty",
157+
code: 299,
158+
message: "",
159+
wantLogged: false,
160+
},
161+
}
162+
for _, tt := range tests {
163+
t.Run(tt.name, func(t *testing.T) {
164+
g := NewWithT(t)
165+
logged := false
166+
h := LogAllHandler(
167+
funcr.New(func(_, _ string) {
168+
logged = true
169+
},
170+
funcr.Options{},
171+
),
172+
)
173+
h.HandleWarningHeader(tt.code, "", tt.message)
174+
g.Expect(logged).To(Equal(tt.wantLogged))
175+
})
176+
}
177+
}

0 commit comments

Comments
 (0)