fix: default verify-source-freshness=true; honor opt-out for glob

H1: --verify-source-freshness used to default to false, so any source
update whose annotation was still in the informer cache (5-20s lag)
would resolve the wrong target list. cleanupOrphanedMirrors then ran
against the stale list and missed orphans (manifested in e2e as
'Orphaned mirror in kubemirror-e2e-app-1 not deleted within timeout'
after target-namespaces was changed). Defaulting to true fixes the
race; the trade-off is one extra API read per stale-cache reconcile.

M2: ResolveTargetNamespaces glob branch checked filter.IsAllowed but
not the opt-out map, so a namespace labeled allow-mirrors=false would
still receive a mirror through patterns like 'app-*'. The 'all' branch
already had the guard; the glob branch now does too. Direct namespace
listings still bypass opt-out by design (explicit author intent).
This commit is contained in:
2026-05-02 22:36:50 +01:00
parent 4277c8ac39
commit 99c0eccd53
3 changed files with 30 additions and 6 deletions
+8 -3
View File
@@ -222,14 +222,19 @@ func ResolveTargetNamespaces(
default:
// Check if it's a pattern or direct namespace name
if strings.Contains(pattern, "*") || strings.Contains(pattern, "?") {
// It's a glob pattern - match against all namespaces
// It's a glob pattern - match against all namespaces. Honor
// opt-out namespaces (allow-mirrors=false) the same way the
// "all" case does — otherwise an opt-out namespace can still
// receive a mirror via a glob like "app-*".
for _, ns := range allNamespaces {
if matchesPattern(ns, pattern) && ns != sourceNamespace && filter.IsAllowed(ns) {
if matchesPattern(ns, pattern) && ns != sourceNamespace && filter.IsAllowed(ns) && !optOutMap[ns] {
targetMap[ns] = true
}
}
} else {
// Direct namespace name
// Direct namespace name. Explicit listing is treated as
// intentional opt-in by the source author and bypasses the
// opt-out guard (matches prior behavior).
if pattern != sourceNamespace && filter.IsAllowed(pattern) {
targetMap[pattern] = true
}
+17
View File
@@ -376,6 +376,23 @@ func TestResolveTargetNamespaces_EdgeCases(t *testing.T) {
// Only "specific-ns" would be allowed, but it's not in allNamespaces
assert.Empty(t, got)
})
t.Run("glob pattern honors opt-out namespaces", func(t *testing.T) {
// Regression: a namespace with allow-mirrors=false used to receive a
// mirror via a glob like "app-*" because the glob branch ignored the
// opt-out list (only the "all" branch checked it).
got := ResolveTargetNamespaces(
[]string{"app-*"},
[]string{"app-1", "app-2", "app-optout"},
[]string{},
[]string{"app-optout"},
"default",
NewNamespaceFilter([]string{}, []string{}),
)
assert.Contains(t, got, "app-1")
assert.Contains(t, got, "app-2")
assert.NotContains(t, got, "app-optout", "opt-out namespace must not receive mirrors via glob match")
})
}
// Benchmark tests for critical paths