Skip to content

Add namespace label selector support for reflection#620

Open
davidswimbird wants to merge 6 commits intoemberstack:mainfrom
davidswimbird:davidswimbird/discussion-409
Open

Add namespace label selector support for reflection#620
davidswimbird wants to merge 6 commits intoemberstack:mainfrom
davidswimbird:davidswimbird/discussion-409

Conversation

@davidswimbird
Copy link
Copy Markdown

@davidswimbird davidswimbird commented Mar 25, 2026

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.

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

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
@davidswimbird davidswimbird force-pushed the davidswimbird/discussion-409 branch from a75fa1c to a704f21 Compare March 25, 2026 10:42
@winromulus
Copy link
Copy Markdown
Contributor

@davidswimbird while I review this, please update the documentation as well (readme)

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 V1Namespace when 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.

Comment on lines +133 to +143
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;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 83d2d40.

Comment on lines +108 to +115

//Cache the namespace for label selector lookups
_namespaceCache.AddOrUpdate(ns.Name(), ns, (_, _) => ns);

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c192e07.

Comment on lines +18 to +24
[Fact]
public void EmptySelector_MatchesAll()
{
var ns = CreateNamespace("test");
Assert.True(MirroringPropertiesExtensions.LabelSelectorMatch("", ns));
Assert.True(MirroringPropertiesExtensions.LabelSelectorMatch(" ", ns));
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5baa341.


/// <summary>
/// Matches a Kubernetes label selector string against namespace labels.
/// Supports equality-based (=, ==, !=) and existence-based (key, !key) selectors.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
/// Supports equality-based (=, ==, !=) and existence-based (key, !key) selectors.
/// Supports equality-based (=, ==, !=), existence-based (key, !key), and set-based (in, notin) selectors.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e966569.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants