From c81e71eee8796f46e429c9a958c8f8ac52df1dce Mon Sep 17 00:00:00 2001 From: dbxbbm Date: Fri, 3 Oct 2025 06:40:53 +0200 Subject: [PATCH 1/2] feat: add flag namespace-label-selector to filter namespaces --- cmd/manager/main.go | 20 ++ docs/namespace-filtering.md | 119 ++++++++++++ pkg/controller/agent/controller.go | 4 +- pkg/controller/apmserver/controller.go | 4 +- pkg/controller/association/reconciler.go | 4 +- .../autoscaling/elasticsearch/controller.go | 4 +- pkg/controller/beat/controller.go | 4 +- pkg/controller/common/operator/flags.go | 1 + .../common/operator/namespace_filtering.go | 52 +++++ .../operator/namespace_filtering_test.go | 178 ++++++++++++++++++ pkg/controller/common/operator/parameters.go | 3 + pkg/controller/common/unmanaged.go | 28 +++ .../elasticsearch/elasticsearch_controller.go | 4 +- .../enterprisesearch_controller.go | 4 +- pkg/controller/kibana/controller.go | 4 +- pkg/controller/license/license_controller.go | 6 + .../logstash/logstash_controller.go | 4 +- pkg/controller/maps/controller.go | 4 +- pkg/controller/remotecluster/controller.go | 5 +- .../stackconfigpolicy/controller.go | 6 +- 20 files changed, 433 insertions(+), 25 deletions(-) create mode 100644 docs/namespace-filtering.md create mode 100644 pkg/controller/common/operator/namespace_filtering.go create mode 100644 pkg/controller/common/operator/namespace_filtering_test.go diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 20e446da730..9aa7bd6b214 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -23,6 +23,7 @@ import ( "go.uber.org/automaxprocs/maxprocs" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" @@ -309,6 +310,11 @@ func Command() *cobra.Command { nil, "Comma-separated list of namespaces in which this operator should manage resources (defaults to all namespaces)", ) + cmd.Flags().String( + operator.NamespaceLabelSelectorFlag, + "", + "Label selector to filter namespaces that this operator should manage (in addition to namespace list). Format: key1=value1,key2=value2 or key1!=value1,key2!=value2. Empty means all namespaces.", + ) cmd.Flags().String( operator.OperatorNamespaceFlag, "", @@ -697,6 +703,19 @@ func startOperator(ctx context.Context) error { return err } + // Parse namespace label selector if provided + var namespaceLabelSelector *metav1.LabelSelector + namespaceLabelSelectorStr := viper.GetString(operator.NamespaceLabelSelectorFlag) + if namespaceLabelSelectorStr != "" { + selector, err := metav1.ParseToLabelSelector(namespaceLabelSelectorStr) + if err != nil { + log.Error(err, "Failed to parse namespace label selector", "selector", namespaceLabelSelectorStr) + return err + } + namespaceLabelSelector = selector + log.Info("Namespace label selector configured", "selector", namespaceLabelSelector) + } + params := operator.Parameters{ Dialer: dialer, ElasticsearchObservationInterval: viper.GetDuration(operator.ElasticsearchObservationIntervalFlag), @@ -718,6 +737,7 @@ func startOperator(ctx context.Context) error { SetDefaultSecurityContext: setDefaultSecurityContext, ValidateStorageClass: viper.GetBool(operator.ValidateStorageClassFlag), Tracer: tracer, + NamespaceLabelSelector: namespaceLabelSelector, } if viper.GetBool(operator.EnableWebhookFlag) { diff --git a/docs/namespace-filtering.md b/docs/namespace-filtering.md new file mode 100644 index 00000000000..d0077ba4b02 --- /dev/null +++ b/docs/namespace-filtering.md @@ -0,0 +1,119 @@ +# Namespace Filtering met Label Selectors + +Deze ECK operator ondersteunt namespace filtering op basis van label selectors, waardoor je meerdere ECK operators kunt draaien die verschillende sets van namespaces beheren. + +## Configuratie + +### Operator 1 - Beheert alleen namespaces met specifieke labels + +```bash +# Operator die alleen namespaces beheert met label "managed-by=eck-operator-1" +./manager \ + --namespace-label-selector="managed-by=eck-operator-1" \ + --operator-namespace=eck-operator-1 + +# Of met Helm: +helm install eck-operator-1 elastic/eck-operator \ + --namespace eck-operator-1 \ + --create-namespace \ + --set="config.namespaceLabelSelector=managed-by=eck-operator-1" +``` + +### Operator 2 - Beheert alle andere namespaces (exclusief) + +```bash +# Operator die namespaces beheert die NIET gelabeld zijn met "managed-by=eck-operator-1" +./manager \ + --namespace-label-selector="managed-by!=eck-operator-1" \ + --operator-namespace=eck-operator-2 + +# Of met matchExpressions voor meer complexe selectors: +./manager \ + --namespace-label-selector='{"matchExpressions":[{"key":"managed-by","operator":"NotIn","values":["eck-operator-1"]}]}' \ + --operator-namespace=eck-operator-2 +``` + +## Voorbeelden van Label Selectors + +### Eenvoudige equality selectors +```bash +# Alleen namespaces met env=production +--namespace-label-selector="env=production" + +# Alleen namespaces met env=production EN team=platform +--namespace-label-selector="env=production,team=platform" +``` + +### Complex selectors met matchExpressions +```bash +# Namespaces waar env NIET development of staging is +--namespace-label-selector='{"matchExpressions":[{"key":"env","operator":"NotIn","values":["development","staging"]}]}' + +# Namespaces die het label "managed-by" hebben (ongeacht de waarde) +--namespace-label-selector='{"matchExpressions":[{"key":"managed-by","operator":"Exists"}]}' + +# Namespaces die het label "ignore" NIET hebben +--namespace-label-selector='{"matchExpressions":[{"key":"ignore","operator":"DoesNotExist"}]}' +``` + +## Namespace Labeling + +Label je namespaces om te bepalen welke operator ze beheert: + +```yaml +# Voor operator 1 +apiVersion: v1 +kind: Namespace +metadata: + name: production-elasticsearch + labels: + managed-by: eck-operator-1 + env: production +--- +# Voor operator 2 (alle andere) +apiVersion: v1 +kind: Namespace +metadata: + name: development-elasticsearch + labels: + env: development + # Geen managed-by label, dus wordt beheerd door operator 2 +``` + +## Use Cases + +### 1. Multi-tenant setup +```bash +# Operator voor tenant A +--namespace-label-selector="tenant=team-a" + +# Operator voor tenant B +--namespace-label-selector="tenant=team-b" + +# Global operator voor shared resources +--namespace-label-selector="shared=true" +``` + +### 2. Environment separation +```bash +# Production operator +--namespace-label-selector="env=production" + +# Non-production operator +--namespace-label-selector="env!=production" +``` + +### 3. Regional separation +```bash +# EU operator +--namespace-label-selector="region=eu" + +# US operator +--namespace-label-selector="region=us" +``` + +## Backwards Compatibility + +Als je geen `--namespace-label-selector` opgeeft, gedraagt de operator zich zoals voorheen en beheert alle namespaces (behalve als je `--namespaces` gebruikt). + +De bestaande `--namespaces` flag blijft werken en wordt gecombineerd met de label selector. Een namespace moet aan beide criteria voldoen om beheerd te worden. \ No newline at end of file diff --git a/pkg/controller/agent/controller.go b/pkg/controller/agent/controller.go index 0416d947d7c..062eadcc6bb 100644 --- a/pkg/controller/agent/controller.go +++ b/pkg/controller/agent/controller.go @@ -155,8 +155,8 @@ func (r *ReconcileAgent) Reconcile(ctx context.Context, request reconcile.Reques return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, agent) { - logconf.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation") + if common.IsUnmanagedOrFiltered(ctx, r.Client, agent, r.Parameters) { + logconf.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation") return reconcile.Result{}, nil } diff --git a/pkg/controller/apmserver/controller.go b/pkg/controller/apmserver/controller.go index d5e19a6d6fc..a8aea71bbdf 100644 --- a/pkg/controller/apmserver/controller.go +++ b/pkg/controller/apmserver/controller.go @@ -191,8 +191,8 @@ func (r *ReconcileApmServer) Reconcile(ctx context.Context, request reconcile.Re return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &as) { - log.Info("Object currently not managed by this controller. Skipping reconciliation", "namespace", as.Namespace, "as_name", as.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &as, r.Parameters) { + log.Info("Object currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", as.Namespace, "as_name", as.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/association/reconciler.go b/pkg/controller/association/reconciler.go index eafbc162d4d..0169eebac7f 100644 --- a/pkg/controller/association/reconciler.go +++ b/pkg/controller/association/reconciler.go @@ -166,8 +166,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( associatedKey := k8s.ExtractNamespacedName(associated) - if common.IsUnmanaged(ctx, associated) { - log.Info("Object is currently not managed by this controller. Skipping reconciliation") + if common.IsUnmanagedOrFiltered(ctx, r.Client, associated, r.Parameters) { + log.Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation") return reconcile.Result{}, nil } diff --git a/pkg/controller/autoscaling/elasticsearch/controller.go b/pkg/controller/autoscaling/elasticsearch/controller.go index 039050692cf..7276485a3da 100644 --- a/pkg/controller/autoscaling/elasticsearch/controller.go +++ b/pkg/controller/autoscaling/elasticsearch/controller.go @@ -127,8 +127,8 @@ func (r *ReconcileElasticsearchAutoscaler) Reconcile(ctx context.Context, reques return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &esa) { - msg := "Object is currently not managed by this controller. Skipping reconciliation" + if common.IsUnmanagedOrFiltered(ctx, r.Client, &esa, r.Parameters) { + msg := "Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation" log.Info(msg, "namespace", request.Namespace, "esa_name", request.Name) return r.reportAsInactive(ctx, log, esa, msg) } diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index cbedb103d7d..e3bf6965461 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -141,8 +141,8 @@ func (r *ReconcileBeat) Reconcile(ctx context.Context, request reconcile.Request return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &beat) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", beat.Namespace, "beat_name", beat.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &beat, r.Parameters) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", beat.Namespace, "beat_name", beat.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/common/operator/flags.go b/pkg/controller/common/operator/flags.go index 2bb311d10de..413cb904099 100644 --- a/pkg/controller/common/operator/flags.go +++ b/pkg/controller/common/operator/flags.go @@ -37,6 +37,7 @@ const ( MetricsSecureFlag = "metrics-secure" MetricsCertDirFlag = "metrics-cert-dir" NamespacesFlag = "namespaces" + NamespaceLabelSelectorFlag = "namespace-label-selector" OperatorNamespaceFlag = "operator-namespace" SetDefaultSecurityContextFlag = "set-default-security-context" TelemetryIntervalFlag = "telemetry-interval" diff --git a/pkg/controller/common/operator/namespace_filtering.go b/pkg/controller/common/operator/namespace_filtering.go new file mode 100644 index 00000000000..cd76bac008f --- /dev/null +++ b/pkg/controller/common/operator/namespace_filtering.go @@ -0,0 +1,52 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package operator + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + + ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log" +) + +// ShouldManageNamespace determines if the operator should manage resources in the given namespace +// based on the configured namespace label selector. +func (p Parameters) ShouldManageNamespace(ctx context.Context, c client.Client, namespace string) (bool, error) { + // If no namespace label selector is configured, manage all namespaces (backwards compatibility) + if p.NamespaceLabelSelector == nil { + return true, nil + } + + log := ulog.FromContext(ctx) + + // Get the namespace object + var ns corev1.Namespace + if err := c.Get(ctx, client.ObjectKey{Name: namespace}, &ns); err != nil { + log.Error(err, "Failed to get namespace", "namespace", namespace) + return false, err + } + + // Convert LabelSelector to labels.Selector + selector, err := metav1.LabelSelectorAsSelector(p.NamespaceLabelSelector) + if err != nil { + log.Error(err, "Failed to convert namespace label selector", "selector", p.NamespaceLabelSelector) + return false, err + } + + // Check if namespace labels match the selector + matches := selector.Matches(labels.Set(ns.Labels)) + + log.V(1).Info("Namespace filtering check", + "namespace", namespace, + "labels", ns.Labels, + "selector", p.NamespaceLabelSelector, + "matches", matches) + + return matches, nil +} \ No newline at end of file diff --git a/pkg/controller/common/operator/namespace_filtering_test.go b/pkg/controller/common/operator/namespace_filtering_test.go new file mode 100644 index 00000000000..c172e65fbc2 --- /dev/null +++ b/pkg/controller/common/operator/namespace_filtering_test.go @@ -0,0 +1,178 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package operator + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s" +) + +func TestShouldManageNamespace(t *testing.T) { + // Test namespace with labels + testNamespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + Labels: map[string]string{ + "env": "production", + "team": "platform", + }, + }, + } + + // Another test namespace with different labels + devNamespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dev-namespace", + Labels: map[string]string{ + "env": "development", + "team": "platform", + }, + }, + } + + // Namespace without relevant labels + otherNamespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other-namespace", + Labels: map[string]string{ + "purpose": "testing", + }, + }, + } + + tests := []struct { + name string + namespaceLabelSelector *metav1.LabelSelector + namespace string + expectedResult bool + expectedError bool + }{ + { + name: "No label selector - should manage all namespaces", + namespaceLabelSelector: nil, + namespace: "test-namespace", + expectedResult: true, + expectedError: false, + }, + { + name: "Matching label selector - should manage namespace", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + }, + }, + namespace: "test-namespace", + expectedResult: true, + expectedError: false, + }, + { + name: "Non-matching label selector - should not manage namespace", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + }, + }, + namespace: "dev-namespace", + expectedResult: false, + expectedError: false, + }, + { + name: "Multiple label selectors - should match when all match", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + "team": "platform", + }, + }, + namespace: "test-namespace", + expectedResult: true, + expectedError: false, + }, + { + name: "Multiple label selectors - should not match when one doesn't match", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + "team": "different", + }, + }, + namespace: "test-namespace", + expectedResult: false, + expectedError: false, + }, + { + name: "Complex selector with matchExpressions", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "env", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"development", "staging"}, + }, + }, + }, + namespace: "test-namespace", + expectedResult: true, + expectedError: false, + }, + { + name: "Label selector with missing labels", + namespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + }, + }, + namespace: "other-namespace", + expectedResult: false, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create fake client with test namespaces + fakeClient := k8s.NewFakeClient(testNamespace, devNamespace, otherNamespace) + + // Create parameters with the test label selector + params := Parameters{ + NamespaceLabelSelector: tt.namespaceLabelSelector, + } + + // Test the function + result, err := params.ShouldManageNamespace(context.Background(), fakeClient, tt.namespace) + + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedResult, result) + } + }) + } +} + +func TestShouldManageNamespace_NonExistentNamespace(t *testing.T) { + fakeClient := k8s.NewFakeClient() + + params := Parameters{ + NamespaceLabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "production", + }, + }, + } + + result, err := params.ShouldManageNamespace(context.Background(), fakeClient, "non-existent") + + assert.False(t, result) + assert.Error(t, err) +} \ No newline at end of file diff --git a/pkg/controller/common/operator/parameters.go b/pkg/controller/common/operator/parameters.go index 6ee12422a86..206f3c35eca 100644 --- a/pkg/controller/common/operator/parameters.go +++ b/pkg/controller/common/operator/parameters.go @@ -9,6 +9,7 @@ import ( "go.elastic.co/apm/v2" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/elastic/cloud-on-k8s/v3/pkg/about" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/certificates" @@ -49,4 +50,6 @@ type Parameters struct { ValidateStorageClass bool // Tracer is a shared APM tracer instance or nil Tracer *apm.Tracer + // NamespaceLabelSelector optionally filters namespaces by labels + NamespaceLabelSelector *metav1.LabelSelector } diff --git a/pkg/controller/common/unmanaged.go b/pkg/controller/common/unmanaged.go index 17df7a91703..f5528c287cb 100644 --- a/pkg/controller/common/unmanaged.go +++ b/pkg/controller/common/unmanaged.go @@ -9,7 +9,9 @@ import ( "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/operator" ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log" ) @@ -32,3 +34,29 @@ func IsUnmanaged(ctx context.Context, object metav1.Object) bool { } return exists && paused == "true" } + +// IsUnmanagedOrFiltered checks if a given resource is currently unmanaged or if its namespace +// should not be managed based on the operator's namespace label selector configuration. +func IsUnmanagedOrFiltered(ctx context.Context, c client.Client, object metav1.Object, params operator.Parameters) bool { + log := ulog.FromContext(ctx) + + // First check if the resource is explicitly unmanaged + if IsUnmanaged(ctx, object) { + return true + } + + // Then check namespace filtering + shouldManage, err := params.ShouldManageNamespace(ctx, c, object.GetNamespace()) + if err != nil { + log.Error(err, "Failed to check namespace management status", "namespace", object.GetNamespace(), "name", object.GetName()) + // In case of error, default to not managing to be safe + return true + } + + if !shouldManage { + log.V(1).Info("Namespace is excluded by namespace label selector", "namespace", object.GetNamespace(), "name", object.GetName()) + return true + } + + return false +} diff --git a/pkg/controller/elasticsearch/elasticsearch_controller.go b/pkg/controller/elasticsearch/elasticsearch_controller.go index 2cb0d1e8130..410093428f8 100644 --- a/pkg/controller/elasticsearch/elasticsearch_controller.go +++ b/pkg/controller/elasticsearch/elasticsearch_controller.go @@ -172,8 +172,8 @@ func (r *ReconcileElasticsearch) Reconcile(ctx context.Context, request reconcil return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &es) { - log.Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", es.Namespace, "es_name", es.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &es, r.Parameters) { + log.Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", es.Namespace, "es_name", es.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/enterprisesearch/enterprisesearch_controller.go b/pkg/controller/enterprisesearch/enterprisesearch_controller.go index f93e679c61e..0cd20bea7f2 100644 --- a/pkg/controller/enterprisesearch/enterprisesearch_controller.go +++ b/pkg/controller/enterprisesearch/enterprisesearch_controller.go @@ -157,8 +157,8 @@ func (r *ReconcileEnterpriseSearch) Reconcile(ctx context.Context, request recon return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &ent) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", ent.Namespace, "ent_name", ent.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &ent, r.Parameters) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", ent.Namespace, "ent_name", ent.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/kibana/controller.go b/pkg/controller/kibana/controller.go index f4ad4e63873..70cc81e5ed1 100644 --- a/pkg/controller/kibana/controller.go +++ b/pkg/controller/kibana/controller.go @@ -149,8 +149,8 @@ func (r *ReconcileKibana) Reconcile(ctx context.Context, request reconcile.Reque return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &kb) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", kb.Namespace, "kibana_name", kb.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &kb, r.params) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", kb.Namespace, "kibana_name", kb.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/license/license_controller.go b/pkg/controller/license/license_controller.go index 5ec4f34a74d..53e9eff1802 100644 --- a/pkg/controller/license/license_controller.go +++ b/pkg/controller/license/license_controller.go @@ -260,6 +260,12 @@ func (r *ReconcileLicenses) reconcileInternal(ctx context.Context, request recon return res.WithError(err) } + log := ulog.FromContext(ctx) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &cluster, r.Parameters) { + log.Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", cluster.Namespace, "es_name", cluster.Name) + return res + } + if !cluster.DeletionTimestamp.IsZero() { // cluster is being deleted nothing to do return res diff --git a/pkg/controller/logstash/logstash_controller.go b/pkg/controller/logstash/logstash_controller.go index 3f907369aec..a0455f34d2e 100644 --- a/pkg/controller/logstash/logstash_controller.go +++ b/pkg/controller/logstash/logstash_controller.go @@ -143,8 +143,8 @@ func (r *ReconcileLogstash) Reconcile(ctx context.Context, request reconcile.Req return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, logstash) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation") + if common.IsUnmanagedOrFiltered(ctx, r.Client, logstash, r.Parameters) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", logstash.Namespace, "logstash_name", logstash.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/maps/controller.go b/pkg/controller/maps/controller.go index 97bc0f3cd4e..2deefad4e3f 100644 --- a/pkg/controller/maps/controller.go +++ b/pkg/controller/maps/controller.go @@ -162,8 +162,8 @@ func (r *ReconcileMapsServer) Reconcile(ctx context.Context, request reconcile.R return reconcile.Result{}, tracing.CaptureError(ctx, err) } - if common.IsUnmanaged(ctx, &ems) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", ems.Namespace, "maps_name", ems.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &ems, r.Parameters) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", ems.Namespace, "maps_name", ems.Name) return reconcile.Result{}, nil } diff --git a/pkg/controller/remotecluster/controller.go b/pkg/controller/remotecluster/controller.go index 4cb73bab3d9..95783f7eb7a 100644 --- a/pkg/controller/remotecluster/controller.go +++ b/pkg/controller/remotecluster/controller.go @@ -104,10 +104,11 @@ func (r *ReconcileRemoteClusters) Reconcile(ctx context.Context, request reconci return reconcile.Result{}, err } - if common.IsUnmanaged(ctx, &es) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", es.Namespace, "es_name", es.Name) + if common.IsUnmanagedOrFiltered(ctx, r.Client, &es, r.Parameters) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation", "namespace", es.Namespace, "es_name", es.Name) return reconcile.Result{}, nil } + return doReconcile(ctx, r, &es) } diff --git a/pkg/controller/stackconfigpolicy/controller.go b/pkg/controller/stackconfigpolicy/controller.go index b2cefc8ea52..bb75c4734f9 100644 --- a/pkg/controller/stackconfigpolicy/controller.go +++ b/pkg/controller/stackconfigpolicy/controller.go @@ -173,9 +173,9 @@ func (r *ReconcileStackConfigPolicy) Reconcile(ctx context.Context, request reco return reconcile.Result{}, tracing.CaptureError(ctx, err) } - // skip unmanaged resources - if common.IsUnmanaged(ctx, &policy) { - ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation") + // skip unmanaged resources and namespace filtering + if common.IsUnmanagedOrFiltered(ctx, r.Client, &policy, r.params) { + ulog.FromContext(ctx).Info("Object is currently not managed by this controller or namespace is filtered. Skipping reconciliation") return reconcile.Result{}, nil } From fde5742e7bc9cfee9ef96f893108e55d7b1a5233 Mon Sep 17 00:00:00 2001 From: dbxbbm Date: Wed, 5 Nov 2025 12:53:10 +0100 Subject: [PATCH 2/2] fix: moved docs --- docs/design/0005-namespace-filtering.md | 41 ++++++++ docs/namespace-filtering.md | 119 ------------------------ 2 files changed, 41 insertions(+), 119 deletions(-) create mode 100644 docs/design/0005-namespace-filtering.md delete mode 100644 docs/namespace-filtering.md diff --git a/docs/design/0005-namespace-filtering.md b/docs/design/0005-namespace-filtering.md new file mode 100644 index 00000000000..79daf3a5ad6 --- /dev/null +++ b/docs/design/0005-namespace-filtering.md @@ -0,0 +1,41 @@ +# Namespace Filtering met Label Selectors + +Deze ECK operator ondersteunt namespace filtering op basis van label selectors, waardoor je meerdere ECK operators kunt draaien die verschillende sets van namespaces beheren. + +## Inleiding + +Namespace filtering is een krachtige functie die je in staat stelt om de reikwijdte van je ECK operator te beperken tot specifieke namespaces binnen je Kubernetes cluster. Dit wordt bereikt door het toepassen van label selectors, die fungeren als een filtermechanisme om alleen die namespaces te selecteren die voldoen aan bepaalde criteria. + +## Hoe het Werkt + +De ECK operator maakt gebruik van Kubernetes label selectors om namespaces te filteren. Een label selector is een uitdrukking die bepaalt welke labels overeenkomen met de gespecificeerde criteria. Door deze selectors te gebruiken, kun je de ECK operator configureren om alleen die namespaces te beheren die de juiste labels hebben. + +Bijvoorbeeld, als je een label selector hebt gedefinieerd als `environment: production`, dan zal de ECK operator alleen van toepassing zijn op die namespaces die het label `environment` hebben met de waarde `production`. + +## Voordelen van Namespace Filtering + +- **Gerichte Toepassingen**: Je kunt de ECK operator richten op specifieke namespaces, wat handig is in omgevingen waar meerdere teams of projecten dezelfde Kubernetes cluster delen. +- **Resource Optimalisatie**: Door de reikwijdte van de operator te beperken, worden de resources efficiƫnter gebruikt en wordt de kans op conflicten tussen verschillende toepassingen verminderd. +- **Veiligheid en Isolatie**: Namespace filtering helpt bij het handhaven van veiligheid en isolatie tussen verschillende delen van je applicatie of tussen verschillende applicaties die op dezelfde cluster draaien. + +## Configuratie Voorbeeld + +Hier is een voorbeeld van hoe je namespace filtering kunt configureren met behulp van label selectors in je ECK operator manifest: + +```yaml +apiVersion: operator.elastic.co/v1 +kind: Elasticsearch +metadata: + name: mijn-elk-cluster + namespace: mijn-namespace + labels: + environment: production +spec: + ... +``` + +In dit voorbeeld zal de ECK operator alleen van toepassing zijn op de namespace `mijn-namespace` als deze het label `environment: production` heeft. + +## Conclusie + +Namespace filtering met label selectors biedt een flexibele en krachtige manier om de reikwijdte van je ECK operator te beheren. Door gebruik te maken van deze functie, kun je efficiƫnter werken met meerdere namespaces binnen je Kubernetes cluster en tegelijkertijd een hoge mate van isolatie en veiligheid handhaven. \ No newline at end of file diff --git a/docs/namespace-filtering.md b/docs/namespace-filtering.md deleted file mode 100644 index d0077ba4b02..00000000000 --- a/docs/namespace-filtering.md +++ /dev/null @@ -1,119 +0,0 @@ -# Namespace Filtering met Label Selectors - -Deze ECK operator ondersteunt namespace filtering op basis van label selectors, waardoor je meerdere ECK operators kunt draaien die verschillende sets van namespaces beheren. - -## Configuratie - -### Operator 1 - Beheert alleen namespaces met specifieke labels - -```bash -# Operator die alleen namespaces beheert met label "managed-by=eck-operator-1" -./manager \ - --namespace-label-selector="managed-by=eck-operator-1" \ - --operator-namespace=eck-operator-1 - -# Of met Helm: -helm install eck-operator-1 elastic/eck-operator \ - --namespace eck-operator-1 \ - --create-namespace \ - --set="config.namespaceLabelSelector=managed-by=eck-operator-1" -``` - -### Operator 2 - Beheert alle andere namespaces (exclusief) - -```bash -# Operator die namespaces beheert die NIET gelabeld zijn met "managed-by=eck-operator-1" -./manager \ - --namespace-label-selector="managed-by!=eck-operator-1" \ - --operator-namespace=eck-operator-2 - -# Of met matchExpressions voor meer complexe selectors: -./manager \ - --namespace-label-selector='{"matchExpressions":[{"key":"managed-by","operator":"NotIn","values":["eck-operator-1"]}]}' \ - --operator-namespace=eck-operator-2 -``` - -## Voorbeelden van Label Selectors - -### Eenvoudige equality selectors -```bash -# Alleen namespaces met env=production ---namespace-label-selector="env=production" - -# Alleen namespaces met env=production EN team=platform ---namespace-label-selector="env=production,team=platform" -``` - -### Complex selectors met matchExpressions -```bash -# Namespaces waar env NIET development of staging is ---namespace-label-selector='{"matchExpressions":[{"key":"env","operator":"NotIn","values":["development","staging"]}]}' - -# Namespaces die het label "managed-by" hebben (ongeacht de waarde) ---namespace-label-selector='{"matchExpressions":[{"key":"managed-by","operator":"Exists"}]}' - -# Namespaces die het label "ignore" NIET hebben ---namespace-label-selector='{"matchExpressions":[{"key":"ignore","operator":"DoesNotExist"}]}' -``` - -## Namespace Labeling - -Label je namespaces om te bepalen welke operator ze beheert: - -```yaml -# Voor operator 1 -apiVersion: v1 -kind: Namespace -metadata: - name: production-elasticsearch - labels: - managed-by: eck-operator-1 - env: production ---- -# Voor operator 2 (alle andere) -apiVersion: v1 -kind: Namespace -metadata: - name: development-elasticsearch - labels: - env: development - # Geen managed-by label, dus wordt beheerd door operator 2 -``` - -## Use Cases - -### 1. Multi-tenant setup -```bash -# Operator voor tenant A ---namespace-label-selector="tenant=team-a" - -# Operator voor tenant B ---namespace-label-selector="tenant=team-b" - -# Global operator voor shared resources ---namespace-label-selector="shared=true" -``` - -### 2. Environment separation -```bash -# Production operator ---namespace-label-selector="env=production" - -# Non-production operator ---namespace-label-selector="env!=production" -``` - -### 3. Regional separation -```bash -# EU operator ---namespace-label-selector="region=eu" - -# US operator ---namespace-label-selector="region=us" -``` - -## Backwards Compatibility - -Als je geen `--namespace-label-selector` opgeeft, gedraagt de operator zich zoals voorheen en beheert alle namespaces (behalve als je `--namespaces` gebruikt). - -De bestaande `--namespaces` flag blijft werken en wordt gecombineerd met de label selector. Een namespace moet aan beide criteria voldoen om beheerd te worden. \ No newline at end of file