Add namespace label selector support for reflection#620
Add namespace label selector support for reflection#620davidswimbird wants to merge 6 commits intoemberstack:mainfrom
Conversation
Add two new annotations that allow selecting target namespaces by Kubernetes label selectors instead of only name regex patterns: - reflection-allowed-namespaces-selector - reflection-auto-namespaces-selector When both a name-pattern and a label selector annotation are set, a namespace matches if it satisfies either condition (OR logic). The label selector supports standard Kubernetes syntax: equality (=, ==, !=), existence, and set-based (in, notin) expressions. Closes emberstack#409
a75fa1c to
a704f21
Compare
|
@davidswimbird while I review this, please update the documentation as well (readme) |
There was a problem hiding this comment.
Pull request overview
Adds namespace label selector support to the reflector’s “allowed namespaces” and “auto namespaces” logic, enabling selection of target namespaces via standard Kubernetes label selector syntax in addition to existing name/regex matching.
Changes:
- Introduces two new reflection annotations for label selectors (allowed/auto namespaces selectors) and wires them into
MirroringProperties. - Implements label-selector matching (with OR logic vs name-pattern matching) and updates mirroring flows to use
V1Namespacewhen available. - Adds unit tests and test helpers for selector behavior; exposes internals to the test project.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ES.Kubernetes.Reflector.Tests/LabelSelectorMatchTests.cs | Adds unit tests for label selector parsing/matching and OR logic with name patterns. |
| tests/ES.Kubernetes.Reflector.Tests/Additions/ReflectorAnnotationsBuilder.cs | Adds builder helpers for the new selector annotations in tests. |
| src/ES.Kubernetes.Reflector/Mirroring/Core/ResourceMirror.cs | Caches namespaces for selector evaluation and uses V1Namespace overloads for matching. |
| src/ES.Kubernetes.Reflector/Mirroring/Core/MirroringPropertiesExtensions.cs | Parses new annotations, adds selector-aware matching, and implements label selector evaluation. |
| src/ES.Kubernetes.Reflector/Mirroring/Core/MirroringProperties.cs | Adds properties for allowed/auto namespace label selectors. |
| src/ES.Kubernetes.Reflector/Mirroring/Core/Annotations.cs | Adds constants for the new selector annotations. |
| src/ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj | Adds InternalsVisibleTo to allow unit tests to access LabelSelectorMatch. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| internal static bool LabelSelectorMatch(string selector, V1Namespace ns) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(selector)) return true; | ||
|
|
||
| var labels = ns.Metadata?.Labels ?? new Dictionary<string, string>(); | ||
| var requirements = SplitRequirements(selector); | ||
|
|
||
| foreach (var raw in requirements) | ||
| { | ||
| var requirement = raw.Trim(); | ||
| if (string.IsNullOrEmpty(requirement)) continue; |
There was a problem hiding this comment.
LabelSelectorMatch currently fails open for some invalid/degenerate selectors. For example, selectors like "," (or "!" / "!=value") produce no valid requirements (or an empty key), and the loop skips them and returns true, which can unintentionally allow reflection to all namespaces. Consider validating that at least one non-empty requirement is parsed, and that each requirement has a non-empty key; if parsing fails, return false (fail closed) or surface an error so misconfigurations don’t widen access.
|
|
||
| //Cache the namespace for label selector lookups | ||
| _namespaceCache.AddOrUpdate(ns.Name(), ns, (_, _) => ns); | ||
|
|
There was a problem hiding this comment.
Namespace label selectors are evaluated against _namespaceCache, but this handler only caches namespaces on WatchEventType.Added. If a namespace’s labels change (Modified) or a namespace is deleted, selector evaluation can become stale and auto-reflections won’t be created/removed when labels are added/removed. Consider also handling Modified to update the cache and reconcile reflections for that namespace, and Deleted to evict the cache entry (and reconcile/removal as needed).
| [Fact] | ||
| public void EmptySelector_MatchesAll() | ||
| { | ||
| var ns = CreateNamespace("test"); | ||
| Assert.True(MirroringPropertiesExtensions.LabelSelectorMatch("", ns)); | ||
| Assert.True(MirroringPropertiesExtensions.LabelSelectorMatch(" ", ns)); | ||
| } |
There was a problem hiding this comment.
Current tests cover the happy-path selector grammar, but there’s no coverage for invalid/degenerate selectors that should not broaden access (e.g. ",", "!", "!=value", unbalanced parentheses). Adding a few negative tests here would help ensure the selector parser fails closed and prevents accidental reflection to all namespaces on malformed input.
|
|
||
| /// <summary> | ||
| /// Matches a Kubernetes label selector string against namespace labels. | ||
| /// Supports equality-based (=, ==, !=) and existence-based (key, !key) selectors. |
There was a problem hiding this comment.
The XML doc for LabelSelectorMatch says it supports equality- and existence-based selectors, but the implementation also supports set-based in/notin expressions. Consider updating the comment to reflect the full supported grammar so callers don’t miss this capability.
| /// Supports equality-based (=, ==, !=) and existence-based (key, !key) selectors. | |
| /// Supports equality-based (=, ==, !=), existence-based (key, !key), and set-based (in, notin) selectors. |
Document the new reflection-allowed-namespaces-selector and reflection-auto-namespaces-selector annotations from emberstack#620.
Invalid or degenerate selectors (e.g. ",", "!", "!=value") previously produced no valid requirements or empty keys, causing LabelSelectorMatch to return true and unintentionally allow reflection to all namespaces. Now returns false when no requirements are parsed or a key is empty.
The namespace cache was only populated on Added events, so label changes (Modified) left stale entries and deleted namespaces were never evicted. - Handle Modified: update cache and reconcile auto-reflections (create if now matching, delete if no longer matching) - Handle Deleted: evict cache entry and clean up auto-reflection tracking
Cover unbalanced parentheses, empty set-based expressions, and bare operators to document that the parser fails closed on all of these.
Add two new annotations that allow selecting target namespaces by
Kubernetes label selectors instead of only name regex patterns:
When both a name-pattern and a label selector annotation are set, a
namespace matches if it satisfies either condition (OR logic). The label
selector supports standard Kubernetes syntax: equality (=, ==, !=),
existence, and set-based (in, notin) expressions.
This is useful when you want to reflect a secret to a subset of non-deterministic namespaces, that can't be represented with a regex.
For my use case, it makes the manual overhead of managing reflector a lot smaller, as I can automate the label configuration for my namespaces ahead of time, to know that they will be receiving the secrets when the namespace is created with a specified label.
This is based on the suggestion in: Discussion #409