Skip to content

Commit

Permalink
feat: Allow to restrict the CRs watched according to their labels
Browse files Browse the repository at this point in the history
Signed-off-by: Wilfried Roset <[email protected]>
  • Loading branch information
wilfriedroset committed Jan 30, 2025
1 parent ff3fb07 commit 698a279
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 7 deletions.
1 change: 1 addition & 0 deletions deploy/helm/grafana-operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | 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. |
6 changes: 6 additions & 0 deletions deploy/helm/grafana-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
{{- end }}
{{- with .Values.env }}
{{- toYaml . | nindent 12 }}
{{- end }}
Expand Down
7 changes: 7 additions & 0 deletions deploy/helm/grafana-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ 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,
# 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

Expand Down
42 changes: 35 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"
"flag"
"fmt"
"log"
"log/slog"
"os"
Expand Down Expand Up @@ -71,6 +72,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: 'partition in (customerA, customerB),environment!=qa'
// If empty of undefined, the operator will watch all CRs.
watchLabelSelectorsEnvVar = "WATCH_LABEL_SELECTORS"
)

var (
Expand Down Expand Up @@ -123,6 +128,7 @@ func main() {

watchNamespace, _ := os.LookupEnv(watchNamespaceEnvVar)
watchNamespaceSelector, _ := os.LookupEnv(watchNamespaceEnvSelector)
watchLabelSelectors, _ := os.LookupEnv(watchLabelSelectorsEnvVar)

// Fetch k8s api credentials and detect platform
restConfig := ctrl.GetConfigOrDie()
Expand Down Expand Up @@ -151,22 +157,28 @@ func main() {
PprofBindAddress: pprofAddr,
}

labelSelectors, err := getLabelSelectors(watchLabelSelectors)
if err != nil {
setupLog.Error(err, fmt.Sprintf("unable to parse %s", watchLabelSelectorsEnvVar))
os.Exit(1) //nolint
}
switch {
case strings.Contains(watchNamespace, ","):
// multi namespace scoped
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfig(watchNamespace)
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfig(watchNamespace, labelSelectors)
setupLog.Info("operator running in namespace scoped mode for multiple namespaces", "namespaces", watchNamespace)
case watchNamespace != "":
// namespace scoped
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfig(watchNamespace)
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfig(watchNamespace, labelSelectors)
setupLog.Info("operator running in namespace scoped mode", "namespace", watchNamespace)
case strings.Contains(watchNamespaceSelector, ":"):
// namespace scoped
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfigSelector(restConfig, watchNamespaceSelector)
controllerOptions.Cache.DefaultNamespaces = getNamespaceConfigSelector(restConfig, watchNamespaceSelector, labelSelectors)
setupLog.Info("operator running in namespace scoped mode using namespace selector", "namespace", watchNamespace)

case watchNamespace == "" && watchNamespaceSelector == "":
// cluster scoped
controllerOptions.Cache.DefaultLabelSelector = labelSelectors
setupLog.Info("operator running in cluster scoped mode")
}

Expand Down Expand Up @@ -266,15 +278,15 @@ func main() {
setupLog.Info("SIGTERM request gotten, shutting down operator")
}

func getNamespaceConfig(namespaces string) map[string]cache.Config {
func getNamespaceConfig(namespaces string, labelSelectors labels.Selector) map[string]cache.Config {
defaultNamespaces := map[string]cache.Config{}
for _, v := range strings.Split(namespaces, ",") {
// Generate a mapping of namespaces to label/field selectors, set to Everything() to enable matching all
// instances in all namespaces from watchNamespace to be controlled by the operator
// 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,
Expand All @@ -283,7 +295,7 @@ func getNamespaceConfig(namespaces string) map[string]cache.Config {
return defaultNamespaces
}

func getNamespaceConfigSelector(restConfig *rest.Config, selector string) map[string]cache.Config {
func getNamespaceConfigSelector(restConfig *rest.Config, selector string, labelSelectors labels.Selector) map[string]cache.Config {
cl, err := client.New(restConfig, client.Options{})
if err != nil {
setupLog.Error(err, "Failed to get watch namespaces")
Expand All @@ -305,11 +317,27 @@ func getNamespaceConfigSelector(restConfig *rest.Config, selector string) map[st
// 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,
}
}
return defaultNamespaces
}

func getLabelSelectors(watchLabelSelectors string) (labels.Selector, error) {
var (
labelSelectors labels.Selector
err error
)
if watchLabelSelectors != "" {
labelSelectors, err = labels.Parse(watchLabelSelectors)
if err != nil {
return labelSelectors, fmt.Errorf("unable to parse %s: %w", watchLabelSelectorsEnvVar, err)
}
} else {
labelSelectors = labels.Everything() // Match any labels
}
return labelSelectors, nil
}

0 comments on commit 698a279

Please sign in to comment.