Skip to content

Commit 667218c

Browse files
committed
feat: scope controller name validation to manager instance
1 parent 6ad5c1d commit 667218c

File tree

5 files changed

+69
-49
lines changed

5 files changed

+69
-49
lines changed

pkg/controller/controller.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ func NewTyped[request comparable](name string, mgr manager.Manager, options Type
166166
return nil, err
167167
}
168168

169+
// Validate controller name uniqueness within this manager
170+
if options.SkipNameValidation == nil || !*options.SkipNameValidation {
171+
if validator, ok := mgr.(manager.ControllerNameValidator); ok {
172+
if err := validator.ValidateControllerName(name); err != nil {
173+
return nil, err
174+
}
175+
}
176+
}
177+
169178
// Add the controller as a Manager components
170179
return c, mgr.Add(c)
171180
}
@@ -190,12 +199,6 @@ func NewTypedUnmanaged[request comparable](name string, options TypedOptions[req
190199
return nil, fmt.Errorf("must specify Name for Controller")
191200
}
192201

193-
if options.SkipNameValidation == nil || !*options.SkipNameValidation {
194-
if err := checkName(name); err != nil {
195-
return nil, err
196-
}
197-
}
198-
199202
if options.LogConstructor == nil {
200203
log := options.Logger.WithValues(
201204
"controller", name,

pkg/controller/controller_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,30 @@ var _ = Describe("controller.Controller", func() {
136136
Expect(c2).ToNot(BeNil())
137137
})
138138

139+
It("should allow controllers with same name in different manager instances", func() {
140+
// Create two separate manager instances
141+
m1, err := manager.New(cfg, manager.Options{})
142+
Expect(err).NotTo(HaveOccurred())
143+
144+
m2, err := manager.New(cfg, manager.Options{})
145+
Expect(err).NotTo(HaveOccurred())
146+
147+
// Both managers should be able to have controllers with the same name
148+
c1, err := controller.New("c1", m1, controller.Options{Reconciler: rec})
149+
Expect(err).NotTo(HaveOccurred())
150+
Expect(c1).ToNot(BeNil())
151+
152+
c2, err := controller.New("c1", m2, controller.Options{Reconciler: rec})
153+
Expect(err).NotTo(HaveOccurred())
154+
Expect(c2).ToNot(BeNil())
155+
156+
// Verify that trying to create another controller with the same name in the same manager fails
157+
c3, err := controller.New("c1", m1, controller.Options{Reconciler: rec})
158+
Expect(err).To(HaveOccurred())
159+
Expect(err.Error()).To(ContainSubstring("controller with name c1 already exists in this manager"))
160+
Expect(c3).To(BeNil())
161+
})
162+
139163
It("should not leak goroutines when stopped", func() {
140164
currentGRs := goleak.IgnoreCurrent()
141165

pkg/controller/name.go

Lines changed: 0 additions & 43 deletions
This file was deleted.

pkg/manager/internal.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"k8s.io/apimachinery/pkg/api/meta"
3232
"k8s.io/apimachinery/pkg/runtime"
3333
kerrors "k8s.io/apimachinery/pkg/util/errors"
34+
"k8s.io/apimachinery/pkg/util/sets"
3435
"k8s.io/client-go/rest"
3536
"k8s.io/client-go/tools/leaderelection"
3637
"k8s.io/client-go/tools/leaderelection/resourcelock"
@@ -110,6 +111,10 @@ type controllerManager struct {
110111
// If none is set, it defaults to log.Log global logger.
111112
logger logr.Logger
112113

114+
// usedControllerNames tracks controller names within this manager instance
115+
usedControllerNames sets.Set[string]
116+
usedNamesMutex sync.RWMutex
117+
113118
// leaderElectionStopped is an internal channel used to signal the stopping procedure that the
114119
// LeaderElection.Run(...) function has returned and the shutdown can proceed.
115120
leaderElectionStopped chan struct{}
@@ -287,6 +292,23 @@ func (cm *controllerManager) GetControllerOptions() config.Controller {
287292
return cm.controllerConfig
288293
}
289294

295+
// ValidateControllerName validates that a controller name is unique within this manager.
296+
func (cm *controllerManager) ValidateControllerName(name string) error {
297+
cm.usedNamesMutex.Lock()
298+
defer cm.usedNamesMutex.Unlock()
299+
300+
if cm.usedControllerNames == nil {
301+
cm.usedControllerNames = sets.Set[string]{}
302+
}
303+
304+
if cm.usedControllerNames.Has(name) {
305+
return fmt.Errorf("controller with name %s already exists in this manager. Controller names must be unique within the same controller manager to avoid multiple controllers reporting the same metric. This validation can be disabled via the SkipNameValidation option", name)
306+
}
307+
308+
cm.usedControllerNames.Insert(name)
309+
return nil
310+
}
311+
290312
func (cm *controllerManager) addHealthProbeServer() error {
291313
mux := http.NewServeMux()
292314
srv := httpserver.New(mux)

pkg/manager/manager.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"k8s.io/utils/ptr"
3636
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
3737

38+
"k8s.io/apimachinery/pkg/util/sets"
3839
"sigs.k8s.io/controller-runtime/pkg/cache"
3940
"sigs.k8s.io/controller-runtime/pkg/client"
4041
"sigs.k8s.io/controller-runtime/pkg/cluster"
@@ -95,6 +96,18 @@ type Manager interface {
9596

9697
// GetControllerOptions returns controller global configuration options.
9798
GetControllerOptions() config.Controller
99+
100+
ControllerNameValidator
101+
}
102+
103+
// ControllerNameValidator defines the interface for validating controller names
104+
type ControllerNameValidator interface {
105+
// ValidateControllerName validates that a controller name is unique within this manager.
106+
// Returns an error if the name is already in use.
107+
// This validation can be skipped by setting SkipNameValidation to true in either:
108+
// - Manager options (config.Controller.SkipNameValidation) - applies to all controllers
109+
// - Controller options (controller.Options.SkipNameValidation) - overrides manager setting
110+
ValidateControllerName(name string) error
98111
}
99112

100113
// Options are the arguments for creating a new Manager.
@@ -428,6 +441,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
428441
metricsServer: metricsServer,
429442
controllerConfig: options.Controller,
430443
logger: options.Logger,
444+
usedControllerNames: sets.Set[string]{},
431445
elected: make(chan struct{}),
432446
webhookServer: options.WebhookServer,
433447
leaderElectionID: options.LeaderElectionID,

0 commit comments

Comments
 (0)