From fc32cc6d07e736eb78b91de1663751dd4cccd7e7 Mon Sep 17 00:00:00 2001 From: Anders Stigaard Date: Thu, 7 May 2026 12:59:35 +0200 Subject: [PATCH 1/3] :sparkles:added support for namespace reconcile exclusion --- api/config/v2alpha2/projectconfig_types.go | 12 +++++++++ .../controller/styra/system_controller.go | 21 +++++++++++++++ .../styra/system_controller_test.go | 26 +++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/api/config/v2alpha2/projectconfig_types.go b/api/config/v2alpha2/projectconfig_types.go index d5c77097..6537e214 100644 --- a/api/config/v2alpha2/projectconfig_types.go +++ b/api/config/v2alpha2/projectconfig_types.go @@ -31,6 +31,12 @@ type ProjectConfig struct { // running multiple controllers in the same cluster. ControllerClass string `json:"controllerClass"` + // NamespaceExclusionSelector defines criteria for excluding namespaces from reconciliation. + // If a resource is in a namespace that matches the criteria defined in NamespaceExclusionSelector, + // it will be excluded from reconciliation even if the resource matches the ControllerClass. + // If not set, the controller will reconcile resources in all namespaces. + NamespaceExclusionSelector *NamespaceExclusionSelector `json:"namespaceExclusionSelector,omitempty"` + // DeletionProtectionDefault sets the default to use with regards to deletion // protection if it is not set on the resource. DeletionProtectionDefault bool `json:"deletionProtectionDefault"` @@ -94,6 +100,12 @@ type OPAControlPlaneConfig struct { LibraryDatasourceChanged string `json:"libraryDatasourceChanged,omitempty"` } +// NamespaceExclusionSelector defines criteria for excluding namespaces. +type NamespaceExclusionSelector struct { + // MatchPatterns is a list of glob patterns to match namespace names against. + MatchPatterns []string `json:"matchPatterns,omitempty"` +} + // BundleObjectStorage defines the structure for object storage configuration used by bundles type BundleObjectStorage struct { S3 *S3ObjectStorage `json:"s3,omitempty" yaml:"s3,omitempty"` diff --git a/internal/controller/styra/system_controller.go b/internal/controller/styra/system_controller.go index 7a95d54c..14e32730 100644 --- a/internal/controller/styra/system_controller.go +++ b/internal/controller/styra/system_controller.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "net/url" + "path/filepath" "strings" "time" @@ -112,6 +113,12 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr log = log.WithValues("controlPlane", system.Labels["styra-controller/control-plane"]) log = log.WithValues("uniqueName", system.OCPUniqueName(r.Config.SystemPrefix, r.Config.SystemSuffix)) + if r.isNamespaceExcluded(&system) { + log.Info("Namespace is excluded from reconciliation. Skipping.") + r.deleteMetrics(req) + return ctrl.Result{}, nil + } + if !labels.ControllerClassMatches(&system, r.Config.ControllerClass) { log.Info("This is not a System we are managing. Skipping reconciliation.") r.deleteMetrics(req) @@ -152,6 +159,20 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return res, err } +func (r *SystemReconciler) isNamespaceExcluded(system *v1beta1.System) bool { + if r.Config.NamespaceExclusionSelector == nil { + return false + } + + for _, pattern := range r.Config.NamespaceExclusionSelector.MatchPatterns { + if matched, _ := filepath.Match(pattern, system.Namespace); matched { + return true + } + } + + return false +} + func (r *SystemReconciler) setSystemStatusError(System *v1beta1.System, err error) { System.Status.FailureMessage = err.Error() System.Status.Phase = v1beta1.SystemPhaseFailed diff --git a/internal/controller/styra/system_controller_test.go b/internal/controller/styra/system_controller_test.go index 5318cf45..228ed498 100644 --- a/internal/controller/styra/system_controller_test.go +++ b/internal/controller/styra/system_controller_test.go @@ -17,8 +17,11 @@ limitations under the License. package styra import ( + configv2alpha2 "github.com/bankdata/styra-controller/api/config/v2alpha2" + "github.com/bankdata/styra-controller/api/styra/v1beta1" ginkgo "github.com/onsi/ginkgo/v2" gomega "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // test the isURLValid method @@ -35,3 +38,26 @@ var _ = ginkgo.DescribeTable("isURLValid", ginkgo.Entry("invalid url", "google.com", false), ginkgo.Entry("invalid url", "google", false), ) + +// test the isNamespaceExcluded method +var _ = ginkgo.DescribeTable("isNamespaceExcluded", + func(namespaceExclusionSelector *configv2alpha2.NamespaceExclusionSelector, systemNamespace string, expected bool) { + reconciler := &SystemReconciler{ + Config: &configv2alpha2.ProjectConfig{ + NamespaceExclusionSelector: namespaceExclusionSelector, + }, + } + system := v1beta1.System{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: systemNamespace, + }, + } + gomega.Ω(reconciler.isNamespaceExcluded(&system)).To(gomega.Equal(expected)) + }, + ginkgo.Entry("no exclusion selector", nil, "namespace-dev", false), + ginkgo.Entry("empty exclusion selector", &configv2alpha2.NamespaceExclusionSelector{}, "namespace-dev", false), + ginkgo.Entry("no match multiple patterns", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"dev-*"}}, "namespace-dev", false), + ginkgo.Entry("match single pattern", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev"}}, "namespace-dev", true), + ginkgo.Entry("no match single pattern", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-staging", "*-prod"}}, "namespace-dev", false), + ginkgo.Entry("match multiple patterns", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev", "*-staging"}}, "namespace-dev", true), +) From 859d21f5bc52bfbb64768bc5f2c94e03106510d4 Mon Sep 17 00:00:00 2001 From: Anders Stigaard Date: Thu, 7 May 2026 13:03:37 +0200 Subject: [PATCH 2/3] :sparkles: config sample --- config/default/config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/default/config.yaml b/config/default/config.yaml index 6ea69364..fb49e9ff 100644 --- a/config/default/config.yaml +++ b/config/default/config.yaml @@ -1,6 +1,12 @@ apiVersion: config.bankdata.dk/v2alpha2 kind: ProjectConfig #controllerClass: + +#namespaceExclusionSelector: +# matchPatterns: +# - "*-dev" +# - "*-staging" + deletionProtectionDefault: false #disableCRDWebhooks: #gitCredentials: From 3a32335eee71e898d2a8a137be9d4ab76ecdd5fc Mon Sep 17 00:00:00 2001 From: Anders Stigaard Date: Thu, 7 May 2026 13:06:18 +0200 Subject: [PATCH 3/3] linting --- .../styra/system_controller_test.go | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/controller/styra/system_controller_test.go b/internal/controller/styra/system_controller_test.go index 228ed498..a897c413 100644 --- a/internal/controller/styra/system_controller_test.go +++ b/internal/controller/styra/system_controller_test.go @@ -54,10 +54,18 @@ var _ = ginkgo.DescribeTable("isNamespaceExcluded", } gomega.Ω(reconciler.isNamespaceExcluded(&system)).To(gomega.Equal(expected)) }, - ginkgo.Entry("no exclusion selector", nil, "namespace-dev", false), - ginkgo.Entry("empty exclusion selector", &configv2alpha2.NamespaceExclusionSelector{}, "namespace-dev", false), - ginkgo.Entry("no match multiple patterns", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"dev-*"}}, "namespace-dev", false), - ginkgo.Entry("match single pattern", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev"}}, "namespace-dev", true), - ginkgo.Entry("no match single pattern", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-staging", "*-prod"}}, "namespace-dev", false), - ginkgo.Entry("match multiple patterns", &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev", "*-staging"}}, "namespace-dev", true), + ginkgo.Entry("no exclusion selector", + nil, "namespace-dev", false), + ginkgo.Entry("empty exclusion selector", + &configv2alpha2.NamespaceExclusionSelector{}, "namespace-dev", false), + ginkgo.Entry("no match multiple patterns", + &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"dev-*"}}, "namespace-dev", false), + ginkgo.Entry("match single pattern", + &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev"}}, "namespace-dev", true), + ginkgo.Entry("no match single pattern", + &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-staging", "*-prod"}}, + "namespace-dev", false), + ginkgo.Entry("match multiple patterns", + &configv2alpha2.NamespaceExclusionSelector{MatchPatterns: []string{"*-dev", "*-staging"}}, + "namespace-dev", true), )