From 22dee73238a01a7d27f388931a06a555845a8ef7 Mon Sep 17 00:00:00 2001 From: Ondra Kupka Date: Wed, 3 Jun 2026 13:50:02 +0200 Subject: [PATCH] controllercmd,certsyncpod: add UserAgentSuffix to distinguish clients Add WithUserAgentSuffix to ControllerBuilder and expose UserAgentSuffix on ControllerCommandConfig so that static pod sidecar controllers using localhost can be identified by the kube-apiserver's pre-readiness request filter. The cert-syncer, which does not use controllercmd, sets its suffix directly in Complete(). --- pkg/controller/controllercmd/builder.go | 18 +++++++- pkg/controller/controllercmd/builder_test.go | 46 ++++++++++++++++++- pkg/controller/controllercmd/cmd.go | 7 ++- .../staticpod/certsyncpod/certsync_cmd.go | 5 +- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/pkg/controller/controllercmd/builder.go b/pkg/controller/controllercmd/builder.go index b71ceea189..4633e54eeb 100644 --- a/pkg/controller/controllercmd/builder.go +++ b/pkg/controller/controllercmd/builder.go @@ -3,12 +3,13 @@ package controllercmd import ( "context" "fmt" - "k8s.io/utils/clock" "os" "strings" "sync" "time" + "k8s.io/utils/clock" + configv1 "github.com/openshift/api/config/v1" operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" "github.com/openshift/library-go/pkg/authorization/hardcodedauthorizer" @@ -106,6 +107,10 @@ type ControllerBuilder struct { enableHTTP2 bool skipInClusterAuthLookup bool + + // userAgentSuffix is appended to the default UserAgent string on REST + // clients created by this builder. Set via WithUserAgentSuffix. + userAgentSuffix string } type TopologyDetector interface { @@ -251,6 +256,13 @@ func (b *ControllerBuilder) WithEventRecorderOptions(options record.CorrelatorOp return b } +// WithUserAgentSuffix appends the given suffix to the default UserAgent on REST +// clients created by this builder, making requests distinguishable by component. +func (b *ControllerBuilder) WithUserAgentSuffix(suffix string) *ControllerBuilder { + b.userAgentSuffix = suffix + return b +} + // WithComponentOwnerReference overrides controller reference resolution for event recording func (b *ControllerBuilder) WithComponentOwnerReference(reference *corev1.ObjectReference) *ControllerBuilder { b.componentOwnerReference = reference @@ -264,6 +276,10 @@ func (b *ControllerBuilder) Run(ctx context.Context, config *unstructured.Unstru return err } + if len(b.userAgentSuffix) > 0 { + rest.AddUserAgent(clientConfig, b.userAgentSuffix) + } + if b.fileObserver != nil { go b.fileObserver.Run(ctx.Done()) } diff --git a/pkg/controller/controllercmd/builder_test.go b/pkg/controller/controllercmd/builder_test.go index 549902dd36..c872f327c6 100644 --- a/pkg/controller/controllercmd/builder_test.go +++ b/pkg/controller/controllercmd/builder_test.go @@ -8,9 +8,12 @@ import ( "testing" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/utils/clock" + configv1 "github.com/openshift/api/config/v1" "github.com/openshift/library-go/pkg/operator/events/eventstesting" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestControllerBuilder_getOnStartedLeadingFunc(t *testing.T) { @@ -247,3 +250,44 @@ func TestInfraStatusTopologyLeaderElection(t *testing.T) { }) } } + +func TestWithUserAgentSuffix(t *testing.T) { + tests := []struct { + name string + withUserAgentSuffix string + wantSuffix string + }{ + { + name: "suffix is appended to default user agent", + withUserAgentSuffix: "cert-recovery", + wantSuffix: "/cert-recovery", + }, + { + name: "no suffix leaves user agent empty for default handling", + withUserAgentSuffix: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := NewController("test-component", nil, clock.RealClock{}). + WithUserAgentSuffix(tt.withUserAgentSuffix) + + cfg := &rest.Config{} + if len(b.userAgentSuffix) > 0 { + rest.AddUserAgent(cfg, b.userAgentSuffix) + } + + if tt.withUserAgentSuffix == "" { + if cfg.UserAgent != "" { + t.Errorf("expected empty UserAgent, got %q", cfg.UserAgent) + } + return + } + + if !strings.HasSuffix(cfg.UserAgent, tt.wantSuffix) { + t.Errorf("expected UserAgent to end with %q, but UserAgent is %q", tt.wantSuffix, cfg.UserAgent) + } + }) + } +} diff --git a/pkg/controller/controllercmd/cmd.go b/pkg/controller/controllercmd/cmd.go index 34787a976b..9dc3bf31d6 100644 --- a/pkg/controller/controllercmd/cmd.go +++ b/pkg/controller/controllercmd/cmd.go @@ -82,6 +82,10 @@ type ControllerCommandConfig struct { ComponentOwnerReference *corev1.ObjectReference healthChecks []healthz.HealthChecker eventRecorderOptions record.CorrelatorOptions + + // UserAgentSuffix is appended to the default UserAgent on REST clients, + // making requests from this component distinguishable. + UserAgentSuffix string } // NewControllerConfig returns a new ControllerCommandConfig which can be used to wire up all the boiler plate of a controller @@ -341,7 +345,8 @@ func (c *ControllerCommandConfig) StartController(ctx context.Context) error { WithHealthChecks(c.healthChecks...). WithEventRecorderOptions(c.eventRecorderOptions). WithRestartOnChange(exitOnChangeReactorCh, startingFileContent, observedFiles...). - WithComponentOwnerReference(c.ComponentOwnerReference) + WithComponentOwnerReference(c.ComponentOwnerReference). + WithUserAgentSuffix(c.UserAgentSuffix) if !c.DisableServing { builder = builder.WithServer(config.ServingInfo, config.Authentication, config.Authorization) diff --git a/pkg/operator/staticpod/certsyncpod/certsync_cmd.go b/pkg/operator/staticpod/certsyncpod/certsync_cmd.go index 05abdf8069..23c318ab97 100644 --- a/pkg/operator/staticpod/certsyncpod/certsync_cmd.go +++ b/pkg/operator/staticpod/certsyncpod/certsync_cmd.go @@ -2,10 +2,11 @@ package certsyncpod import ( "context" - "k8s.io/utils/clock" "os" "time" + "k8s.io/utils/clock" + "github.com/spf13/cobra" "k8s.io/klog/v2" @@ -119,6 +120,8 @@ func (o *CertSyncControllerOptions) Complete() error { return err } + rest.AddUserAgent(kubeConfig, "cert-syncer") + if len(o.Namespace) == 0 && len(os.Getenv("POD_NAMESPACE")) > 0 { o.Namespace = os.Getenv("POD_NAMESPACE") }