From bca76056f9877d857d7ea1cf430183fa9531a941 Mon Sep 17 00:00:00 2001 From: Michael Pleshakov Date: Fri, 9 Sep 2022 16:52:58 -0500 Subject: [PATCH] Refactor controllers and implementations This commit refactors controllers and implementations. Previously, all controllers and implementations looked almost the same. The commit replaces all controllers with a single controller that handles all the types. Similarly, the commit replaces all implementations with a single one that handles all the types. The new code relies on generics --- internal/implementations/gateway/gateway.go | 49 ---------- .../implementations/gateway/gateway_test.go | 61 ------------- .../gateway/implementation_suite_test.go | 13 --- .../gatewayclass/gatewayclass.go | 64 ------------- .../gatewayclass/gatewayclass_test.go | 88 ------------------ .../gatewayclass/implementation_suite_test.go | 13 --- .../gatewayconfig/gatewayconfig.go | 48 ---------- .../implementations/httproute/httproute.go | 53 ----------- internal/implementations/implementation.go | 89 +++++++++++++++++++ .../secret/implementation_suite_test.go | 13 --- internal/implementations/secret/secret.go | 53 ----------- .../implementations/secret/secret_test.go | 64 ------------- internal/implementations/service/service.go | 52 ----------- internal/manager/manager.go | 51 +++++++---- pkg/sdk/gateway_controller.go | 56 ------------ pkg/sdk/gatewayclass_controller.go | 56 ------------ pkg/sdk/gatewayconfig_controller.go | 57 ------------ pkg/sdk/httproute_controller.go | 62 ------------- pkg/sdk/interfaces.go | 35 ++------ pkg/sdk/reconciler.go | 83 +++++++++++++++++ pkg/sdk/secret_controller.go | 62 ------------- pkg/sdk/service_controller.go | 62 ------------- 22 files changed, 213 insertions(+), 971 deletions(-) delete mode 100644 internal/implementations/gateway/gateway.go delete mode 100644 internal/implementations/gateway/gateway_test.go delete mode 100644 internal/implementations/gateway/implementation_suite_test.go delete mode 100644 internal/implementations/gatewayclass/gatewayclass.go delete mode 100644 internal/implementations/gatewayclass/gatewayclass_test.go delete mode 100644 internal/implementations/gatewayclass/implementation_suite_test.go delete mode 100644 internal/implementations/gatewayconfig/gatewayconfig.go delete mode 100644 internal/implementations/httproute/httproute.go create mode 100644 internal/implementations/implementation.go delete mode 100644 internal/implementations/secret/implementation_suite_test.go delete mode 100644 internal/implementations/secret/secret.go delete mode 100644 internal/implementations/secret/secret_test.go delete mode 100644 internal/implementations/service/service.go delete mode 100644 pkg/sdk/gateway_controller.go delete mode 100644 pkg/sdk/gatewayclass_controller.go delete mode 100644 pkg/sdk/gatewayconfig_controller.go delete mode 100644 pkg/sdk/httproute_controller.go create mode 100644 pkg/sdk/reconciler.go delete mode 100644 pkg/sdk/secret_controller.go delete mode 100644 pkg/sdk/service_controller.go diff --git a/internal/implementations/gateway/gateway.go b/internal/implementations/gateway/gateway.go deleted file mode 100644 index 67c4afffc..000000000 --- a/internal/implementations/gateway/gateway.go +++ /dev/null @@ -1,49 +0,0 @@ -package implementation - -import ( - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type gatewayImplementation struct { - logger logr.Logger - eventCh chan<- interface{} -} - -func NewGatewayImplementation(conf config.Config, eventCh chan<- interface{}) sdk.GatewayImpl { - return &gatewayImplementation{ - logger: conf.Logger, - eventCh: eventCh, - } -} - -// FIXME(pleshakov) All Implementations (Gateway, HTTPRoute, ...) look similar. Consider writing a general-purpose -// component to implement all implementations. This will avoid the duplication code and tests. - -func (impl *gatewayImplementation) Upsert(gw *v1beta1.Gateway) { - impl.logger.Info("Gateway was upserted", - "namespace", gw.Namespace, - "name", gw.Name, - ) - - impl.eventCh <- &events.UpsertEvent{ - Resource: gw, - } -} - -func (impl *gatewayImplementation) Remove(nsname types.NamespacedName) { - impl.logger.Info("Gateway was removed", - "namespace", nsname.Namespace, - "name", nsname.Name, - ) - - impl.eventCh <- &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1beta1.Gateway{}, - } -} diff --git a/internal/implementations/gateway/gateway_test.go b/internal/implementations/gateway/gateway_test.go deleted file mode 100644 index 2d6869491..000000000 --- a/internal/implementations/gateway/gateway_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package implementation_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - implementation "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/gateway" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -var _ = Describe("GatewayImplementation", func() { - var ( - eventCh chan interface{} - impl sdk.GatewayImpl - ) - - BeforeEach(func() { - eventCh = make(chan interface{}) - - impl = implementation.NewGatewayImplementation(config.Config{ - Logger: zap.New(), - }, eventCh) - }) - - Describe("Implementation processes Gateways", func() { - It("should process upsert", func() { - gc := &v1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test-add", - Name: "gateway", - }, - } - - go func() { - impl.Upsert(gc) - }() - - Eventually(eventCh).Should(Receive(Equal(&events.UpsertEvent{Resource: gc}))) - }) - - It("should process remove", func() { - nsname := types.NamespacedName{Namespace: "test-remove", Name: "gateway"} - - go func() { - impl.Remove(nsname) - }() - - Eventually(eventCh).Should(Receive(Equal( - &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1beta1.Gateway{}, - }))) - }) - }) -}) diff --git a/internal/implementations/gateway/implementation_suite_test.go b/internal/implementations/gateway/implementation_suite_test.go deleted file mode 100644 index dffdfe85a..000000000 --- a/internal/implementations/gateway/implementation_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package implementation_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestGatewayImplementation(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Gateway Implementation Suite") -} diff --git a/internal/implementations/gatewayclass/gatewayclass.go b/internal/implementations/gatewayclass/gatewayclass.go deleted file mode 100644 index 3bf254ba0..000000000 --- a/internal/implementations/gatewayclass/gatewayclass.go +++ /dev/null @@ -1,64 +0,0 @@ -package implementation - -import ( - "fmt" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type gatewayClassImplementation struct { - logger logr.Logger - gatewayClassName string - eventCh chan<- interface{} -} - -func NewGatewayClassImplementation(conf config.Config, eventCh chan<- interface{}) sdk.GatewayClassImpl { - return &gatewayClassImplementation{ - logger: conf.Logger, - gatewayClassName: conf.GatewayClassName, - eventCh: eventCh, - } -} - -func (impl *gatewayClassImplementation) Upsert(gc *v1beta1.GatewayClass) { - if gc.Name != impl.gatewayClassName { - msg := fmt.Sprintf("GatewayClass was upserted but ignored because this controller only supports the GatewayClass %s", impl.gatewayClassName) - impl.logger.Info(msg, - "name", gc.Name, - ) - return - } - - impl.eventCh <- &events.UpsertEvent{ - Resource: gc, - } - - impl.logger.Info("GatewayClass was upserted", - "name", gc.Name) -} - -func (impl *gatewayClassImplementation) Remove(nsname types.NamespacedName) { - // GatewayClass is a cluster scoped resource - no namespace. - - if nsname.Name != impl.gatewayClassName { - msg := fmt.Sprintf("GatewayClass was removed but ignored because this controller only supports the GatewayClass %s", impl.gatewayClassName) - impl.logger.Info(msg, - "name", nsname.Name, - ) - return - } - - impl.logger.Info("GatewayClass was removed", - "name", nsname.Name) - - impl.eventCh <- &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1beta1.GatewayClass{}, - } -} diff --git a/internal/implementations/gatewayclass/gatewayclass_test.go b/internal/implementations/gatewayclass/gatewayclass_test.go deleted file mode 100644 index 125378396..000000000 --- a/internal/implementations/gatewayclass/gatewayclass_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package implementation_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - implementation "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/gatewayclass" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -var _ = Describe("GatewayClassImplementation", func() { - var ( - eventCh chan interface{} - impl sdk.GatewayClassImpl - ) - - const ( - className = "my-class" - unrelatedClassName = "not-my-class" - ) - - BeforeEach(func() { - eventCh = make(chan interface{}) - - impl = implementation.NewGatewayClassImplementation(config.Config{ - Logger: zap.New(), - GatewayClassName: className, - }, eventCh) - }) - - Describe("Implementation processes GatewayClass", func() { - It("should process upsert", func() { - gc := &v1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: className, - }, - } - - go func() { - impl.Upsert(gc) - }() - - Eventually(eventCh).Should(Receive(Equal(&events.UpsertEvent{Resource: gc}))) - }) - - It("should process remove", func() { - nsname := types.NamespacedName{Name: className} - - go func() { - impl.Remove(nsname) - }() - - Eventually(eventCh).Should(Receive(Equal( - &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1beta1.GatewayClass{}, - }))) - }) - }) - - Describe("Implementation ignores unrelated GatewayClass", func() { - It("should ignore upsert", func() { - gc := &v1beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: unrelatedClassName, - }, - } - - impl.Upsert(gc) - - Expect(eventCh).ShouldNot(Receive()) - }) - - It("should ignore remove", func() { - nsname := types.NamespacedName{Name: unrelatedClassName} - - impl.Remove(nsname) - - Expect(eventCh).ShouldNot(Receive()) - }) - }) -}) diff --git a/internal/implementations/gatewayclass/implementation_suite_test.go b/internal/implementations/gatewayclass/implementation_suite_test.go deleted file mode 100644 index a6f600b94..000000000 --- a/internal/implementations/gatewayclass/implementation_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package implementation_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestGatewayClassImplementation(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Gateway Class Implementation Suite") -} diff --git a/internal/implementations/gatewayconfig/gatewayconfig.go b/internal/implementations/gatewayconfig/gatewayconfig.go deleted file mode 100644 index 29d5bfaae..000000000 --- a/internal/implementations/gatewayconfig/gatewayconfig.go +++ /dev/null @@ -1,48 +0,0 @@ -package implementation - -import ( - "github.com/go-logr/logr" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - nginxgwv1alpha1 "github.com/nginxinc/nginx-kubernetes-gateway/pkg/apis/gateway/v1alpha1" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type gatewayConfigImplementation struct { - conf config.Config -} - -func NewGatewayConfigImplementation(conf config.Config) sdk.GatewayConfigImpl { - return &gatewayConfigImplementation{ - conf: conf, - } -} - -func (impl *gatewayConfigImplementation) Logger() logr.Logger { - return impl.conf.Logger -} - -func (impl *gatewayConfigImplementation) Upsert(gcfg *nginxgwv1alpha1.GatewayConfig) { - impl.Logger().Info("Processing GatewayConfig", - "name", gcfg.Name, - ) - - if gcfg.Spec.Worker != nil && gcfg.Spec.Worker.Processes != nil { - impl.Logger().Info("Worker config", - "processes", gcfg.Spec.Worker.Processes) - } - - if gcfg.Spec.HTTP != nil { - for _, l := range gcfg.Spec.HTTP.AccessLogs { - impl.Logger().Info("AccessLog config", - "format", l.Format, - "destination", l.Destination) - } - } -} - -func (impl *gatewayConfigImplementation) Remove(name string) { - impl.Logger().Info("Removing GatewayConfig", - "name", name, - ) -} diff --git a/internal/implementations/httproute/httproute.go b/internal/implementations/httproute/httproute.go deleted file mode 100644 index 825d1df47..000000000 --- a/internal/implementations/httproute/httproute.go +++ /dev/null @@ -1,53 +0,0 @@ -package implementation - -import ( - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type httpRouteImplementation struct { - conf config.Config - eventCh chan<- interface{} -} - -// NewHTTPRouteImplementation creates a new HTTPRouteImplementation. -func NewHTTPRouteImplementation(cfg config.Config, eventCh chan<- interface{}) sdk.HTTPRouteImpl { - return &httpRouteImplementation{ - conf: cfg, - eventCh: eventCh, - } -} - -func (impl *httpRouteImplementation) Logger() logr.Logger { - return impl.conf.Logger -} - -func (impl *httpRouteImplementation) ControllerName() string { - return impl.conf.GatewayCtlrName -} - -func (impl *httpRouteImplementation) Upsert(hr *v1beta1.HTTPRoute) { - impl.Logger().Info("HTTPRoute was upserted", - "namespace", hr.Namespace, "name", hr.Name, - ) - - impl.eventCh <- &events.UpsertEvent{ - Resource: hr, - } -} - -func (impl *httpRouteImplementation) Remove(nsname types.NamespacedName) { - impl.Logger().Info("HTTPRoute resource was removed", - "namespace", nsname.Namespace, "name", nsname.Name, - ) - - impl.eventCh <- &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1beta1.HTTPRoute{}, - } -} diff --git a/internal/implementations/implementation.go b/internal/implementations/implementation.go new file mode 100644 index 000000000..bb001261c --- /dev/null +++ b/internal/implementations/implementation.go @@ -0,0 +1,89 @@ +package implementations + +import ( + "fmt" + "reflect" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" + "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" +) + +type ImplementationImpl[T sdk.ObjectConstraint] struct { + logger logr.Logger + eventCh chan<- interface{} + filter NamespacedNameFilter + resourceKind string +} + +var _ sdk.Implementation[*v1beta1.Gateway] = &ImplementationImpl[*v1beta1.Gateway]{} + +type NamespacedNameFilter func(nsname types.NamespacedName) (bool, string) + +func NewImplementation[T sdk.ObjectConstraint](logger logr.Logger, eventCh chan<- interface{}) *ImplementationImpl[T] { + return NewImplementationWithFilter[T](logger, eventCh, nil) +} + +func NewImplementationWithFilter[T sdk.ObjectConstraint]( + logger logr.Logger, + eventCh chan<- interface{}, + filter NamespacedNameFilter, +) *ImplementationImpl[T] { + var obj T + return &ImplementationImpl[T]{ + logger: logger, + eventCh: eventCh, + filter: filter, + resourceKind: reflect.TypeOf(obj).Elem().Name(), + } +} + +func (impl *ImplementationImpl[T]) Upsert(obj T) { + nsname := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + + if impl.filter != nil { + if ignore, msg := impl.filter(nsname); ignore { + impl.logger.Info(msg, + "namespace", nsname.Namespace, + "name", nsname.Name, + ) + return + } + } + + impl.logger.Info(fmt.Sprintf("%s was upserted", impl.resourceKind), + "namespace", obj.GetNamespace(), + "name", obj.GetName(), + ) + + impl.eventCh <- &events.UpsertEvent{ + Resource: obj, + } +} + +func (impl *ImplementationImpl[T]) Remove(nsname types.NamespacedName) { + if impl.filter != nil { + if ignore, msg := impl.filter(nsname); ignore { + impl.logger.Info(msg, + "namespace", nsname.Namespace, + "name", nsname.Name, + ) + return + } + } + + impl.logger.Info(fmt.Sprintf("%s was removed", impl.resourceKind), + "namespace", nsname.Namespace, + "name", nsname.Name, + ) + + var t T + + impl.eventCh <- &events.DeleteEvent{ + NamespacedName: nsname, + Type: t, + } +} diff --git a/internal/implementations/secret/implementation_suite_test.go b/internal/implementations/secret/implementation_suite_test.go deleted file mode 100644 index bfa87e8df..000000000 --- a/internal/implementations/secret/implementation_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package implementation_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestSecretImplementation(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Secret Implementation Suite") -} diff --git a/internal/implementations/secret/secret.go b/internal/implementations/secret/secret.go deleted file mode 100644 index d3fbc9f28..000000000 --- a/internal/implementations/secret/secret.go +++ /dev/null @@ -1,53 +0,0 @@ -package implementation - -import ( - "github.com/go-logr/logr" - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type secretImplementation struct { - conf config.Config - eventCh chan<- interface{} -} - -// NewSecretImplementation creates a new SecretImplementation. -func NewSecretImplementation(cfg config.Config, eventCh chan<- interface{}) sdk.SecretImpl { - return &secretImplementation{ - conf: cfg, - eventCh: eventCh, - } -} - -func (impl *secretImplementation) Logger() logr.Logger { - return impl.conf.Logger -} - -func (impl secretImplementation) Upsert(secret *apiv1.Secret) { - impl.Logger().Info( - "Secret was upserted", - "namespace", secret.Namespace, - "name", secret.Name, - ) - - impl.eventCh <- &events.UpsertEvent{ - Resource: secret, - } -} - -func (impl secretImplementation) Remove(nsname types.NamespacedName) { - impl.Logger().Info( - "Secret was removed", - "namespace", nsname.Namespace, - "name", nsname.Name, - ) - - impl.eventCh <- &events.DeleteEvent{ - NamespacedName: nsname, - Type: &apiv1.Secret{}, - } -} diff --git a/internal/implementations/secret/secret_test.go b/internal/implementations/secret/secret_test.go deleted file mode 100644 index 8d0fc8dfd..000000000 --- a/internal/implementations/secret/secret_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package implementation_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - implementation "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/secret" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -var _ = Describe("SecretImplementation", func() { - var ( - eventCh chan interface{} - impl sdk.SecretImpl - ) - - BeforeEach(func() { - eventCh = make(chan interface{}) - - impl = implementation.NewSecretImplementation(config.Config{ - Logger: zap.New(), - }, eventCh) - }) - - const secretName = "my-secret" - const secretNamespace = "test" - - Describe("Implementation processes Secret", func() { - It("should process upsert", func() { - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: secretNamespace, - }, - } - - go func() { - impl.Upsert(secret) - }() - - Eventually(eventCh).Should(Receive(Equal(&events.UpsertEvent{Resource: secret}))) - }) - - It("should process remove", func() { - nsname := types.NamespacedName{Name: secretName, Namespace: secretNamespace} - - go func() { - impl.Remove(nsname) - }() - - Eventually(eventCh).Should(Receive(Equal( - &events.DeleteEvent{ - NamespacedName: nsname, - Type: &v1.Secret{}, - }))) - }) - }) -}) diff --git a/internal/implementations/service/service.go b/internal/implementations/service/service.go deleted file mode 100644 index a04cb1276..000000000 --- a/internal/implementations/service/service.go +++ /dev/null @@ -1,52 +0,0 @@ -package implementation - -import ( - "github.com/go-logr/logr" - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - - "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - "github.com/nginxinc/nginx-kubernetes-gateway/pkg/sdk" -) - -type serviceImplementation struct { - conf config.Config - eventCh chan<- interface{} -} - -// FIXME(pleshakov): serviceImplementation looks similar to httpRouteImplemenation -// consider if it is possible to reduce the amount of code. - -// NewServiceImplementation creates a new ServiceImplementation. -func NewServiceImplementation(cfg config.Config, eventCh chan<- interface{}) sdk.ServiceImpl { - return &serviceImplementation{ - conf: cfg, - eventCh: eventCh, - } -} - -func (impl *serviceImplementation) Logger() logr.Logger { - return impl.conf.Logger -} - -func (impl *serviceImplementation) Upsert(svc *apiv1.Service) { - impl.Logger().Info("Service was upserted", - "namespace", svc.Namespace, "name", svc.Name, - ) - - impl.eventCh <- &events.UpsertEvent{ - Resource: svc, - } -} - -func (impl *serviceImplementation) Remove(nsname types.NamespacedName) { - impl.Logger().Info("Service resource was removed", - "namespace", nsname.Namespace, "name", nsname.Name, - ) - - impl.eventCh <- &events.DeleteEvent{ - NamespacedName: nsname, - Type: &apiv1.Service{}, - } -} diff --git a/internal/manager/manager.go b/internal/manager/manager.go index 14c5150c4..90631418f 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -7,6 +7,7 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctlr "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -14,11 +15,7 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/config" "github.com/nginxinc/nginx-kubernetes-gateway/internal/events" - gw "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/gateway" - gc "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/gatewayclass" - hr "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/httproute" - secret "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/secret" - svc "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations/service" + "github.com/nginxinc/nginx-kubernetes-gateway/internal/implementations" ngxcfg "github.com/nginxinc/nginx-kubernetes-gateway/internal/nginx/config" "github.com/nginxinc/nginx-kubernetes-gateway/internal/nginx/file" ngxruntime "github.com/nginxinc/nginx-kubernetes-gateway/internal/nginx/runtime" @@ -60,25 +57,49 @@ func Start(cfg config.Config) error { return fmt.Errorf("cannot build runtime manager: %w", err) } - err = sdk.RegisterGatewayClassController(mgr, gc.NewGatewayClassImplementation(cfg, eventCh)) + // Register GatewayClass implementation + err = sdk.RegisterController[*gatewayv1beta1.GatewayClass](mgr, + implementations.NewImplementationWithFilter[*gatewayv1beta1.GatewayClass](cfg.Logger, eventCh, func(nsname types.NamespacedName) (bool, string) { + if nsname.Name == cfg.GatewayClassName { + return false, fmt.Sprintf("GatewayClass was upserted but ignored because this controller only supports the GatewayClass %s", cfg.GatewayClassName) + } + return true, "" + }), + ) if err != nil { - return fmt.Errorf("cannot register gatewayclass implementation: %w", err) + return fmt.Errorf("cannot register GatewayClass implementation: %w", err) } - err = sdk.RegisterGatewayController(mgr, gw.NewGatewayImplementation(cfg, eventCh)) + + // Register Gateway implementation + err = sdk.RegisterController[*gatewayv1beta1.Gateway](mgr, + implementations.NewImplementation[*gatewayv1beta1.Gateway](cfg.Logger, eventCh), + ) if err != nil { - return fmt.Errorf("cannot register gateway implementation: %w", err) + return fmt.Errorf("cannot register Gateway implementation: %w", err) } - err = sdk.RegisterHTTPRouteController(mgr, hr.NewHTTPRouteImplementation(cfg, eventCh)) + + // Register HTTPRoute implementation + err = sdk.RegisterController[*gatewayv1beta1.HTTPRoute](mgr, + implementations.NewImplementation[*gatewayv1beta1.HTTPRoute](cfg.Logger, eventCh), + ) if err != nil { - return fmt.Errorf("cannot register httproute implementation: %w", err) + return fmt.Errorf("cannot register HTTPRoute implementation: %w", err) } - err = sdk.RegisterServiceController(mgr, svc.NewServiceImplementation(cfg, eventCh)) + + // Register Service implementation + err = sdk.RegisterController[*apiv1.Service](mgr, + implementations.NewImplementation[*apiv1.Service](cfg.Logger, eventCh), + ) if err != nil { - return fmt.Errorf("cannot register service implementation: %w", err) + return fmt.Errorf("cannot register Service implementation: %w", err) } - err = sdk.RegisterSecretController(mgr, secret.NewSecretImplementation(cfg, eventCh)) + + // Register Secret implementation + err = sdk.RegisterController[*apiv1.Secret](mgr, + implementations.NewImplementation[*apiv1.Secret](cfg.Logger, eventCh), + ) if err != nil { - return fmt.Errorf("cannot register secret implementation: %w", err) + return fmt.Errorf("cannot register Secret implementation: %w", err) } secretStore := state.NewSecretStore() diff --git a/pkg/sdk/gateway_controller.go b/pkg/sdk/gateway_controller.go deleted file mode 100644 index a6c21f11d..000000000 --- a/pkg/sdk/gateway_controller.go +++ /dev/null @@ -1,56 +0,0 @@ -package sdk - -import ( - "context" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctlr "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -type gatewayReconciler struct { - client.Client - scheme *runtime.Scheme - impl GatewayImpl -} - -func RegisterGatewayController(mgr manager.Manager, impl GatewayImpl) error { - r := &gatewayReconciler{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - impl: impl, - } - - return ctlr.NewControllerManagedBy(mgr). - For(&v1beta1.Gateway{}). - Complete(r) -} - -func (r *gatewayReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("gateway", req.Name) - log.V(3).Info("Reconciling Gateway") - - found := true - var gw v1beta1.Gateway - err := r.Get(ctx, req.NamespacedName, &gw) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get Gateway") - return reconcile.Result{}, err - } - found = false - } - - if !found { - r.impl.Remove(req.NamespacedName) - return reconcile.Result{}, nil - } - - r.impl.Upsert(&gw) - return reconcile.Result{}, nil -} diff --git a/pkg/sdk/gatewayclass_controller.go b/pkg/sdk/gatewayclass_controller.go deleted file mode 100644 index d4a211c04..000000000 --- a/pkg/sdk/gatewayclass_controller.go +++ /dev/null @@ -1,56 +0,0 @@ -package sdk - -import ( - "golang.org/x/net/context" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -type gatewayClassReconciler struct { - client.Client - scheme *runtime.Scheme - impl GatewayClassImpl -} - -func RegisterGatewayClassController(mgr manager.Manager, impl GatewayClassImpl) error { - r := &gatewayClassReconciler{ - impl: impl, - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - } - - return ctrl.NewControllerManagedBy(mgr). - For(&v1beta1.GatewayClass{}). - Complete(r) -} - -func (r *gatewayClassReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("gatewayclass", req.Name) - log.V(3).Info("Reconciling GatewayClass") - - var gc v1beta1.GatewayClass - found := true - - err := r.Get(ctx, req.NamespacedName, &gc) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get GatewayClass") - return reconcile.Result{}, err - } - found = false - } - - if !found { - r.impl.Remove(req.NamespacedName) - return reconcile.Result{}, nil - } - - r.impl.Upsert(&gc) - return reconcile.Result{}, nil -} diff --git a/pkg/sdk/gatewayconfig_controller.go b/pkg/sdk/gatewayconfig_controller.go deleted file mode 100644 index 8631252d3..000000000 --- a/pkg/sdk/gatewayconfig_controller.go +++ /dev/null @@ -1,57 +0,0 @@ -package sdk - -import ( - "context" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctlr "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - nginxgwv1alpha1 "github.com/nginxinc/nginx-kubernetes-gateway/pkg/apis/gateway/v1alpha1" -) - -type gatewayConfigReconciler struct { - client.Client - scheme *runtime.Scheme - impl GatewayConfigImpl -} - -func RegisterGatewayConfigController(mgr manager.Manager, impl GatewayConfigImpl) error { - r := &gatewayConfigReconciler{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - impl: impl, - } - - return ctlr.NewControllerManagedBy(mgr). - For(&nginxgwv1alpha1.GatewayConfig{}). - Complete(r) -} - -func (r *gatewayConfigReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("gatewayconfig", req.Name) - log.V(3).Info("Reconciling GatewayConfig") - - found := true - var gcfg nginxgwv1alpha1.GatewayConfig - err := r.Get(ctx, req.NamespacedName, &gcfg) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get GatewayConfig") - return reconcile.Result{}, err - } - found = false - } - - if !found { - r.impl.Remove(req.Name) - return reconcile.Result{}, nil - } - - r.impl.Upsert(&gcfg) - return reconcile.Result{}, nil -} diff --git a/pkg/sdk/httproute_controller.go b/pkg/sdk/httproute_controller.go deleted file mode 100644 index d5b6dc258..000000000 --- a/pkg/sdk/httproute_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -package sdk - -import ( - "context" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctlr "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -type httpRouteReconciler struct { - client.Client - scheme *runtime.Scheme - impl HTTPRouteImpl -} - -// RegisterHTTPRouteController registers the HTTPRouteController in the manager. -func RegisterHTTPRouteController(mgr manager.Manager, impl HTTPRouteImpl) error { - r := &httpRouteReconciler{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - impl: impl, - } - - return ctlr.NewControllerManagedBy(mgr). - For(&v1beta1.HTTPRoute{}). - Complete(r) -} - -func (r *httpRouteReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("httpRoute", req.NamespacedName) - - log.V(3).Info("Reconciling HTTPRoute") - - found := true - var hr v1beta1.HTTPRoute - err := r.Get(ctx, req.NamespacedName, &hr) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get HTTPRoute") - return reconcile.Result{}, err - } - found = false - } - - if !found { - log.V(3).Info("Removing HTTPRoute") - - r.impl.Remove(req.NamespacedName) - return reconcile.Result{}, nil - } - - log.V(3).Info("Upserting HTTPRoute") - - r.impl.Upsert(&hr) - return reconcile.Result{}, nil -} diff --git a/pkg/sdk/interfaces.go b/pkg/sdk/interfaces.go index 7df8965fc..e1f0d29da 100644 --- a/pkg/sdk/interfaces.go +++ b/pkg/sdk/interfaces.go @@ -1,40 +1,15 @@ package sdk import ( - apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/apis/v1beta1" - - nginxgwv1alpha1 "github.com/nginxinc/nginx-kubernetes-gateway/pkg/apis/gateway/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" ) -type GatewayClassImpl interface { - Upsert(gc *v1beta1.GatewayClass) - Remove(nsname types.NamespacedName) -} - -type GatewayImpl interface { - Upsert(*v1beta1.Gateway) - Remove(types.NamespacedName) -} - -type GatewayConfigImpl interface { - Upsert(config *nginxgwv1alpha1.GatewayConfig) - Remove(string) -} - -type HTTPRouteImpl interface { - Upsert(config *v1beta1.HTTPRoute) - // FIXME(pleshakov): change other interfaces to use types.NamespacedName - Remove(types.NamespacedName) -} - -type ServiceImpl interface { - Upsert(svc *apiv1.Service) +type Implementation[T ObjectConstraint] interface { + Upsert(obj T) Remove(nsname types.NamespacedName) } -type SecretImpl interface { - Upsert(secret *apiv1.Secret) - Remove(name types.NamespacedName) +type ObjectConstraint interface { + client.Object } diff --git a/pkg/sdk/reconciler.go b/pkg/sdk/reconciler.go new file mode 100644 index 000000000..04046055e --- /dev/null +++ b/pkg/sdk/reconciler.go @@ -0,0 +1,83 @@ +package sdk + +import ( + "context" + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + ctlr "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type reconciler[T ObjectConstraint] struct { + client.Client + impl Implementation[T] + resourceKind string +} + +var _ reconcile.Reconciler = &reconciler[client.Object]{} + +func RegisterController[T ObjectConstraint](mgr manager.Manager, impl Implementation[T]) error { + return RegisterControllerWithEventFilter(mgr, impl, nil) +} + +// To handle new controllers from PR https://github.com/nginxinc/nginx-kubernetes-gateway/pull/221 +func RegisterControllerWithEventFilter[T ObjectConstraint]( + mgr manager.Manager, + impl Implementation[T], + filter predicate.Predicate, +) error { + var obj T + + r := &reconciler[T]{ + Client: mgr.GetClient(), + impl: impl, + resourceKind: reflect.TypeOf(obj).Elem().Name(), + } + + obj = reflect.New(reflect.TypeOf(obj).Elem()).Interface().(T) + + builder := ctlr.NewControllerManagedBy(mgr).For(obj) + + if filter != nil { + builder = builder.WithEventFilter(filter) + } + + return builder.Complete(r) +} + +func (r *reconciler[T]) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + log := log.FromContext(ctx).WithValues( + "namespace", req.Namespace, + "name", req.Name, + ) + + log.Info(fmt.Sprintf("Reconciling %s", r.resourceKind)) + + found := true + + var obj T + obj = reflect.New(reflect.TypeOf(obj).Elem()).Interface().(T) + + err := r.Get(ctx, req.NamespacedName, obj) + if err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, fmt.Sprintf("Failed to get %s", r.resourceKind)) + return reconcile.Result{}, err + } + found = false + } + + if !found { + r.impl.Remove(req.NamespacedName) + return reconcile.Result{}, nil + } + + r.impl.Upsert(obj) + return reconcile.Result{}, nil +} diff --git a/pkg/sdk/secret_controller.go b/pkg/sdk/secret_controller.go deleted file mode 100644 index 9f682d089..000000000 --- a/pkg/sdk/secret_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -package sdk - -import ( - "context" - - apiv1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctlr "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -type secretReconciler struct { - client.Client - scheme *runtime.Scheme - impl SecretImpl -} - -// RegisterSecretController registers the SecretController in the manager. -func RegisterSecretController(mgr manager.Manager, impl SecretImpl) error { - r := &secretReconciler{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - impl: impl, - } - - return ctlr.NewControllerManagedBy(mgr). - For(&apiv1.Secret{}). - Complete(r) -} - -func (r *secretReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("secret", req.NamespacedName) - - log.V(3).Info("Reconciling Secret") - - found := true - var secret apiv1.Secret - err := r.Get(ctx, req.NamespacedName, &secret) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get Secret") - return reconcile.Result{}, err - } - found = false - } - - if !found { - log.V(3).Info("Removing Secret") - - r.impl.Remove(req.NamespacedName) - return reconcile.Result{}, nil - } - - log.V(3).Info("Upserting Secret") - - r.impl.Upsert(&secret) - return reconcile.Result{}, nil -} diff --git a/pkg/sdk/service_controller.go b/pkg/sdk/service_controller.go deleted file mode 100644 index 3d28a2564..000000000 --- a/pkg/sdk/service_controller.go +++ /dev/null @@ -1,62 +0,0 @@ -package sdk - -import ( - "context" - - apiv1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctlr "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -type serviceReconciler struct { - client.Client - scheme *runtime.Scheme - impl ServiceImpl -} - -// RegisterServiceController registers the ServiceController in the manager. -func RegisterServiceController(mgr manager.Manager, impl ServiceImpl) error { - r := &serviceReconciler{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), - impl: impl, - } - - return ctlr.NewControllerManagedBy(mgr). - For(&apiv1.Service{}). - Complete(r) -} - -func (r *serviceReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := log.FromContext(ctx).WithValues("service", req.NamespacedName) - - log.V(3).Info("Reconciling Service") - - found := true - var svc apiv1.Service - err := r.Get(ctx, req.NamespacedName, &svc) - if err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get Service") - return reconcile.Result{}, err - } - found = false - } - - if !found { - log.V(3).Info("Removing Service") - - r.impl.Remove(req.NamespacedName) - return reconcile.Result{}, nil - } - - log.V(3).Info("Upserting Service") - - r.impl.Upsert(&svc) - return reconcile.Result{}, nil -}