Prioritize exact namespace match in restore (#10033)
Run the E2E test on kind / get-go-version (push) Failing after 1m12s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 12s
Main CI / Build (push) Failing after 26s

* Prioritize exact namespace match in restore

Align restore pipeline with backup pipeline by
evaluating exact namespace matches before glob
patterns in namespacedFilterPolicies. This ensures
specific overrides always win regardless of list
order.

Signed-off-by: Adam Zhang <adam.zhang@broadcom.com>

* improve test cases

add test cases for exact listed first, and excat listed last to
ensure the behavior that the order does not matter for exact
listed namespace, the rule will be always honored.

Signed-off-by: Adam Zhang <adam.zhang@broadcom.com>

---------

Signed-off-by: Adam Zhang <adam.zhang@broadcom.com>
This commit is contained in:
Adam Zhang
2026-07-20 09:18:13 -04:00
committed by GitHub
parent b96bb73308
commit d59e7d4eb8
4 changed files with 62 additions and 16 deletions
+10 -6
View File
@@ -478,7 +478,15 @@ func (ctx *restoreContext) getNamespaceFilter(namespace string) *resolvedNamespa
return filter
}
// 2. Walk patterns in definition order (first-match semantics)
// 2. Check for exact match first (O(1) map lookup)
// This ensures exact namespace matches take precedence over globs,
// regardless of where they are listed in the configuration.
if filter, ok := ctx.namespacedFilterMap[namespace]; ok {
ctx.namespaceFilterCache[namespace] = filter
return filter
}
// 3. Walk patterns in definition order using pre-compiled globs
// Note: namespaceFilterCache is mutated below without synchronization. This is safe
// today because resource collection runs sequentially. If the restore loop is
// parallelized in the future, these map writes will need a lock to prevent data races.
@@ -489,14 +497,10 @@ func (ctx *restoreContext) getNamespaceFilter(namespace string) *resolvedNamespa
ctx.namespaceFilterCache[namespace] = filter
return filter
}
} else if p.pattern == namespace {
filter := ctx.namespacedFilterMap[p.pattern]
ctx.namespaceFilterCache[namespace] = filter
return filter
}
}
// 3. Cache the miss so we don't re-evaluate failed matches
// 4. Cache the miss so we don't re-evaluate failed matches
ctx.namespaceFilterCache[namespace] = nil
return nil
}
+38 -2
View File
@@ -62,7 +62,7 @@ namespacedFilterPolicies:
},
},
{
name: "namespaced filter policy with glob namespace match and first-match semantics",
name: "namespaced filter policy with exact match priority over glob (glob listed first)",
restore: defaultRestore().Result(),
backup: defaultBackup().Result(),
policyYAML: `version: v1
@@ -94,7 +94,43 @@ namespacedFilterPolicies:
test.Pods(),
},
want: map[*test.APIResource][]string{
test.Pods(): {"ns-1/pod-1", "ns-2/pod-1"},
test.Pods(): {"ns-1/pod-2", "ns-2/pod-1"},
},
},
{
name: "namespaced filter policy with exact match priority over glob (exact listed first)",
restore: defaultRestore().Result(),
backup: defaultBackup().Result(),
policyYAML: `version: v1
namespacedFilterPolicies:
- namespaces:
- ns-1
resourceFilters:
- kinds:
- pods
names:
- pod-2
- namespaces:
- ns-*
resourceFilters:
- kinds:
- pods
names:
- pod-1
`,
tarball: test.NewTarWriter(t).
AddItems("pods",
builder.ForPod("ns-1", "pod-1").Result(),
builder.ForPod("ns-1", "pod-2").Result(),
builder.ForPod("ns-2", "pod-1").Result(),
builder.ForPod("ns-2", "pod-2").Result(),
).
Done(),
apiResources: []*test.APIResource{
test.Pods(),
},
want: map[*test.APIResource][]string{
test.Pods(): {"ns-1/pod-2", "ns-2/pod-1"},
},
},
{