From b0e72333a0cb6d4c00016054a574ecd7bab2f18d Mon Sep 17 00:00:00 2001 From: Adam Zhang Date: Thu, 21 May 2026 15:30:25 +0800 Subject: [PATCH 1/2] extend backup resource policy - added ClusterScopedFilterPolicy/NamespacedFilterPolicy - added run time data structure, ResolvedResourceFilter and ResolvedNamespaceFilter Signed-off-by: Adam Zhang --- changelogs/unreleased/9821-adam-jian-zhang | 1 + .../resourcepolicies/resource_policies.go | 51 ++++++++++++++-- pkg/backup/request.go | 61 +++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/9821-adam-jian-zhang diff --git a/changelogs/unreleased/9821-adam-jian-zhang b/changelogs/unreleased/9821-adam-jian-zhang new file mode 100644 index 000000000..4cbdcdf20 --- /dev/null +++ b/changelogs/unreleased/9821-adam-jian-zhang @@ -0,0 +1 @@ +Fix issue #9811, add interface to support ClusterScopedFilterPolicy and NamespacedFilterPolicy diff --git a/internal/resourcepolicies/resource_policies.go b/internal/resourcepolicies/resource_policies.go index 3a173fce1..0e685c2bb 100644 --- a/internal/resourcepolicies/resource_policies.go +++ b/internal/resourcepolicies/resource_policies.go @@ -54,6 +54,31 @@ type Action struct { Parameters map[string]any `yaml:"parameters,omitempty"` } +// ResourceFilter defines a filter for specific resource kinds within a namespace. +type ResourceFilter struct { + Kinds []string `yaml:"kinds"` + LabelSelector map[string]string `yaml:"labelSelector,omitempty"` + OrLabelSelectors []map[string]string `yaml:"orLabelSelectors,omitempty"` + Names []string `yaml:"names,omitempty"` + ExcludedNames []string `yaml:"excludedNames,omitempty"` +} + +// IsCatchAll returns true if the filter is a catch-all entry (empty kinds or ["*"]) +func (rf *ResourceFilter) IsCatchAll() bool { + return len(rf.Kinds) == 0 || (len(rf.Kinds) == 1 && rf.Kinds[0] == "*") +} + +// ClusterScopedFilterPolicy defines backup filters scoped globally to cluster-scoped resources. +type ClusterScopedFilterPolicy struct { + ResourceFilters []ResourceFilter `yaml:"resourceFilters"` +} + +// NamespacedFilterPolicy defines backup filters scoped to specific namespaces. +type NamespacedFilterPolicy struct { + Namespaces []string `yaml:"namespaces"` + ResourceFilters []ResourceFilter `yaml:"resourceFilters"` +} + // IncludeExcludePolicy defined policy to include or exclude resources based on the names type IncludeExcludePolicy struct { // The following fields have the same semantics as those from the spec of backup. @@ -95,17 +120,21 @@ type VolumePolicy struct { // ResourcePolicies currently defined slice of volume policies to handle backup type ResourcePolicies struct { - Version string `yaml:"version"` - VolumePolicies []VolumePolicy `yaml:"volumePolicies"` - IncludeExcludePolicy *IncludeExcludePolicy `yaml:"includeExcludePolicy"` + Version string `yaml:"version"` + VolumePolicies []VolumePolicy `yaml:"volumePolicies"` + IncludeExcludePolicy *IncludeExcludePolicy `yaml:"includeExcludePolicy"` + ClusterScopedFilterPolicy *ClusterScopedFilterPolicy `yaml:"clusterScopedFilterPolicy,omitempty"` + NamespacedFilterPolicies []NamespacedFilterPolicy `yaml:"namespacedFilterPolicies,omitempty"` // we may support other resource policies in the future, and they could be added separately // OtherResourcePolicies []OtherResourcePolicy } type Policies struct { - version string - volumePolicies []volPolicy - includeExcludePolicy *IncludeExcludePolicy + version string + volumePolicies []volPolicy + includeExcludePolicy *IncludeExcludePolicy + clusterScopedFilterPolicy *ClusterScopedFilterPolicy + namespacedFilterPolicies []NamespacedFilterPolicy // OtherPolicies } @@ -158,6 +187,8 @@ func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error { p.version = resPolicies.Version p.includeExcludePolicy = resPolicies.IncludeExcludePolicy + p.clusterScopedFilterPolicy = resPolicies.ClusterScopedFilterPolicy + p.namespacedFilterPolicies = resPolicies.NamespacedFilterPolicies return nil } @@ -235,6 +266,14 @@ func (p *Policies) GetIncludeExcludePolicy() *IncludeExcludePolicy { return p.includeExcludePolicy } +func (p *Policies) GetClusterScopedFilterPolicy() *ClusterScopedFilterPolicy { + return p.clusterScopedFilterPolicy +} + +func (p *Policies) GetNamespacedFilterPolicies() []NamespacedFilterPolicy { + return p.namespacedFilterPolicies +} + func GetResourcePoliciesFromBackup( backup velerov1api.Backup, client crclient.Client, diff --git a/pkg/backup/request.go b/pkg/backup/request.go index eb9edcbe8..ca2e638c2 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -19,6 +19,9 @@ package backup import ( "sync" + "github.com/gobwas/glob" + "k8s.io/apimachinery/pkg/labels" + "github.com/vmware-tanzu/velero/internal/hook" "github.com/vmware-tanzu/velero/internal/resourcepolicies" "github.com/vmware-tanzu/velero/internal/volume" @@ -34,6 +37,21 @@ type itemKey struct { name string } +// ResolvedResourceFilter holds the materialized filter state for one kind-group +// within a namespace. +type ResolvedResourceFilter struct { + LabelSelector labels.Selector + OrLabelSelectors []labels.Selector + NameIE *collections.IncludesExcludes +} + +// ResolvedNamespaceFilter holds the materialized filter state for a namespace. +// ResourceFilterMap is keyed by the resolved group-resource string. +type ResolvedNamespaceFilter struct { + ResourceFilterMap map[string]*ResolvedResourceFilter + CatchAllFilter *ResolvedResourceFilter +} + type SynchronizedVSList struct { sync.Mutex VolumeSnapshotList []*volume.Snapshot @@ -70,6 +88,27 @@ type Request struct { SkippedPVTracker *skipPVTracker VolumesInformation volume.BackupVolumesInformation WorkerPool *ItemBlockWorkerPool + + // ClusterScopedFilterMap holds resolved global filters for cluster-scoped resources. + // Key is the resolved group-resource string. + ClusterScopedFilterMap map[string]*ResolvedResourceFilter + + // NamespacedFilterMap holds resolved per-namespace filters. + // Key is either an exact namespace name or a glob pattern. + NamespacedFilterMap map[string]*ResolvedNamespaceFilter + + // NamespacedFilterPatterns preserves the order of patterns for first-match semantics + // and caches pre-compiled globs to avoid repeated compilation in the hot path. + NamespacedFilterPatterns []NamespacedFilterPattern +} + +// NamespacedFilterPattern pairs a namespace pattern string with its pre-compiled +// glob so that GetNamespaceFilter does not recompile on every call. +// Compiled is nil for exact-match (non-glob) patterns, which are looked up +// directly in NamespacedFilterMap. +type NamespacedFilterPattern struct { + Pattern string + Compiled glob.Glob } // BackupVolumesInformation contains the information needs by generating @@ -107,3 +146,25 @@ func (r *Request) FillVolumesInformation() { func (r *Request) StopWorkerPool() { r.WorkerPool.Stop() } + +// GetNamespaceFilter returns the resolved filter for a namespace, or nil +// if the namespace should use global filters. Uses first-match semantics +// when multiple patterns could match the same namespace. +func (r *Request) GetNamespaceFilter(namespace string) *ResolvedNamespaceFilter { + if r.NamespacedFilterMap == nil { + return nil + } + + // First check for exact match + if f, ok := r.NamespacedFilterMap[namespace]; ok { + return f + } + + // Walk patterns in definition order using pre-compiled globs (no allocation per call) + for _, p := range r.NamespacedFilterPatterns { + if p.Compiled != nil && p.Compiled.Match(namespace) { + return r.NamespacedFilterMap[p.Pattern] + } + } + return nil +} From 348f9227aa667d6d325d5ca92a3b7da3c213e022 Mon Sep 17 00:00:00 2001 From: Adam Zhang Date: Fri, 22 May 2026 13:20:53 +0800 Subject: [PATCH 2/2] Update internal/resourcepolicies/resource_policies.go Co-authored-by: Tiger Kaovilai Signed-off-by: Adam Zhang --- internal/resourcepolicies/resource_policies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/resourcepolicies/resource_policies.go b/internal/resourcepolicies/resource_policies.go index 0e685c2bb..ce32dba19 100644 --- a/internal/resourcepolicies/resource_policies.go +++ b/internal/resourcepolicies/resource_policies.go @@ -54,7 +54,7 @@ type Action struct { Parameters map[string]any `yaml:"parameters,omitempty"` } -// ResourceFilter defines a filter for specific resource kinds within a namespace. +// ResourceFilter defines a filter for specific resource kinds. type ResourceFilter struct { Kinds []string `yaml:"kinds"` LabelSelector map[string]string `yaml:"labelSelector,omitempty"`