Skip to content

Commit 385eeaa

Browse files
authored
Merge pull request #3141 from alvaroaleman/factor-out
⚠️ NewTypedUnmanaged: Stop requiring a manager
2 parents 6eb011f + d9ff283 commit 385eeaa

File tree

5 files changed

+111
-35
lines changed

5 files changed

+111
-35
lines changed

pkg/config/controller.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ limitations under the License.
1616

1717
package config
1818

19-
import "time"
19+
import (
20+
"time"
2021

21-
// Controller contains configuration options for a controller.
22+
"github.com/go-logr/logr"
23+
)
24+
25+
// Controller contains configuration options for controllers. It only includes options
26+
// that makes sense for a set of controllers and is used for defaulting the options
27+
// of multiple controllers.
2228
type Controller struct {
2329
// SkipNameValidation allows skipping the name validation that ensures that every controller name is unique.
2430
// Unique controller names are important to get unique metrics and logs for a controller.
@@ -59,4 +65,7 @@ type Controller struct {
5965
//
6066
// Note: This flag is disabled by default until a future version. It's currently in beta.
6167
UsePriorityQueue *bool
68+
69+
// Logger is the logger controllers should use.
70+
Logger logr.Logger
6271
}

pkg/controller/controller.go

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/klog/v2"
2727
"k8s.io/utils/ptr"
2828

29+
"sigs.k8s.io/controller-runtime/pkg/config"
2930
"sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
3031
"sigs.k8s.io/controller-runtime/pkg/internal/controller"
3132
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -80,13 +81,53 @@ type TypedOptions[request comparable] struct {
8081
// Only use a custom NewQueue if you know what you are doing.
8182
NewQueue func(controllerName string, rateLimiter workqueue.TypedRateLimiter[request]) workqueue.TypedRateLimitingInterface[request]
8283

84+
// Logger will be used to build a default LogConstructor if unset.
85+
Logger logr.Logger
86+
8387
// LogConstructor is used to construct a logger used for this controller and passed
8488
// to each reconciliation via the context field.
8589
LogConstructor func(request *request) logr.Logger
90+
91+
// UsePriorityQueue configures the controllers queue to use the controller-runtime provided
92+
// priority queue.
93+
//
94+
// Note: This flag is disabled by default until a future version. It's currently in beta.
95+
UsePriorityQueue *bool
96+
}
97+
98+
// DefaultFromConfig defaults the config from a config.Controller
99+
func (options *TypedOptions[request]) DefaultFromConfig(config config.Controller) {
100+
if options.Logger.GetSink() == nil {
101+
options.Logger = config.Logger
102+
}
103+
104+
if options.SkipNameValidation == nil {
105+
options.SkipNameValidation = config.SkipNameValidation
106+
}
107+
108+
if options.MaxConcurrentReconciles <= 0 && config.MaxConcurrentReconciles > 0 {
109+
options.MaxConcurrentReconciles = config.MaxConcurrentReconciles
110+
}
111+
112+
if options.CacheSyncTimeout == 0 && config.CacheSyncTimeout > 0 {
113+
options.CacheSyncTimeout = config.CacheSyncTimeout
114+
}
115+
116+
if options.UsePriorityQueue == nil {
117+
options.UsePriorityQueue = config.UsePriorityQueue
118+
}
119+
120+
if options.RecoverPanic == nil {
121+
options.RecoverPanic = config.RecoverPanic
122+
}
123+
124+
if options.NeedLeaderElection == nil {
125+
options.NeedLeaderElection = config.NeedLeaderElection
126+
}
86127
}
87128

88-
// Controller implements a Kubernetes API. A Controller manages a work queue fed reconcile.Requests
89-
// from source.Sources. Work is performed through the reconcile.Reconciler for each enqueued item.
129+
// Controller implements an API. A Controller manages a work queue fed reconcile.Requests
130+
// from source.Sources. Work is performed through the reconcile.Reconciler for each enqueued item.
90131
// Work typically is reads and writes Kubernetes objects to make the system state match the state specified
91132
// in the object Spec.
92133
type Controller = TypedController[reconcile.Request]
@@ -119,7 +160,8 @@ func New(name string, mgr manager.Manager, options Options) (Controller, error)
119160
//
120161
// The name must be unique as it is used to identify the controller in metrics and logs.
121162
func NewTyped[request comparable](name string, mgr manager.Manager, options TypedOptions[request]) (TypedController[request], error) {
122-
c, err := NewTypedUnmanaged(name, mgr, options)
163+
options.DefaultFromConfig(mgr.GetControllerOptions())
164+
c, err := NewTypedUnmanaged(name, options)
123165
if err != nil {
124166
return nil, err
125167
}
@@ -132,14 +174,14 @@ func NewTyped[request comparable](name string, mgr manager.Manager, options Type
132174
// caller is responsible for starting the returned controller.
133175
//
134176
// The name must be unique as it is used to identify the controller in metrics and logs.
135-
func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller, error) {
136-
return NewTypedUnmanaged(name, mgr, options)
177+
func NewUnmanaged(name string, options Options) (Controller, error) {
178+
return NewTypedUnmanaged(name, options)
137179
}
138180

139181
// NewTypedUnmanaged returns a new typed controller without adding it to the manager.
140182
//
141183
// The name must be unique as it is used to identify the controller in metrics and logs.
142-
func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, options TypedOptions[request]) (TypedController[request], error) {
184+
func NewTypedUnmanaged[request comparable](name string, options TypedOptions[request]) (TypedController[request], error) {
143185
if options.Reconciler == nil {
144186
return nil, fmt.Errorf("must specify Reconciler")
145187
}
@@ -148,18 +190,14 @@ func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, opt
148190
return nil, fmt.Errorf("must specify Name for Controller")
149191
}
150192

151-
if options.SkipNameValidation == nil {
152-
options.SkipNameValidation = mgr.GetControllerOptions().SkipNameValidation
153-
}
154-
155193
if options.SkipNameValidation == nil || !*options.SkipNameValidation {
156194
if err := checkName(name); err != nil {
157195
return nil, err
158196
}
159197
}
160198

161199
if options.LogConstructor == nil {
162-
log := mgr.GetLogger().WithValues(
200+
log := options.Logger.WithValues(
163201
"controller", name,
164202
)
165203
options.LogConstructor = func(in *request) logr.Logger {
@@ -175,23 +213,15 @@ func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, opt
175213
}
176214

177215
if options.MaxConcurrentReconciles <= 0 {
178-
if mgr.GetControllerOptions().MaxConcurrentReconciles > 0 {
179-
options.MaxConcurrentReconciles = mgr.GetControllerOptions().MaxConcurrentReconciles
180-
} else {
181-
options.MaxConcurrentReconciles = 1
182-
}
216+
options.MaxConcurrentReconciles = 1
183217
}
184218

185219
if options.CacheSyncTimeout == 0 {
186-
if mgr.GetControllerOptions().CacheSyncTimeout != 0 {
187-
options.CacheSyncTimeout = mgr.GetControllerOptions().CacheSyncTimeout
188-
} else {
189-
options.CacheSyncTimeout = 2 * time.Minute
190-
}
220+
options.CacheSyncTimeout = 2 * time.Minute
191221
}
192222

193223
if options.RateLimiter == nil {
194-
if ptr.Deref(mgr.GetControllerOptions().UsePriorityQueue, false) {
224+
if ptr.Deref(options.UsePriorityQueue, false) {
195225
options.RateLimiter = workqueue.NewTypedItemExponentialFailureRateLimiter[request](5*time.Millisecond, 1000*time.Second)
196226
} else {
197227
options.RateLimiter = workqueue.DefaultTypedControllerRateLimiter[request]()
@@ -200,9 +230,9 @@ func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, opt
200230

201231
if options.NewQueue == nil {
202232
options.NewQueue = func(controllerName string, rateLimiter workqueue.TypedRateLimiter[request]) workqueue.TypedRateLimitingInterface[request] {
203-
if ptr.Deref(mgr.GetControllerOptions().UsePriorityQueue, false) {
233+
if ptr.Deref(options.UsePriorityQueue, false) {
204234
return priorityqueue.New(controllerName, func(o *priorityqueue.Opts[request]) {
205-
o.Log = mgr.GetLogger().WithValues("controller", controllerName)
235+
o.Log = options.Logger.WithValues("controller", controllerName)
206236
o.RateLimiter = rateLimiter
207237
})
208238
}
@@ -212,14 +242,6 @@ func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, opt
212242
}
213243
}
214244

215-
if options.RecoverPanic == nil {
216-
options.RecoverPanic = mgr.GetControllerOptions().RecoverPanic
217-
}
218-
219-
if options.NeedLeaderElection == nil {
220-
options.NeedLeaderElection = mgr.GetControllerOptions().NeedLeaderElection
221-
}
222-
223245
// Create controller with dependencies set
224246
return &controller.Controller[request]{
225247
Do: options.Reconciler,

pkg/controller/example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func ExampleNewUnmanaged() {
129129

130130
// Configure creates a new controller but does not add it to the supplied
131131
// manager.
132-
c, err := controller.NewUnmanaged("pod-controller", mgr, controller.Options{
132+
c, err := controller.NewUnmanaged("pod-controller", controller.Options{
133133
Reconciler: reconcile.Func(func(context.Context, reconcile.Request) (reconcile.Result, error) {
134134
return reconcile.Result{}, nil
135135
}),

pkg/manager/manager.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ func setOptionsDefaults(options Options) Options {
544544
options.Logger = log.Log
545545
}
546546

547+
if options.Controller.Logger.GetSink() == nil {
548+
options.Controller.Logger = options.Logger
549+
}
550+
547551
if options.BaseContext == nil {
548552
options.BaseContext = defaultBaseContext
549553
}

pkg/manager/manager_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,47 @@ var _ = Describe("manger.Manager", func() {
10561056
)))
10571057
})
10581058

1059+
It("should default controller logger from manager logger", func() {
1060+
var lock sync.Mutex
1061+
var messages []string
1062+
options.Logger = funcr.NewJSON(func(object string) {
1063+
lock.Lock()
1064+
messages = append(messages, object)
1065+
lock.Unlock()
1066+
}, funcr.Options{})
1067+
options.LeaderElection = false
1068+
1069+
m, err := New(cfg, options)
1070+
Expect(err).NotTo(HaveOccurred())
1071+
for _, cb := range callbacks {
1072+
cb(m)
1073+
}
1074+
1075+
started := make(chan struct{})
1076+
Expect(m.Add(RunnableFunc(func(ctx context.Context) error {
1077+
close(started)
1078+
return nil
1079+
}))).To(Succeed())
1080+
1081+
stopped := make(chan error)
1082+
ctx, cancel := context.WithCancel(context.Background())
1083+
go func() {
1084+
stopped <- m.Start(ctx)
1085+
}()
1086+
1087+
// Wait for runnables to start as a proxy for the manager being fully started.
1088+
<-started
1089+
cancel()
1090+
Expect(<-stopped).To(Succeed())
1091+
1092+
msg := "controller log message"
1093+
m.GetControllerOptions().Logger.Info(msg)
1094+
1095+
Expect(messages).To(ContainElement(
1096+
ContainSubstring(msg),
1097+
))
1098+
})
1099+
10591100
It("should return both runnables and stop errors when both error", func() {
10601101
m, err := New(cfg, options)
10611102
Expect(err).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)