diff --git a/internal/manager/controller_setup.go b/internal/manager/controller_setup.go new file mode 100644 index 00000000..f87cfff3 --- /dev/null +++ b/internal/manager/controller_setup.go @@ -0,0 +1,110 @@ +package manager + +import ( + "reflect" + + "github.com/kong/gateway-operator/controllers" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +// ----------------------------------------------------------------------------- +// Controller Manager - Controller Definition Interfaces +// ----------------------------------------------------------------------------- + +// Controller is a Kubernetes controller that can be plugged into Manager. +type Controller interface { + SetupWithManager(ctrl.Manager) error +} + +// AutoHandler decides whether the specific controller shall be enabled (true) or disabled (false). +type AutoHandler func(client.Client) bool + +// ControllerDef is a specification of a Controller that can be conditionally registered with Manager. +type ControllerDef struct { + Enabled bool + AutoHandler AutoHandler + Controller Controller +} + +// Name returns a human-readable name of the controller. +func (c *ControllerDef) Name() string { + return reflect.TypeOf(c.Controller).String() +} + +// MaybeSetupWithManager runs SetupWithManager on the controller if it is enabled +// and its AutoHandler (if any) indicates that it can load. +func (c *ControllerDef) MaybeSetupWithManager(mgr ctrl.Manager) error { + if !c.Enabled { + return nil + } + + if c.AutoHandler != nil { + if enable := c.AutoHandler(mgr.GetClient()); !enable { + return nil + } + } + return c.Controller.SetupWithManager(mgr) +} + +func setupControllers(mgr manager.Manager, c *Config) []ControllerDef { + controllers := []ControllerDef{ + // Gateway controller + { + Enabled: c.GatewayControllerEnabled, + AutoHandler: crdExistsChecker{ + GVR: schema.GroupVersionResource{ + Group: gatewayv1alpha2.SchemeGroupVersion.Group, + Version: gatewayv1alpha2.SchemeGroupVersion.Version, + Resource: "gateways", + }, + }.CRDExists, + Controller: &controllers.GatewayReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }, + }, + // ControlPlane controller + { + Enabled: c.ControlPlaneControllerEnabled, + Controller: &controllers.ControlPlaneReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterCASecretName: c.ClusterCASecretName, + ClusterCASecretNamespace: c.ClusterCASecretNamespace, + }, + }, + // DataPlane controller + { + Enabled: c.DataPlaneControllerEnabled, + Controller: &controllers.DataPlaneReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ClusterCASecretName: c.ClusterCASecretName, + ClusterCASecretNamespace: c.ClusterCASecretNamespace, + }, + }, + } + + return controllers +} + +// crdExistsChecker verifies whether the resource type defined by GVR is supported by the k8s apiserver. +type crdExistsChecker struct { + GVR schema.GroupVersionResource +} + +// CRDExists returns true iff the apiserver supports the specified group/version/resource. +func (c crdExistsChecker) CRDExists(r client.Client) bool { + return CRDExists(r, c.GVR) +} + +// CRDExists returns false if CRD does not exist. +func CRDExists(client client.Client, gvr schema.GroupVersionResource) bool { + _, err := client.RESTMapper().KindFor(gvr) + return !meta.IsNoMatchError(err) +} diff --git a/internal/manager/run.go b/internal/manager/run.go index 6fa1eb34..ec7ff785 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -49,7 +49,6 @@ import ( gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" operatorv1alpha1 "github.com/kong/gateway-operator/apis/v1alpha1" - "github.com/kong/gateway-operator/controllers" "github.com/kong/gateway-operator/internal/admission" "github.com/kong/gateway-operator/internal/manager/metadata" "github.com/kong/gateway-operator/internal/telemetry" @@ -90,18 +89,27 @@ type Config struct { KubeconfigPath string ClusterCASecretName string ClusterCASecretNamespace string + + GatewayControllerEnabled bool + ControlPlaneControllerEnabled bool + DataPlaneControllerEnabled bool } -var DefaultConfig = Config{ - MetricsAddr: ":8080", - ProbeAddr: ":8081", - WebhookPort: 9443, - DevelopmentMode: false, - LeaderElection: true, - ClusterCASecretName: "kong-operator-ca", - // TODO: Extract this into a named const and use it in all the placed where - // "kong-system" is used verbatim: https://github.com/Kong/gateway-operator/pull/149. - ClusterCASecretNamespace: "kong-system", +func DefaultConfig() Config { + return Config{ + MetricsAddr: ":8080", + ProbeAddr: ":8081", + WebhookPort: 9443, + DevelopmentMode: false, + LeaderElection: true, + ClusterCASecretName: "kong-operator-ca", + // TODO: Extract this into a named const and use it in all the placed where + // "kong-system" is used verbatim: https://github.com/Kong/gateway-operator/pull/149. + ClusterCASecretNamespace: "kong-system", + GatewayControllerEnabled: true, + ControlPlaneControllerEnabled: true, + DataPlaneControllerEnabled: true, + } } func Run(cfg Config) error { @@ -143,28 +151,14 @@ func Run(cfg Config) error { return fmt.Errorf("unable to start manager: %w", err) } - if err = (&controllers.DataPlaneReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ClusterCASecretName: cfg.ClusterCASecretName, - ClusterCASecretNamespace: cfg.ClusterCASecretNamespace, - }).SetupWithManager(mgr); err != nil { - return fmt.Errorf("unable to create controller DataPlane: %w", err) - } - if err = (&controllers.ControlPlaneReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ClusterCASecretName: cfg.ClusterCASecretName, - ClusterCASecretNamespace: cfg.ClusterCASecretNamespace, - }).SetupWithManager(mgr); err != nil { - return fmt.Errorf("unable to create controller ControlPlane: %w", err) - } - if err = (&controllers.GatewayReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - return fmt.Errorf("unable to create controller Gateway: %w", err) + // load controllers + controllers := setupControllers(mgr, &cfg) + for _, c := range controllers { + if err := c.MaybeSetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create controller %q: %w", c.Name(), err) + } } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/main.go b/main.go index b6d2bd0d..85c0d8bd 100644 --- a/main.go +++ b/main.go @@ -28,15 +28,18 @@ import ( func main() { var ( - metricsAddr string - probeAddr string - disableLeaderElection bool - controllerName string - anonymousReports bool - apiServerHost string - kubeconfigPath string - clusterCASecret string - clusterCASecretNamespace string + metricsAddr string + probeAddr string + disableLeaderElection bool + controllerName string + anonymousReports bool + apiServerHost string + kubeconfigPath string + clusterCASecret string + clusterCASecretNamespace string + enableControllerGateway bool + enableControllerControlPlane bool + enableControllerDataPlane bool ) flagSet := flag.NewFlagSet("", flag.ExitOnError) @@ -52,17 +55,22 @@ func main() { flagSet.StringVar(&controllerName, "controller-name", "", "a controller name to use if other than the default, only needed for multi-tenancy") flagSet.StringVar(&clusterCASecret, "cluster-ca-secret", "kong-operator-ca", "name of the Secret containing the cluster CA certificate") flagSet.StringVar(&clusterCASecretNamespace, "cluster-ca-secret-namespace", "", "name of the namespace for Secret containing the cluster CA certificate") + + flagSet.BoolVar(&enableControllerGateway, "enable-controller-gateway", true, "Enable the Gateway controller.") + flagSet.BoolVar(&enableControllerControlPlane, "enable-controller-controlplane", true, "Enable the ControlPlane controller.") + flagSet.BoolVar(&enableControllerDataPlane, "enable-controller-dataplane", true, "Enable the DataPlane controller.") + if err := flagSet.Parse(os.Args[1:]); err != nil { fmt.Println(err.Error()) os.Exit(1) } - developmentModeEnabled := manager.DefaultConfig.DevelopmentMode + developmentModeEnabled := manager.DefaultConfig().DevelopmentMode if v := os.Getenv("CONTROLLER_DEVELOPMENT_MODE"); v == "true" { // TODO: clean env handling https://github.com/Kong/gateway-operator/issues/19 fmt.Println("INFO: development mode has been enabled") developmentModeEnabled = true } - leaderElection := manager.DefaultConfig.LeaderElection + leaderElection := manager.DefaultConfig().LeaderElection if disableLeaderElection { fmt.Println("INFO: leader election has been disabled") leaderElection = false @@ -91,16 +99,19 @@ func main() { } cfg := manager.Config{ - DevelopmentMode: developmentModeEnabled, - MetricsAddr: metricsAddr, - ProbeAddr: probeAddr, - LeaderElection: leaderElection, - ControllerName: controllerName, - AnonymousReports: anonymousReports, - APIServerPath: apiServerHost, - KubeconfigPath: kubeconfigPath, - ClusterCASecretName: clusterCASecret, - ClusterCASecretNamespace: clusterCASecretNamespace, + DevelopmentMode: developmentModeEnabled, + MetricsAddr: metricsAddr, + ProbeAddr: probeAddr, + LeaderElection: leaderElection, + ControllerName: controllerName, + AnonymousReports: anonymousReports, + APIServerPath: apiServerHost, + KubeconfigPath: kubeconfigPath, + ClusterCASecretName: clusterCASecret, + ClusterCASecretNamespace: clusterCASecretNamespace, + GatewayControllerEnabled: enableControllerGateway, + ControlPlaneControllerEnabled: enableControllerControlPlane, + DataPlaneControllerEnabled: enableControllerDataPlane, } if err := manager.Run(cfg); err != nil { diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go index 23845c16..b5981235 100644 --- a/test/integration/suite_test.go +++ b/test/integration/suite_test.go @@ -154,7 +154,7 @@ func TestMain(m *testing.M) { timeout := time.Now().Add(time.Minute) for timeout.After(time.Now()) { err = func() error { - ca, err := k8sClient.CoreV1().Secrets("kong-system").Get(ctx, manager.DefaultConfig.ClusterCASecretName, metav1.GetOptions{}) + ca, err := k8sClient.CoreV1().Secrets("kong-system").Get(ctx, manager.DefaultConfig().ClusterCASecretName, metav1.GetOptions{}) if err != nil { return err } @@ -242,10 +242,13 @@ func setupControllerLogger() (closeLogFile func() error) { } func startControllerManager() { - cfg := manager.DefaultConfig + cfg := manager.DefaultConfig() cfg.LeaderElection = false cfg.DevelopmentMode = true cfg.ControllerName = "konghq.com/gateway-operator-integration-tests" + cfg.GatewayControllerEnabled = true + cfg.ControlPlaneControllerEnabled = true + cfg.DataPlaneControllerEnabled = true if runWebhookTests { cfg.WebhookCertDir = webhookCertDir