From c10e6653450b1d9f64130f5293584a9242d1cfdb Mon Sep 17 00:00:00 2001 From: Wilfried Roset Date: Mon, 20 Jan 2025 16:19:17 +0100 Subject: [PATCH 1/2] feat: Allow to restrict the CRs watched according to their labels Signed-off-by: Wilfried Roset --- deploy/helm/grafana-operator/README.md | 1 + .../templates/deployment.yaml | 6 +++++ deploy/helm/grafana-operator/values.yaml | 6 +++++ main.go | 24 +++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/deploy/helm/grafana-operator/README.md b/deploy/helm/grafana-operator/README.md index f4f044555..6eccd8975 100644 --- a/deploy/helm/grafana-operator/README.md +++ b/deploy/helm/grafana-operator/README.md @@ -104,5 +104,6 @@ It's easier to just manage this configuration outside of the operator. | serviceMonitor.targetLabels | list | `[]` | Set of labels to transfer from the Kubernetes Service onto the target | | serviceMonitor.telemetryPath | string | `"/metrics"` | Set path to metrics path | | tolerations | list | `[]` | pod tolerations | +| watchLabelSelectors | object | `{}` | Sets the `WATCH_LABEL_SELECTORS` environment variable, if defines which CRs are watched according to thei labels. By default, the operator watches all CRs. To make it watch only a subset of CRs, set the value to a map of key/value | | watchNamespaceSelector | string | `""` | Sets the `WATCH_NAMESPACE_SELECTOR` environment variable, it defines which namespaces the operator should be listening for based on a namespace label (e.g. `"environment: dev"`). By default, the operator watches all namespaces. To make it watch only its own namespace, check out `namespaceScope` option instead. | | watchNamespaces | string | `""` | Sets the `WATCH_NAMESPACE` environment variable, it defines which namespaces the operator should be listening for (e.g. `"grafana, foo"`). By default, the operator watches all namespaces. To make it watch only its own namespace, check out `namespaceScope` option instead. | diff --git a/deploy/helm/grafana-operator/templates/deployment.yaml b/deploy/helm/grafana-operator/templates/deployment.yaml index 0ea27aeb5..f1699167f 100644 --- a/deploy/helm/grafana-operator/templates/deployment.yaml +++ b/deploy/helm/grafana-operator/templates/deployment.yaml @@ -51,6 +51,12 @@ spec: {{ else }} value: {{quote .Values.watchNamespaceSelector }} {{- end }} + - name: WATCH_LABEL_SELECTORS + {{- if and .Values.watchLabelSelectors (eq .Values.watchLabelSelectors "") }} + value: "" + {{ else }} + value: {{quote .Values.watchLabelSelectors | toJson }} + {{- end }} {{- with .Values.env }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/deploy/helm/grafana-operator/values.yaml b/deploy/helm/grafana-operator/values.yaml index 489fb9c6b..34a6ef445 100644 --- a/deploy/helm/grafana-operator/values.yaml +++ b/deploy/helm/grafana-operator/values.yaml @@ -15,6 +15,12 @@ watchNamespaces: "" # By default, the operator watches all namespaces. To make it watch only its own namespace, check out `namespaceScope` option instead. watchNamespaceSelector: "" +# -- Sets the `WATCH_LABEL_SELECTORS` environment variable, +# if defines which CRs are watched according to thei labels. +# By default, the operator watches all CRs. To make it watch only a subset of CRs, set the value to a map of key/value +watchLabelSelectors: {} +# labelKey: labelValue + # -- Determines if the target cluster is OpenShift. Additional rbac permissions for routes will be added on OpenShift isOpenShift: false diff --git a/main.go b/main.go index 85828cabd..e18328cc6 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ package main import ( "context" + "encoding/json" "flag" "os" "os/signal" @@ -48,6 +49,7 @@ import ( "github.com/grafana/grafana-operator/v5/controllers/autodetect" "github.com/grafana/grafana-operator/v5/embeds" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -65,6 +67,10 @@ const ( // eg: "environment: dev" // If empty or undefined, the operator will run in cluster scope. watchNamespaceEnvSelector = "WATCH_NAMESPACE_SELECTOR" + // watchLabelSelectorsEnvVar is the constant for env variable WATCH_LABEL_SELECTORS which specifies the resources to watch according to their labels. + // eg: '{"labelKey": "labelValue"}' + // If empty of undefined, the operator will watch all CRs. + watchLabelSelectorsEnvVar = "WATCH_LABEL_SELECTORS" ) var ( @@ -105,6 +111,7 @@ func main() { watchNamespace, _ := os.LookupEnv(watchNamespaceEnvVar) watchNamespaceSelector, _ := os.LookupEnv(watchNamespaceEnvSelector) + watchLabelSelectors, _ := os.LookupEnv(watchLabelSelectorsEnvVar) controllerOptions := ctrl.Options{ Scheme: scheme, @@ -183,6 +190,23 @@ func main() { setupLog.Info("operator running in cluster scoped mode") } + if watchLabelSelectors != "" { + labelSelectors := map[string]string{} + err := json.Unmarshal([]byte(watchLabelSelectors), &labelSelectors) + if err != nil { + setupLog.Error(err, "unable to Unmarshal labelSelectors") + os.Exit(1) //nolint + } + for k, v := range labelSelectors { + req, err := labels.NewRequirement(k, selection.Equals, []string{v}, nil) + if err != nil { + setupLog.Error(err, "unable to create labels selector") + os.Exit(1) //nolint + } + controllerOptions.Cache.DefaultLabelSelector.Add(*req) + } + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE) defer stop() From b06e4da92a2cec4a86dceea349b14ebc2fea815b Mon Sep 17 00:00:00 2001 From: Wilfried Roset Date: Tue, 21 Jan 2025 09:43:53 +0100 Subject: [PATCH 2/2] Use Set-based requirement to simplify the support of labelSelectors definition Signed-off-by: Wilfried Roset --- deploy/helm/grafana-operator/README.md | 2 +- .../templates/deployment.yaml | 2 +- deploy/helm/grafana-operator/values.yaml | 9 +++-- main.go | 39 ++++++++----------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/deploy/helm/grafana-operator/README.md b/deploy/helm/grafana-operator/README.md index 6eccd8975..8883fe7f1 100644 --- a/deploy/helm/grafana-operator/README.md +++ b/deploy/helm/grafana-operator/README.md @@ -104,6 +104,6 @@ It's easier to just manage this configuration outside of the operator. | serviceMonitor.targetLabels | list | `[]` | Set of labels to transfer from the Kubernetes Service onto the target | | serviceMonitor.telemetryPath | string | `"/metrics"` | Set path to metrics path | | tolerations | list | `[]` | pod tolerations | -| watchLabelSelectors | object | `{}` | Sets the `WATCH_LABEL_SELECTORS` environment variable, if defines which CRs are watched according to thei labels. By default, the operator watches all CRs. To make it watch only a subset of CRs, set the value to a map of key/value | +| watchLabelSelectors | string | `""` | Sets the `WATCH_LABEL_SELECTORS` environment variable, if defines which CRs are watched according to thei labels. By default, the operator watches all CRs. To make it watch only a subset of CRs, define the variable as a *Set-based requirement* See also: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ | | watchNamespaceSelector | string | `""` | Sets the `WATCH_NAMESPACE_SELECTOR` environment variable, it defines which namespaces the operator should be listening for based on a namespace label (e.g. `"environment: dev"`). By default, the operator watches all namespaces. To make it watch only its own namespace, check out `namespaceScope` option instead. | | watchNamespaces | string | `""` | Sets the `WATCH_NAMESPACE` environment variable, it defines which namespaces the operator should be listening for (e.g. `"grafana, foo"`). By default, the operator watches all namespaces. To make it watch only its own namespace, check out `namespaceScope` option instead. | diff --git a/deploy/helm/grafana-operator/templates/deployment.yaml b/deploy/helm/grafana-operator/templates/deployment.yaml index f1699167f..459087a8f 100644 --- a/deploy/helm/grafana-operator/templates/deployment.yaml +++ b/deploy/helm/grafana-operator/templates/deployment.yaml @@ -55,7 +55,7 @@ spec: {{- if and .Values.watchLabelSelectors (eq .Values.watchLabelSelectors "") }} value: "" {{ else }} - value: {{quote .Values.watchLabelSelectors | toJson }} + value: {{quote .Values.watchLabelSelectors }} {{- end }} {{- with .Values.env }} {{- toYaml . | nindent 12 }} diff --git a/deploy/helm/grafana-operator/values.yaml b/deploy/helm/grafana-operator/values.yaml index 34a6ef445..d0c704c1a 100644 --- a/deploy/helm/grafana-operator/values.yaml +++ b/deploy/helm/grafana-operator/values.yaml @@ -16,10 +16,11 @@ watchNamespaces: "" watchNamespaceSelector: "" # -- Sets the `WATCH_LABEL_SELECTORS` environment variable, -# if defines which CRs are watched according to thei labels. -# By default, the operator watches all CRs. To make it watch only a subset of CRs, set the value to a map of key/value -watchLabelSelectors: {} -# labelKey: labelValue +# it defines which CRs are watched according to their labels. +# By default, the operator watches all CRs. To make it watch only a subset of CRs, define the variable as a *stringified label selector*. +# See also: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +watchLabelSelectors: "" +# watchLabelSelectors: "partition in (customerA, customerB),environment!=qa" # -- Determines if the target cluster is OpenShift. Additional rbac permissions for routes will be added on OpenShift isOpenShift: false diff --git a/main.go b/main.go index e18328cc6..1caaffc27 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,8 @@ package main import ( "context" - "encoding/json" "flag" + "fmt" "os" "os/signal" "strings" @@ -49,7 +49,6 @@ import ( "github.com/grafana/grafana-operator/v5/controllers/autodetect" "github.com/grafana/grafana-operator/v5/embeds" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/selection" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -68,7 +67,7 @@ const ( // If empty or undefined, the operator will run in cluster scope. watchNamespaceEnvSelector = "WATCH_NAMESPACE_SELECTOR" // watchLabelSelectorsEnvVar is the constant for env variable WATCH_LABEL_SELECTORS which specifies the resources to watch according to their labels. - // eg: '{"labelKey": "labelValue"}' + // eg: 'partition in (customerA, customerB),environment!=qa' // If empty of undefined, the operator will watch all CRs. watchLabelSelectorsEnvVar = "WATCH_LABEL_SELECTORS" ) @@ -127,6 +126,18 @@ func main() { PprofBindAddress: pprofAddr, } + var labelSelectors labels.Selector + var err error + if watchLabelSelectors != "" { + labelSelectors, err = labels.Parse(watchLabelSelectors) + if err != nil { + setupLog.Error(err, fmt.Sprintf("unable to parse %s", watchLabelSelectorsEnvVar)) + os.Exit(1) //nolint + } + } else { + labelSelectors = labels.Everything() // Match any labels + } + getNamespaceConfig := func(namespaces string) map[string]cache.Config { defaultNamespaces := map[string]cache.Config{} for _, v := range strings.Split(namespaces, ",") { @@ -135,7 +146,7 @@ func main() { // this is the default behavior of the operator on v5, if you require finer grained control over this // please file an issue in the grafana-operator/grafana-operator GH project defaultNamespaces[v] = cache.Config{ - LabelSelector: labels.Everything(), // Match any labels + LabelSelector: labelSelectors, FieldSelector: fields.Everything(), // Match any fields Transform: nil, UnsafeDisableDeepCopy: nil, @@ -163,7 +174,7 @@ func main() { // this is the default behavior of the operator on v5, if you require finer grained control over this // please file an issue in the grafana-operator/grafana-operator GH project defaultNamespaces[v.Name] = cache.Config{ - LabelSelector: labels.Everything(), // Match any labels + LabelSelector: labelSelectors, FieldSelector: fields.Everything(), // Match any fields Transform: nil, UnsafeDisableDeepCopy: nil, @@ -187,26 +198,10 @@ func main() { case watchNamespace == "" && watchNamespaceSelector == "": // cluster scoped + controllerOptions.Cache.DefaultLabelSelector = labelSelectors setupLog.Info("operator running in cluster scoped mode") } - if watchLabelSelectors != "" { - labelSelectors := map[string]string{} - err := json.Unmarshal([]byte(watchLabelSelectors), &labelSelectors) - if err != nil { - setupLog.Error(err, "unable to Unmarshal labelSelectors") - os.Exit(1) //nolint - } - for k, v := range labelSelectors { - req, err := labels.NewRequirement(k, selection.Equals, []string{v}, nil) - if err != nil { - setupLog.Error(err, "unable to create labels selector") - os.Exit(1) //nolint - } - controllerOptions.Cache.DefaultLabelSelector.Add(*req) - } - } - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE) defer stop()