Skip to content

Commit 486bed0

Browse files
authored
Refactor controllers registration (#646)
- Move unexported controller registration function and types from internal/manager to internal/reconciler package and make them exported. This will allow us to reuse them in packages other than manager. - Rename reconciler package to controller to better reflect the purpose of the package.
1 parent 2ade9f8 commit 486bed0

13 files changed

+101
-84
lines changed

internal/reconciler/reconciler_suite_test.go renamed to internal/controller/controller_suite_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package reconciler_test
1+
package controller_test
22

33
import (
44
"testing"

internal/manager/managerfakes/fake_field_indexer.go renamed to internal/controller/controllerfakes/fake_field_indexer.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/reconciler/reconcilerfakes/fake_getter.go renamed to internal/controller/controllerfakes/fake_getter.go

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/manager/managerfakes/fake_manager.go renamed to internal/controller/controllerfakes/fake_manager.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controller/doc.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
Package controller is responsible for creating and registering controllers for
3+
sigs.k8s.io/controller-runtime/pkg/manager.Manager.
4+
5+
A controller is responsible for watching for updates to the resource of a desired type and propagating those updates
6+
as events through the event channel.
7+
8+
The reconciliation part of a controller -- reacting on a resource change -- is implemented by the Reconciler type,
9+
which in turn implements sigs.k8s.io/controller-runtime/pkg/reconcile.Reconciler.
10+
*/
11+
package controller

internal/manager/fakes.go renamed to internal/controller/fakes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package manager
1+
package controller
22

33
import (
44
_ "sigs.k8s.io/controller-runtime/pkg/client" // used below to generate a fake

internal/reconciler/getter.go renamed to internal/controller/getter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package reconciler
1+
package controller
22

33
import (
44
"context"

internal/reconciler/implementation.go renamed to internal/controller/reconciler.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package reconciler
1+
package controller
22

33
import (
44
"context"
@@ -18,8 +18,8 @@ import (
1818
// If the function returns false, the reconciler will log the returned string.
1919
type NamespacedNameFilterFunc func(nsname types.NamespacedName) (bool, string)
2020

21-
// Config contains the configuration for the Implementation.
22-
type Config struct {
21+
// ReconcilerConfig is the configuration for the reconciler.
22+
type ReconcilerConfig struct {
2323
// Getter gets a resource from the k8s API.
2424
Getter Getter
2525
// ObjectType is the type of the resource that the reconciler will reconcile.
@@ -30,21 +30,21 @@ type Config struct {
3030
NamespacedNameFilter NamespacedNameFilterFunc
3131
}
3232

33-
// Implementation is a reconciler for Kubernetes resources.
33+
// Reconciler reconciles Kubernetes resources of a specific type.
3434
// It implements the reconcile.Reconciler interface.
3535
// A successful reconciliation of a resource has the two possible outcomes:
3636
// (1) If the resource is deleted, the Implementation will send a DeleteEvent to the event channel.
3737
// (2) If the resource is upserted (created or updated), the Implementation will send an UpsertEvent
3838
// to the event channel.
39-
type Implementation struct {
40-
cfg Config
39+
type Reconciler struct {
40+
cfg ReconcilerConfig
4141
}
4242

43-
var _ reconcile.Reconciler = &Implementation{}
43+
var _ reconcile.Reconciler = &Reconciler{}
4444

45-
// NewImplementation creates a new Implementation.
46-
func NewImplementation(cfg Config) *Implementation {
47-
return &Implementation{
45+
// NewReconciler creates a new reconciler.
46+
func NewReconciler(cfg ReconcilerConfig) *Reconciler {
47+
return &Reconciler{
4848
cfg: cfg,
4949
}
5050
}
@@ -59,7 +59,7 @@ func newObject(objectType client.Object) client.Object {
5959
}
6060

6161
// Reconcile implements the reconcile.Reconciler Reconcile method.
62-
func (r *Implementation) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
62+
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
6363
logger := log.FromContext(ctx)
6464
// The controller runtime has set the logger with the group, kind, namespace and name of the resource,
6565
// and a few other key/value pairs. So we don't need to set them here.

internal/reconciler/implementation_test.go renamed to internal/controller/reconciler_test.go

+12-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package reconciler_test
1+
package controller_test
22

33
import (
44
"context"
@@ -14,10 +14,9 @@ import (
1414
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1515
"sigs.k8s.io/gateway-api/apis/v1beta1"
1616

17-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler/reconcilerfakes"
18-
17+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/controller"
18+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/controller/controllerfakes"
1919
"github.com/nginxinc/nginx-kubernetes-gateway/internal/events"
20-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler"
2120
)
2221

2322
type getFunc func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error
@@ -29,8 +28,8 @@ type result struct {
2928

3029
var _ = Describe("Reconciler", func() {
3130
var (
32-
rec *reconciler.Implementation
33-
fakeGetter *reconcilerfakes.FakeGetter
31+
rec *controller.Reconciler
32+
fakeGetter *controllerfakes.FakeGetter
3433
eventCh chan interface{}
3534

3635
hr1NsName = types.NamespacedName{
@@ -108,7 +107,7 @@ var _ = Describe("Reconciler", func() {
108107
}
109108

110109
BeforeEach(func() {
111-
fakeGetter = &reconcilerfakes.FakeGetter{}
110+
fakeGetter = &controllerfakes.FakeGetter{}
112111
eventCh = make(chan interface{})
113112
})
114113

@@ -136,7 +135,7 @@ var _ = Describe("Reconciler", func() {
136135

137136
When("Reconciler doesn't have a filter", func() {
138137
BeforeEach(func() {
139-
rec = reconciler.NewImplementation(reconciler.Config{
138+
rec = controller.NewReconciler(controller.ReconcilerConfig{
140139
Getter: fakeGetter,
141140
ObjectType: &v1beta1.HTTPRoute{},
142141
EventCh: eventCh,
@@ -161,7 +160,7 @@ var _ = Describe("Reconciler", func() {
161160
return true, ""
162161
}
163162

164-
rec = reconciler.NewImplementation(reconciler.Config{
163+
rec = controller.NewReconciler(controller.ReconcilerConfig{
165164
Getter: fakeGetter,
166165
ObjectType: &v1beta1.HTTPRoute{},
167166
EventCh: eventCh,
@@ -203,7 +202,7 @@ var _ = Describe("Reconciler", func() {
203202

204203
Describe("Edge cases", func() {
205204
BeforeEach(func() {
206-
rec = reconciler.NewImplementation(reconciler.Config{
205+
rec = controller.NewReconciler(controller.ReconcilerConfig{
207206
Getter: fakeGetter,
208207
ObjectType: &v1beta1.HTTPRoute{},
209208
EventCh: eventCh,
@@ -221,7 +220,7 @@ var _ = Describe("Reconciler", func() {
221220
})
222221

223222
DescribeTable("Reconciler should not block when ctx is done",
224-
func(get getFunc, invalidResourceEventCount int, nsname types.NamespacedName) {
223+
func(get getFunc, nsname types.NamespacedName) {
225224
fakeGetter.GetCalls(get)
226225

227226
ctx, cancel := context.WithCancel(context.Background())
@@ -232,9 +231,8 @@ var _ = Describe("Reconciler", func() {
232231
Consistently(eventCh).ShouldNot(Receive())
233232
Expect(resultCh).To(Receive(Equal(result{err: nil, reconcileResult: reconcile.Result{}})))
234233
},
235-
Entry("Upserting valid HTTPRoute", getReturnsHRForHR(hr1), 0, hr1NsName),
236-
Entry("Deleting valid HTTPRoute", getReturnsNotFoundErrorForHR(hr1), 0, hr1NsName),
237-
Entry("Upserting invalid HTTPRoute", getReturnsHRForHR(hr2), 1, hr2NsName),
234+
Entry("Upserting HTTPRoute", getReturnsHRForHR(hr1), hr1NsName),
235+
Entry("Deleting HTTPRoute", getReturnsNotFoundErrorForHR(hr1), hr1NsName),
238236
)
239237
})
240238
})

internal/manager/controllers.go renamed to internal/controller/register.go

+31-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package manager
1+
package controller
22

33
import (
44
"context"
@@ -11,64 +11,71 @@ import (
1111
"sigs.k8s.io/controller-runtime/pkg/predicate"
1212

1313
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/index"
14-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler"
1514
)
1615

1716
const (
1817
// addIndexFieldTimeout is the timeout used for adding an Index Field to a cache.
1918
addIndexFieldTimeout = 2 * time.Minute
2019
)
2120

22-
type newReconcilerFunc func(cfg reconciler.Config) *reconciler.Implementation
23-
24-
type controllerConfig struct {
25-
namespacedNameFilter reconciler.NamespacedNameFilterFunc
21+
type config struct {
22+
namespacedNameFilter NamespacedNameFilterFunc
2623
k8sPredicate predicate.Predicate
2724
fieldIndices index.FieldIndices
28-
newReconciler newReconcilerFunc
25+
newReconciler NewReconcilerFunc
2926
}
3027

31-
type controllerOption func(*controllerConfig)
28+
// NewReconcilerFunc defines a function that creates a new Reconciler. Used for unit-testing.
29+
type NewReconcilerFunc func(cfg ReconcilerConfig) *Reconciler
30+
31+
// Option defines configuration options for registering a controller.
32+
type Option func(*config)
3233

33-
func withNamespacedNameFilter(filter reconciler.NamespacedNameFilterFunc) controllerOption {
34-
return func(cfg *controllerConfig) {
34+
// WithNamespacedNameFilter enables filtering of objects by NamespacedName by the controller.
35+
func WithNamespacedNameFilter(filter NamespacedNameFilterFunc) Option {
36+
return func(cfg *config) {
3537
cfg.namespacedNameFilter = filter
3638
}
3739
}
3840

39-
func withK8sPredicate(p predicate.Predicate) controllerOption {
40-
return func(cfg *controllerConfig) {
41+
// WithK8sPredicate enables filtering of events before they are sent to the controller.
42+
func WithK8sPredicate(p predicate.Predicate) Option {
43+
return func(cfg *config) {
4144
cfg.k8sPredicate = p
4245
}
4346
}
4447

45-
func withFieldIndices(fieldIndices index.FieldIndices) controllerOption {
46-
return func(cfg *controllerConfig) {
48+
// WithFieldIndices adds indices to the FieldIndexer of the manager.
49+
func WithFieldIndices(fieldIndices index.FieldIndices) Option {
50+
return func(cfg *config) {
4751
cfg.fieldIndices = fieldIndices
4852
}
4953
}
5054

51-
// withNewReconciler allows us to mock reconciler creation in the unit tests.
52-
func withNewReconciler(newReconciler newReconcilerFunc) controllerOption {
53-
return func(cfg *controllerConfig) {
55+
// WithNewReconciler allows us to mock reconciler creation in the unit tests.
56+
func WithNewReconciler(newReconciler NewReconcilerFunc) Option {
57+
return func(cfg *config) {
5458
cfg.newReconciler = newReconciler
5559
}
5660
}
5761

58-
func defaultControllerConfig() controllerConfig {
59-
return controllerConfig{
60-
newReconciler: reconciler.NewImplementation,
62+
func defaultConfig() config {
63+
return config{
64+
newReconciler: NewReconciler,
6165
}
6266
}
6367

64-
func registerController(
68+
// Register registers a new controller for the object type in the manager and configure it with the provided options.
69+
// If the options include WithFieldIndices, it will add the specified indices to FieldIndexer of the manager.
70+
// The registered controller will send events to the provided channel.
71+
func Register(
6572
ctx context.Context,
6673
objectType client.Object,
6774
mgr manager.Manager,
6875
eventCh chan<- interface{},
69-
options ...controllerOption,
76+
options ...Option,
7077
) error {
71-
cfg := defaultControllerConfig()
78+
cfg := defaultConfig()
7279

7380
for _, opt := range options {
7481
opt(&cfg)
@@ -87,7 +94,7 @@ func registerController(
8794
builder = builder.WithEventFilter(cfg.k8sPredicate)
8895
}
8996

90-
recCfg := reconciler.Config{
97+
recCfg := ReconcilerConfig{
9198
Getter: mgr.GetClient(),
9299
ObjectType: objectType,
93100
EventCh: eventCh,

internal/manager/controllers_test.go renamed to internal/controller/register_test.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package manager
1+
package controller_test
22

33
import (
44
"context"
@@ -15,26 +15,26 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/log/zap"
1616
"sigs.k8s.io/gateway-api/apis/v1beta1"
1717

18+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/controller"
19+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/controller/controllerfakes"
1820
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/filter"
1921
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/index"
20-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/managerfakes"
2122
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/predicate"
22-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler"
2323
)
2424

25-
func TestRegisterController(t *testing.T) {
25+
func TestRegister(t *testing.T) {
2626
type fakes struct {
27-
mgr *managerfakes.FakeManager
28-
indexer *managerfakes.FakeFieldIndexer
27+
mgr *controllerfakes.FakeManager
28+
indexer *controllerfakes.FakeFieldIndexer
2929
}
3030

3131
getDefaultFakes := func() fakes {
32-
scheme = runtime.NewScheme()
32+
scheme := runtime.NewScheme()
3333
utilruntime.Must(v1beta1.AddToScheme(scheme))
3434

35-
indexer := &managerfakes.FakeFieldIndexer{}
35+
indexer := &controllerfakes.FakeFieldIndexer{}
3636

37-
mgr := &managerfakes.FakeManager{}
37+
mgr := &controllerfakes.FakeManager{}
3838
mgr.GetClientReturns(fake.NewClientBuilder().Build())
3939
mgr.GetSchemeReturns(scheme)
4040
mgr.GetLoggerReturns(zap.New())
@@ -97,24 +97,24 @@ func TestRegisterController(t *testing.T) {
9797
t.Run(test.msg, func(t *testing.T) {
9898
g := NewGomegaWithT(t)
9999

100-
newReconciler := func(c reconciler.Config) *reconciler.Implementation {
100+
newReconciler := func(c controller.ReconcilerConfig) *controller.Reconciler {
101101
g.Expect(c.Getter).To(BeIdenticalTo(test.fakes.mgr.GetClient()))
102102
g.Expect(c.ObjectType).To(BeIdenticalTo(objectType))
103103
g.Expect(c.EventCh).To(BeIdenticalTo(eventCh))
104104
g.Expect(c.NamespacedNameFilter).Should(beSameFunctionPointer(namespacedNameFilter))
105105

106-
return reconciler.NewImplementation(c)
106+
return controller.NewReconciler(c)
107107
}
108108

109-
err := registerController(
109+
err := controller.Register(
110110
context.Background(),
111111
objectType,
112112
test.fakes.mgr,
113113
eventCh,
114-
withNamespacedNameFilter(namespacedNameFilter),
115-
withK8sPredicate(predicate.ServicePortsChangedPredicate{}),
116-
withFieldIndices(fieldIndexes),
117-
withNewReconciler(newReconciler),
114+
controller.WithNamespacedNameFilter(namespacedNameFilter),
115+
controller.WithK8sPredicate(predicate.ServicePortsChangedPredicate{}),
116+
controller.WithFieldIndices(fieldIndexes),
117+
controller.WithNewReconciler(newReconciler),
118118
)
119119

120120
if test.expectedErr == nil {

0 commit comments

Comments
 (0)