@@ -12,6 +12,7 @@ import (
1212 "net/http/pprof"
1313 "os"
1414 "path/filepath"
15+ "sort"
1516 "strconv"
1617 "strings"
1718 "time"
@@ -91,6 +92,7 @@ import (
9192 lsvalidation "github.com/elastic/cloud-on-k8s/v3/pkg/controller/logstash/validation"
9293 "github.com/elastic/cloud-on-k8s/v3/pkg/controller/maps"
9394 "github.com/elastic/cloud-on-k8s/v3/pkg/controller/packageregistry"
95+ "github.com/elastic/cloud-on-k8s/v3/pkg/controller/namespacefilter"
9496 "github.com/elastic/cloud-on-k8s/v3/pkg/controller/remotecluster"
9597 "github.com/elastic/cloud-on-k8s/v3/pkg/controller/stackconfigpolicy"
9698 "github.com/elastic/cloud-on-k8s/v3/pkg/controller/webhook"
@@ -580,6 +582,13 @@ func startOperator(ctx context.Context) error {
580582 // also set up the v1beta1 scheme, used by the v1beta1 webhook
581583 controllerscheme .SetupV1beta1Scheme ()
582584
585+ // Setup a client to query namespaces and set operator uuid config map.
586+ clientset , err := kubernetes .NewForConfig (cfg )
587+ if err != nil {
588+ log .Error (err , "Failed to create Kubernetes client" )
589+ return err
590+ }
591+
583592 // Create a new Cmd to provide shared dependencies and start components
584593 opts := ctrl.Options {
585594 Scheme : clientgoscheme .Scheme ,
@@ -597,22 +606,57 @@ func startOperator(ctx context.Context) error {
597606 log .Error (err , "Failed to parse managed namespaces flag" )
598607 return err
599608 }
600- switch {
601- case len (managedNamespaces ) == 0 :
602- log .Info ("Operator configured to manage all namespaces" )
603- case len (managedNamespaces ) == 1 && managedNamespaces [0 ] == operatorNamespace :
604- log .Info ("Operator configured to manage a single namespace" , "namespace" , managedNamespaces [0 ], "operator_namespace" , operatorNamespace )
605609
606- default :
607- log .Info ("Operator configured to manage multiple namespaces" , "namespaces" , managedNamespaces , "operator_namespace" , operatorNamespace )
610+ namespaceLabelSelectorStr := viper .GetString (operator .NamespaceLabelSelectorFlag )
611+ configuredNamespaces := managedNamespaces
612+ managedNamespaces , namespaceLabelSelector , err := resolveManagedNamespaces (ctx , clientset , configuredNamespaces , namespaceLabelSelectorStr )
613+ if err != nil {
614+ log .Error (err , "Failed to resolve managed namespaces from selector" , "selector" , namespaceLabelSelectorStr )
615+ return err
616+ }
617+
618+ var namespaceFilter * operator.NamespaceFilter
619+ if namespaceLabelSelector != nil {
620+ namespaceFilter , err = operator .NewNamespaceFilter (namespaceLabelSelector , configuredNamespaces , managedNamespaces )
621+ if err != nil {
622+ log .Error (err , "Failed to initialize namespace filter" )
623+ return err
624+ }
625+ }
626+
627+ restrictedScope := len (configuredNamespaces ) > 0
628+ if ! restrictedScope {
629+ if namespaceLabelSelector == nil {
630+ log .Info ("Operator configured to manage all namespaces" )
631+ } else {
632+ log .Info ("Operator configured for dynamic namespace label filtering" , "selector" , namespaceLabelSelector , "initial_managed_namespaces" , managedNamespaces )
633+ }
634+ } else {
608635 // The managed cache should always include the operator namespace so that we can work with operator-internal resources.
609- managedNamespaces = append (managedNamespaces , operatorNamespace )
636+ managedNamespaces = appendIfMissing (configuredNamespaces , operatorNamespace )
637+ managedNamespaces = uniqueNamespaces (managedNamespaces )
638+ sort .Strings (managedNamespaces )
639+
640+ switch {
641+ case namespaceLabelSelector != nil && len (managedNamespaces ) == 1 && managedNamespaces [0 ] == operatorNamespace :
642+ log .Info ("Namespace label selector matched no namespaces; limiting cache to operator namespace" , "operator_namespace" , operatorNamespace , "selector" , namespaceLabelSelectorStr )
643+ case len (managedNamespaces ) == 1 :
644+ log .Info ("Operator configured to manage a single namespace" , "namespace" , managedNamespaces [0 ], "operator_namespace" , operatorNamespace )
645+ default :
646+ log .Info ("Operator configured to manage multiple namespaces" , "namespaces" , managedNamespaces , "operator_namespace" , operatorNamespace )
647+ }
648+
649+ if namespaceLabelSelector != nil {
650+ log .Info ("Namespace label selector configured" , "selector" , namespaceLabelSelector )
651+ }
610652 }
611653
612- // implicitly allows watching cluster-scoped resources (e.g. storage classes)
613- opts .Cache = cache.Options {DefaultNamespaces : map [string ]cache.Config {}}
614- for _ , ns := range managedNamespaces {
615- opts .Cache .DefaultNamespaces [ns ] = cache.Config {}
654+ if restrictedScope {
655+ // implicitly allows watching cluster-scoped resources (e.g. storage classes)
656+ opts .Cache = cache.Options {DefaultNamespaces : map [string ]cache.Config {}}
657+ for _ , ns := range managedNamespaces {
658+ opts .Cache .DefaultNamespaces [ns ] = cache.Config {}
659+ }
616660 }
617661
618662 // only expose prometheus metrics if provided a non-zero port
@@ -676,13 +720,6 @@ func startOperator(ctx context.Context) error {
676720 return err
677721 }
678722
679- // Setup a client to set the operator uuid config map
680- clientset , err := kubernetes .NewForConfig (cfg )
681- if err != nil {
682- log .Error (err , "Failed to create Kubernetes client" )
683- return err
684- }
685-
686723 distributionChannel := viper .GetString (operator .DistributionChannelFlag )
687724 operatorInfo , err := about .GetOperatorInfo (clientset , operatorNamespace , distributionChannel )
688725 if err != nil {
@@ -725,19 +762,6 @@ func startOperator(ctx context.Context) error {
725762 return err
726763 }
727764
728- // Parse namespace label selector if provided
729- var namespaceLabelSelector * metav1.LabelSelector
730- namespaceLabelSelectorStr := viper .GetString (operator .NamespaceLabelSelectorFlag )
731- if namespaceLabelSelectorStr != "" {
732- selector , err := metav1 .ParseToLabelSelector (namespaceLabelSelectorStr )
733- if err != nil {
734- log .Error (err , "Failed to parse namespace label selector" , "selector" , namespaceLabelSelectorStr )
735- return err
736- }
737- namespaceLabelSelector = selector
738- log .Info ("Namespace label selector configured" , "selector" , namespaceLabelSelector )
739- }
740-
741765 params := operator.Parameters {
742766 Dialer : dialer ,
743767 ElasticsearchObservationInterval : viper .GetDuration (operator .ElasticsearchObservationIntervalFlag ),
@@ -761,6 +785,7 @@ func startOperator(ctx context.Context) error {
761785 ValidateStorageClass : viper .GetBool (operator .ValidateStorageClassFlag ),
762786 Tracer : tracer ,
763787 NamespaceLabelSelector : namespaceLabelSelector ,
788+ NamespaceFilter : namespaceFilter ,
764789 }
765790
766791 if viper .GetBool (operator .EnableWebhookFlag ) {
@@ -830,6 +855,75 @@ func readOptionalCA(caDir string) (*certificates.CA, error) {
830855 return certificates .BuildCAFromFile (caDir )
831856}
832857
858+ func resolveManagedNamespaces (
859+ ctx context.Context ,
860+ clientset kubernetes.Interface ,
861+ configuredNamespaces []string ,
862+ namespaceLabelSelectorStr string ,
863+ ) ([]string , * metav1.LabelSelector , error ) {
864+ if namespaceLabelSelectorStr == "" {
865+ return configuredNamespaces , nil , nil
866+ }
867+
868+ namespaceLabelSelector , err := metav1 .ParseToLabelSelector (namespaceLabelSelectorStr )
869+ if err != nil {
870+ return nil , nil , err
871+ }
872+
873+ namespaceList , err := clientset .CoreV1 ().Namespaces ().List (ctx , metav1.ListOptions {LabelSelector : namespaceLabelSelectorStr })
874+ if err != nil {
875+ return nil , nil , err
876+ }
877+
878+ selectorNamespaces := make ([]string , 0 , len (namespaceList .Items ))
879+ for _ , namespace := range namespaceList .Items {
880+ selectorNamespaces = append (selectorNamespaces , namespace .Name )
881+ }
882+
883+ if len (configuredNamespaces ) == 0 {
884+ return selectorNamespaces , namespaceLabelSelector , nil
885+ }
886+
887+ selectorNamespacesSet := make (map [string ]struct {}, len (selectorNamespaces ))
888+ for _ , namespace := range selectorNamespaces {
889+ selectorNamespacesSet [namespace ] = struct {}{}
890+ }
891+
892+ filteredNamespaces := make ([]string , 0 , len (configuredNamespaces ))
893+ for _ , namespace := range configuredNamespaces {
894+ if _ , exists := selectorNamespacesSet [namespace ]; exists {
895+ filteredNamespaces = append (filteredNamespaces , namespace )
896+ }
897+ }
898+
899+ return filteredNamespaces , namespaceLabelSelector , nil
900+ }
901+
902+ func appendIfMissing (namespaces []string , namespace string ) []string {
903+ for _ , existing := range namespaces {
904+ if existing == namespace {
905+ return namespaces
906+ }
907+ }
908+ return append (namespaces , namespace )
909+ }
910+
911+ func uniqueNamespaces (namespaces []string ) []string {
912+ unique := make ([]string , 0 , len (namespaces ))
913+ seen := make (map [string ]struct {}, len (namespaces ))
914+ for _ , namespace := range namespaces {
915+ if namespace == "" {
916+ continue
917+ }
918+ if _ , alreadySeen := seen [namespace ]; alreadySeen {
919+ continue
920+ }
921+ seen [namespace ] = struct {}{}
922+ unique = append (unique , namespace )
923+ }
924+ return unique
925+ }
926+
833927// asyncTasks schedules some tasks to be started when this instance of the operator is elected
834928func asyncTasks (
835929 ctx context.Context ,
@@ -936,6 +1030,7 @@ func registerControllers(mgr manager.Manager, params operator.Parameters, access
9361030 name string
9371031 registerFunc func (manager.Manager , operator.Parameters ) error
9381032 }{
1033+ {name : "NamespaceFilter" , registerFunc : namespacefilter .Add },
9391034 {name : "APMServer" , registerFunc : apmserver .Add },
9401035 {name : "Elasticsearch" , registerFunc : elasticsearch .Add },
9411036 {name : "ElasticsearchAutoscaling" , registerFunc : autoscaling .Add },
0 commit comments