Add include/exclude policy to resources policy

fixes #8610

This commit extends the resources policy, such that user can define
resource include exclude filters in the policy and reuse it in different backups.

Signed-off-by: Daniel Jiang <daniel.jiang@broadcom.com>
This commit is contained in:
Daniel Jiang
2025-07-21 20:48:51 +08:00
parent a410c316d3
commit 249d8f581a
11 changed files with 537 additions and 215 deletions

View File

@@ -15,7 +15,7 @@ jobs:
with: with:
# ignore the config/.../crd.go file as it's generated binary data that is edited elsewhere. # ignore the config/.../crd.go file as it's generated binary data that is edited elsewhere.
skip: .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico,./config/crd/v1beta1/crds/crds.go,./config/crd/v1/crds/crds.go,./config/crd/v2alpha1/crds/crds.go,./go.sum,./LICENSE skip: .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico,./config/crd/v1beta1/crds/crds.go,./config/crd/v1/crds/crds.go,./config/crd/v2alpha1/crds/crds.go,./go.sum,./LICENSE
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot,atleast,notin,sme,optin ignore_words_list: iam,aks,ist,bridget,ue,shouldnot,atleast,notin,sme,optin,sie
check_filenames: true check_filenames: true
check_hidden: true check_hidden: true

View File

@@ -0,0 +1 @@
Add include/exclude policy to resources policy

View File

@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package resourcepolicies package resourcepolicies
import ( import (
@@ -20,6 +21,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1" corev1api "k8s.io/api/core/v1"
@@ -49,24 +52,58 @@ type Action struct {
Parameters map[string]any `yaml:"parameters,omitempty"` Parameters map[string]any `yaml:"parameters,omitempty"`
} }
// volumePolicy defined policy to conditions to match Volumes and related action to handle matched Volumes // 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.
// Refer to the comment in the velerov1api.BackupSpec for more details.
IncludedClusterScopedResources []string `yaml:"includedClusterScopedResources"`
ExcludedClusterScopedResources []string `yaml:"excludedClusterScopedResources"`
IncludedNamespaceScopedResources []string `yaml:"includedNamespaceScopedResources"`
ExcludedNamespaceScopedResources []string `yaml:"excludedNamespaceScopedResources"`
}
func (p *IncludeExcludePolicy) Validate() error {
if err := p.validateIncludeExclude(p.IncludedClusterScopedResources, p.ExcludedClusterScopedResources); err != nil {
return err
}
return p.validateIncludeExclude(p.IncludedNamespaceScopedResources, p.ExcludedNamespaceScopedResources)
}
func (p *IncludeExcludePolicy) validateIncludeExclude(includesList, excludesList []string) error {
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
if includes.Has("*") || excludes.Has("*") {
return fmt.Errorf("cannot use '*' in includes or excludes filters in the policy")
}
for _, itm := range excludes.List() {
if includes.Has(itm) {
return fmt.Errorf("excludes list cannot contain an item in the includes list: %s", itm)
}
}
return nil
}
// VolumePolicy defined policy to conditions to match Volumes and related action to handle matched Volumes
type VolumePolicy struct { type VolumePolicy struct {
// Conditions defined list of conditions to match Volumes // Conditions defined list of conditions to match Volumes
Conditions map[string]any `yaml:"conditions"` Conditions map[string]any `yaml:"conditions"`
Action Action `yaml:"action"` Action Action `yaml:"action"`
} }
// resourcePolicies currently defined slice of volume policies to handle backup // ResourcePolicies currently defined slice of volume policies to handle backup
type ResourcePolicies struct { type ResourcePolicies struct {
Version string `yaml:"version"` Version string `yaml:"version"`
VolumePolicies []VolumePolicy `yaml:"volumePolicies"` VolumePolicies []VolumePolicy `yaml:"volumePolicies"`
IncludeExcludePolicy *IncludeExcludePolicy `yaml:"includeExcludePolicy"`
// we may support other resource policies in the future, and they could be added separately // we may support other resource policies in the future, and they could be added separately
// OtherResourcePolicies []OtherResourcePolicy // OtherResourcePolicies []OtherResourcePolicy
} }
type Policies struct { type Policies struct {
version string version string
volumePolicies []volPolicy volumePolicies []volPolicy
includeExcludePolicy *IncludeExcludePolicy
// OtherPolicies // OtherPolicies
} }
@@ -115,6 +152,7 @@ func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error {
// Other resource policies // Other resource policies
p.version = resPolicies.Version p.version = resPolicies.Version
p.includeExcludePolicy = resPolicies.IncludeExcludePolicy
return nil return nil
} }
@@ -175,9 +213,20 @@ func (p *Policies) Validate() error {
} }
} }
} }
if p.GetIncludeExcludePolicy() != nil {
if err := p.GetIncludeExcludePolicy().Validate(); err != nil {
return errors.WithStack(err)
}
}
return nil return nil
} }
func (p *Policies) GetIncludeExcludePolicy() *IncludeExcludePolicy {
return p.includeExcludePolicy
}
func GetResourcePoliciesFromBackup( func GetResourcePoliciesFromBackup(
backup velerov1api.Backup, backup velerov1api.Backup,
client crclient.Client, client crclient.Client,

View File

@@ -453,6 +453,102 @@ func TestValidate(t *testing.T) {
}, },
wantErr: true, wantErr: true,
}, },
{
name: " '*' in the filters of include exclude policy - 1",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"*"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: []string{"secrets"},
},
},
wantErr: true,
},
{
name: " '*' in the filters of include exclude policy - 2",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: []string{"*"},
},
},
wantErr: true,
},
{
name: " dup item in both the include and exclude filters of include exclude policy",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods", "configmaps"},
ExcludedNamespaceScopedResources: []string{"secrets", "pods"},
},
},
wantErr: true,
},
{
name: " valid volume policies and valid include/exclude policy",
res: &ResourcePolicies{
Version: "v1",
VolumePolicies: []VolumePolicy{
{
Action: Action{Type: "skip"},
Conditions: map[string]any{
"pvcLabels": map[string]string{
"environment": "production",
"app": "database",
},
},
},
},
IncludeExcludePolicy: &IncludeExcludePolicy{
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
IncludedNamespaceScopedResources: []string{"pods", "configmaps"},
ExcludedNamespaceScopedResources: []string{"secrets"},
},
},
wantErr: false,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

View File

@@ -43,7 +43,6 @@ import (
kbclient "sigs.k8s.io/controller-runtime/pkg/client" kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/vmware-tanzu/velero/internal/hook" "github.com/vmware-tanzu/velero/internal/hook"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
"github.com/vmware-tanzu/velero/internal/volume" "github.com/vmware-tanzu/velero/internal/volume"
"github.com/vmware-tanzu/velero/internal/volumehelper" "github.com/vmware-tanzu/velero/internal/volumehelper"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@@ -270,13 +269,17 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
backupRequest.Spec.IncludeClusterResources, backupRequest.Spec.IncludeClusterResources,
*backupRequest.NamespaceIncludesExcludes) *backupRequest.NamespaceIncludesExcludes)
} else { } else {
backupRequest.ResourceIncludesExcludes = collections.GetScopeResourceIncludesExcludes(kb.discoveryHelper, log, srie := collections.GetScopeResourceIncludesExcludes(kb.discoveryHelper, log,
backupRequest.Spec.IncludedNamespaceScopedResources, backupRequest.Spec.IncludedNamespaceScopedResources,
backupRequest.Spec.ExcludedNamespaceScopedResources, backupRequest.Spec.ExcludedNamespaceScopedResources,
backupRequest.Spec.IncludedClusterScopedResources, backupRequest.Spec.IncludedClusterScopedResources,
backupRequest.Spec.ExcludedClusterScopedResources, backupRequest.Spec.ExcludedClusterScopedResources,
*backupRequest.NamespaceIncludesExcludes, *backupRequest.NamespaceIncludesExcludes,
) )
if backupRequest.ResPolicies != nil {
srie.CombineWithPolicy(backupRequest.ResPolicies.GetIncludeExcludePolicy())
}
backupRequest.ResourceIncludesExcludes = srie
} }
log.Infof("Backing up all volumes using pod volume backup: %t", boolptr.IsSetToTrue(backupRequest.Backup.Spec.DefaultVolumesToFsBackup)) log.Infof("Backing up all volumes using pod volume backup: %t", boolptr.IsSetToTrue(backupRequest.Backup.Spec.DefaultVolumesToFsBackup))
@@ -355,11 +358,6 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
} }
backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(items)} backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(items)}
var resourcePolicy *resourcepolicies.Policies
if backupRequest.ResPolicies != nil {
resourcePolicy = backupRequest.ResPolicies
}
itemBackupper := &itemBackupper{ itemBackupper := &itemBackupper{
backupRequest: backupRequest, backupRequest: backupRequest,
tarWriter: tw, tarWriter: tw,
@@ -374,7 +372,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
}, },
hookTracker: hook.NewHookTracker(), hookTracker: hook.NewHookTracker(),
volumeHelperImpl: volumehelper.NewVolumeHelperImpl( volumeHelperImpl: volumehelper.NewVolumeHelperImpl(
resourcePolicy, backupRequest.ResPolicies,
backupRequest.Spec.SnapshotVolumes, backupRequest.Spec.SnapshotVolumes,
log, log,
kb.kbClient, kb.kbClient,

View File

@@ -235,9 +235,10 @@ Hooks: <none>
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>
@@ -292,9 +293,10 @@ OrderedResources:
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>
@@ -325,9 +327,10 @@ Hooks: <none>
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>

View File

@@ -32,9 +32,10 @@ Backup Template:
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>
@@ -81,9 +82,10 @@ Backup Template:
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>
@@ -127,9 +129,10 @@ Backup Template:
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>
@@ -174,9 +177,10 @@ Backup Template:
Excluded: <none> Excluded: <none>
Resources: Resources:
Included: * Included cluster-scoped: <none>
Excluded: <none> Excluded cluster-scoped: <none>
Cluster-scoped: auto Included namespace-scoped: *
Excluded namespace-scoped: <none>
Label selector: <none> Label selector: <none>

View File

@@ -558,8 +558,11 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
if err != nil { if err != nil {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, err.Error()) request.Status.ValidationErrors = append(request.Status.ValidationErrors, err.Error())
} }
if resourcePolicies != nil && resourcePolicies.GetIncludeExcludePolicy() != nil && collections.UseOldResourceFilters(request.Spec) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n"+
"They cannot be used with include-exclude policies.")
}
request.ResPolicies = resourcePolicies request.ResPolicies = resourcePolicies
return request return request
} }
@@ -812,7 +815,6 @@ func (b *backupReconciler) runBackup(backup *pkgbackup.Request) error {
fatalErrs = append(fatalErrs, errs...) fatalErrs = append(fatalErrs, errs...)
} }
} }
b.logger.WithField(constant.ControllerBackup, kubeutil.NamespaceAndName(backup)).Infof("Initial backup processing complete, moving to %s", backup.Status.Phase) b.logger.WithField(constant.ControllerBackup, kubeutil.NamespaceAndName(backup)).Infof("Initial backup processing complete, moving to %s", backup.Status.Phase)
// if we return a non-nil error, the calling function will update // if we return a non-nil error, the calling function will update

View File

@@ -702,10 +702,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -740,10 +741,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: "alt-loc", StorageLocation: "alt-loc",
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -782,10 +784,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: "read-write", StorageLocation: "read-write",
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -820,11 +823,12 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
TTL: metav1.Duration{Duration: 10 * time.Minute}, TTL: metav1.Duration{Duration: 10 * time.Minute},
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -860,10 +864,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -900,10 +905,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -940,10 +946,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -980,10 +987,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1020,10 +1028,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1061,10 +1070,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed, Phase: velerov1api.BackupPhaseFailed,
@@ -1102,10 +1112,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.True(), DefaultVolumesToFsBackup: boolptr.True(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed, Phase: velerov1api.BackupPhaseFailed,
@@ -1143,10 +1154,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.True(), SnapshotMoveData: boolptr.True(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1185,10 +1197,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1227,10 +1240,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1269,10 +1283,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.True(), SnapshotMoveData: boolptr.True(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1312,10 +1327,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.False(), SnapshotMoveData: boolptr.False(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,
@@ -1354,10 +1370,11 @@ func TestProcessBackupCompletions(t *testing.T) {
}, },
}, },
Spec: velerov1api.BackupSpec{ Spec: velerov1api.BackupSpec{
StorageLocation: defaultBackupLocation.Name, StorageLocation: defaultBackupLocation.Name,
DefaultVolumesToFsBackup: boolptr.False(), DefaultVolumesToFsBackup: boolptr.False(),
SnapshotMoveData: boolptr.True(), SnapshotMoveData: boolptr.True(),
ExcludedResources: append(autoExcludeNamespaceScopedResources, autoExcludeClusterScopedResources...), ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
}, },
Status: velerov1api.BackupStatus{ Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing, Phase: velerov1api.BackupPhaseFinalizing,

View File

@@ -19,6 +19,8 @@ package collections
import ( import (
"strings" "strings"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -105,14 +107,63 @@ func (ie *IncludesExcludes) ShouldInclude(s string) bool {
return ie.includes.Len() == 0 || ie.includes.Has("*") || ie.includes.match(s) return ie.includes.Len() == 0 || ie.includes.Has("*") || ie.includes.match(s)
} }
// IncludesString returns a string containing all of the includes, separated by commas, or * if the
// list is empty.
func (ie *IncludesExcludes) IncludesString() string {
return asString(ie.GetIncludes(), "*")
}
// ExcludesString returns a string containing all of the excludes, separated by commas, or <none> if the
// list is empty.
func (ie *IncludesExcludes) ExcludesString() string {
return asString(ie.GetExcludes(), "<none>")
}
// IncludeEverything returns true if the includes list is empty or '*'
// and the excludes list is empty, or false otherwise.
func (ie *IncludesExcludes) IncludeEverything() bool {
return ie.excludes.Len() == 0 && (ie.includes.Len() == 0 || (ie.includes.Len() == 1 && ie.includes.Has("*")))
}
// GetResourceIncludesExcludes takes the lists of resources to include and exclude, uses the
// discovery helper to resolve them to fully-qualified group-resource names, and returns an
// IncludesExcludes list.
func GetResourceIncludesExcludes(helper discovery.Helper, includes, excludes []string) *IncludesExcludes {
resources := generateIncludesExcludes(
includes,
excludes,
func(item string) string {
gvr, _, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
// If we can't resolve it, return it as-is. This prevents the generated
// includes-excludes list from including *everything*, if none of the includes
// can be resolved. ref. https://github.com/vmware-tanzu/velero/issues/2461
return item
}
gr := gvr.GroupResource()
return gr.String()
},
)
return resources
}
func asString(in []string, empty string) string {
if len(in) == 0 {
return empty
}
return strings.Join(in, ", ")
}
// IncludesExcludesInterface is used as polymorphic IncludesExcludes for Global and scope // IncludesExcludesInterface is used as polymorphic IncludesExcludes for Global and scope
// resources Include/Exclude. // resources Include/Exclude.
type IncludesExcludesInterface interface { type IncludesExcludesInterface interface {
// Check whether the type name passed in by parameter should be included. // ShouldInclude checks whether the type name passed in by parameter should be included.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result. // typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldInclude(typeName string) bool ShouldInclude(typeName string) bool
// Check whether the type name passed in by parameter should be excluded. // ShouldExclude checks whether the type name passed in by parameter should be excluded.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result. // typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldExclude(typeName string) bool ShouldExclude(typeName string) bool
} }
@@ -188,6 +239,20 @@ func (ie *GlobalIncludesExcludes) ShouldExclude(typeName string) bool {
return false return false
} }
func GetGlobalResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, includes, excludes []string, includeClusterResources *bool, nsIncludesExcludes IncludesExcludes) *GlobalIncludesExcludes {
ret := &GlobalIncludesExcludes{
resourceFilter: *GetResourceIncludesExcludes(helper, includes, excludes),
includeClusterResources: includeClusterResources,
namespaceFilter: nsIncludesExcludes,
helper: helper,
logger: logger,
}
logger.Infof("Including resources: %s", ret.resourceFilter.IncludesString())
logger.Infof("Excluding resources: %s", ret.resourceFilter.ExcludesString())
return ret
}
type ScopeIncludesExcludes struct { type ScopeIncludesExcludes struct {
namespaceScopedResourceFilter IncludesExcludes // namespace-scoped resource filter namespaceScopedResourceFilter IncludesExcludes // namespace-scoped resource filter
clusterScopedResourceFilter IncludesExcludes // cluster-scoped resource filter clusterScopedResourceFilter IncludesExcludes // cluster-scoped resource filter
@@ -259,29 +324,64 @@ func (ie *ScopeIncludesExcludes) ShouldExclude(typeName string) bool {
return false return false
} }
// IncludesString returns a string containing all of the includes, separated by commas, or * if the func (ie *ScopeIncludesExcludes) CombineWithPolicy(policy *resourcepolicies.IncludeExcludePolicy) {
// list is empty. if policy == nil {
func (ie *IncludesExcludes) IncludesString() string { return
return asString(ie.GetIncludes(), "*")
}
// ExcludesString returns a string containing all of the excludes, separated by commas, or <none> if the
// list is empty.
func (ie *IncludesExcludes) ExcludesString() string {
return asString(ie.GetExcludes(), "<none>")
}
func asString(in []string, empty string) string {
if len(in) == 0 {
return empty
} }
return strings.Join(in, ", ") mapFunc := scopeResourceMapFunc(ie.helper)
} for _, item := range policy.ExcludedNamespaceScopedResources {
resolvedItem := mapFunc(item, true)
// IncludeEverything returns true if the includes list is empty or '*' if resolvedItem == "" {
// and the excludes list is empty, or false otherwise. continue
func (ie *IncludesExcludes) IncludeEverything() bool { }
return ie.excludes.Len() == 0 && (ie.includes.Len() == 0 || (ie.includes.Len() == 1 && ie.includes.Has("*"))) // The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not include this item and this item is not yet in the excludes filter.
if !ie.namespaceScopedResourceFilter.includes.match(resolvedItem) &&
!ie.namespaceScopedResourceFilter.excludes.match(resolvedItem) {
ie.namespaceScopedResourceFilter.Excludes(resolvedItem)
}
}
for _, item := range policy.IncludedNamespaceScopedResources {
resolvedItem := mapFunc(item, true)
if resolvedItem == "" {
continue
}
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
if !ie.namespaceScopedResourceFilter.includes.match(resolvedItem) &&
!ie.namespaceScopedResourceFilter.excludes.match(resolvedItem) {
ie.namespaceScopedResourceFilter.Includes(resolvedItem)
}
}
for _, item := range policy.ExcludedClusterScopedResources {
resolvedItem := mapFunc(item, false)
if resolvedItem == "" {
continue
}
if !ie.clusterScopedResourceFilter.includes.match(resolvedItem) &&
!ie.clusterScopedResourceFilter.excludes.match(resolvedItem) {
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
ie.clusterScopedResourceFilter.Excludes(resolvedItem)
}
}
for _, item := range policy.IncludedClusterScopedResources {
resolvedItem := mapFunc(item, false)
if resolvedItem == "" {
continue
}
if !ie.clusterScopedResourceFilter.includes.match(resolvedItem) &&
!ie.clusterScopedResourceFilter.excludes.match(resolvedItem) {
// The existing includeExcludes in the struct has higher priority, therefore, we should only add the item to the filter
// when the struct does not exclude this item and this item is not yet in the includes filter.
ie.clusterScopedResourceFilter.Includes(resolvedItem)
}
}
ie.logger.Infof("Scoped resource includes/excludes after combining with resource policy")
ie.logger.Infof("Including namespace-scoped resources: %s", ie.namespaceScopedResourceFilter.IncludesString())
ie.logger.Infof("Excluding namespace-scoped resources: %s", ie.namespaceScopedResourceFilter.ExcludesString())
ie.logger.Infof("Including cluster-scoped resources: %s", ie.clusterScopedResourceFilter.GetIncludes())
ie.logger.Infof("Excluding cluster-scoped resources: %s", ie.clusterScopedResourceFilter.ExcludesString())
} }
func newScopeIncludesExcludes(nsIncludesExcludes IncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes { func newScopeIncludesExcludes(nsIncludesExcludes IncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes {
@@ -302,6 +402,43 @@ func newScopeIncludesExcludes(nsIncludesExcludes IncludesExcludes, helper discov
return ret return ret
} }
// GetScopeResourceIncludesExcludes function is similar with GetResourceIncludesExcludes,
// but it's used for scoped Includes/Excludes, and can handle both cluster-scoped and namespace-scoped resources.
func GetScopeResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, namespaceIncludes, namespaceExcludes, clusterIncludes, clusterExcludes []string, nsIncludesExcludes IncludesExcludes) *ScopeIncludesExcludes {
ret := generateScopedIncludesExcludes(
namespaceIncludes,
namespaceExcludes,
clusterIncludes,
clusterExcludes,
scopeResourceMapFunc(helper),
nsIncludesExcludes,
helper,
logger,
)
logger.Infof("Scoped resource includes/excludes after initialization")
logger.Infof("Including namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.IncludesString())
logger.Infof("Excluding namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.ExcludesString())
logger.Infof("Including cluster-scoped resources: %s", ret.clusterScopedResourceFilter.GetIncludes())
logger.Infof("Excluding cluster-scoped resources: %s", ret.clusterScopedResourceFilter.ExcludesString())
return ret
}
func scopeResourceMapFunc(helper discovery.Helper) func(string, bool) string {
return func(item string, namespaced bool) string {
gvr, resource, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
return item
}
if resource.Namespaced != namespaced {
return ""
}
gr := gvr.GroupResource()
return gr.String()
}
}
// ValidateIncludesExcludes checks provided lists of included and excluded // ValidateIncludesExcludes checks provided lists of included and excluded
// items to ensure they are a valid set of IncludesExcludes data. // items to ensure they are a valid set of IncludesExcludes data.
func ValidateIncludesExcludes(includesList, excludesList []string) []error { func ValidateIncludesExcludes(includesList, excludesList []string) []error {
@@ -470,97 +607,16 @@ func generateFilter(filter globStringSet, resources []string, mapFunc func(strin
} }
} }
// GetResourceIncludesExcludes takes the lists of resources to include and exclude, uses the
// discovery helper to resolve them to fully-qualified group-resource names, and returns an
// IncludesExcludes list.
func GetResourceIncludesExcludes(helper discovery.Helper, includes, excludes []string) *IncludesExcludes {
resources := generateIncludesExcludes(
includes,
excludes,
func(item string) string {
gvr, _, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
// If we can't resolve it, return it as-is. This prevents the generated
// includes-excludes list from including *everything*, if none of the includes
// can be resolved. ref. https://github.com/vmware-tanzu/velero/issues/2461
return item
}
gr := gvr.GroupResource()
return gr.String()
},
)
return resources
}
func GetGlobalResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, includes, excludes []string, includeClusterResources *bool, nsIncludesExcludes IncludesExcludes) *GlobalIncludesExcludes {
ret := &GlobalIncludesExcludes{
resourceFilter: *GetResourceIncludesExcludes(helper, includes, excludes),
includeClusterResources: includeClusterResources,
namespaceFilter: nsIncludesExcludes,
helper: helper,
logger: logger,
}
logger.Infof("Including resources: %s", ret.resourceFilter.IncludesString())
logger.Infof("Excluding resources: %s", ret.resourceFilter.ExcludesString())
return ret
}
// GetScopeResourceIncludesExcludes function is similar with GetResourceIncludesExcludes,
// but it's used for scoped Includes/Excludes, and can handle both cluster-scoped and namespace-scoped resources.
func GetScopeResourceIncludesExcludes(helper discovery.Helper, logger logrus.FieldLogger, namespaceIncludes, namespaceExcludes, clusterIncludes, clusterExcludes []string, nsIncludesExcludes IncludesExcludes) *ScopeIncludesExcludes {
ret := generateScopedIncludesExcludes(
namespaceIncludes,
namespaceExcludes,
clusterIncludes,
clusterExcludes,
func(item string, namespaced bool) string {
gvr, resource, err := helper.ResourceFor(schema.ParseGroupResource(item).WithVersion(""))
if err != nil {
return item
}
if resource.Namespaced != namespaced {
return ""
}
gr := gvr.GroupResource()
return gr.String()
},
nsIncludesExcludes,
helper,
logger,
)
logger.Infof("Including namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.IncludesString())
logger.Infof("Excluding namespace-scoped resources: %s", ret.namespaceScopedResourceFilter.ExcludesString())
logger.Infof("Including cluster-scoped resources: %s", ret.clusterScopedResourceFilter.GetIncludes())
logger.Infof("Excluding cluster-scoped resources: %s", ret.clusterScopedResourceFilter.ExcludesString())
return ret
}
// UseOldResourceFilters checks whether to use old resource filters (IncludeClusterResources, // UseOldResourceFilters checks whether to use old resource filters (IncludeClusterResources,
// IncludedResources and ExcludedResources), depending the backup's filters setting. // IncludedResources and ExcludedResources), depending the backup's filters setting.
// New filters are IncludedClusterScopedResources, ExcludedClusterScopedResources, // New filters are IncludedClusterScopedResources, ExcludedClusterScopedResources,
// IncludedNamespaceScopedResources and ExcludedNamespaceScopedResources. // IncludedNamespaceScopedResources and ExcludedNamespaceScopedResources.
// If all resource filters are none, it is treated as using new parameter filters.
func UseOldResourceFilters(backupSpec velerov1api.BackupSpec) bool { func UseOldResourceFilters(backupSpec velerov1api.BackupSpec) bool {
// If all resource filters are none, it is treated as using old parameter filters.
if backupSpec.IncludeClusterResources == nil &&
len(backupSpec.IncludedResources) == 0 &&
len(backupSpec.ExcludedResources) == 0 &&
len(backupSpec.IncludedClusterScopedResources) == 0 &&
len(backupSpec.ExcludedClusterScopedResources) == 0 &&
len(backupSpec.IncludedNamespaceScopedResources) == 0 &&
len(backupSpec.ExcludedNamespaceScopedResources) == 0 {
return true
}
if backupSpec.IncludeClusterResources != nil || if backupSpec.IncludeClusterResources != nil ||
len(backupSpec.IncludedResources) > 0 || len(backupSpec.IncludedResources) > 0 ||
len(backupSpec.ExcludedResources) > 0 { len(backupSpec.ExcludedResources) > 0 {
return true return true
} }
return false return false
} }

View File

@@ -19,6 +19,8 @@ package collections
import ( import (
"testing" "testing"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -741,6 +743,100 @@ func TestGetScopedResourceIncludesExcludes(t *testing.T) {
} }
} }
func TestScopeIncludesExcludes_CombineWithPolicy(t *testing.T) {
apiResources := []*test.APIResource{test.Deployments(), test.Pods(), test.ConfigMaps(), test.Secrets(), test.PVs(), test.CRDs(), test.ServiceAccounts()}
tests := []struct {
name string
namespaceScopedIncludes []string
namespaceScopedExcludes []string
clusterScopedIncludes []string
clusterScopedExcludes []string
policy *resourcepolicies.IncludeExcludePolicy
verify func(sie ScopeIncludesExcludes) bool
}{
{
name: "When policy is nil, the original includes excludes filters should not change",
namespaceScopedIncludes: []string{"deployments", "pods"},
namespaceScopedExcludes: []string{"configmaps"},
clusterScopedIncludes: []string{"persistentvolumes"},
clusterScopedExcludes: []string{"crds"},
policy: nil,
verify: func(sie ScopeIncludesExcludes) bool {
return sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
!sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
sie.namespaceScopedResourceFilter.ShouldInclude("deployments") &&
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps")
},
},
{
name: "policy includes excludes should be merged to the original includes excludes when there's no conflict",
namespaceScopedIncludes: []string{"pods"},
namespaceScopedExcludes: []string{"configmaps"},
clusterScopedIncludes: []string{},
clusterScopedExcludes: []string{"crds"},
policy: &resourcepolicies.IncludeExcludePolicy{
IncludedNamespaceScopedResources: []string{"deployments"},
ExcludedNamespaceScopedResources: []string{"secrets"},
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{},
},
verify: func(sie ScopeIncludesExcludes) bool {
return sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
!sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
sie.namespaceScopedResourceFilter.ShouldInclude("deployments") &&
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
!sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
},
},
{
name: "when there are conflicts, the existing includes excludes filters have higher priorities",
namespaceScopedIncludes: []string{"pods", "deployments"},
namespaceScopedExcludes: []string{"configmaps"},
clusterScopedIncludes: []string{"crds"},
clusterScopedExcludes: []string{"persistentvolumes"},
policy: &resourcepolicies.IncludeExcludePolicy{
IncludedNamespaceScopedResources: []string{"configmaps"},
ExcludedNamespaceScopedResources: []string{"pods", "secrets"},
IncludedClusterScopedResources: []string{"persistentvolumes"},
ExcludedClusterScopedResources: []string{"crds"},
},
verify: func(sie ScopeIncludesExcludes) bool {
return sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
!sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
sie.namespaceScopedResourceFilter.ShouldInclude("pods") &&
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
!sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
},
},
{
name: "verify the case when there's '*' in the original include filter",
namespaceScopedIncludes: []string{"*"},
namespaceScopedExcludes: []string{},
clusterScopedIncludes: []string{},
clusterScopedExcludes: []string{},
policy: &resourcepolicies.IncludeExcludePolicy{
IncludedNamespaceScopedResources: []string{"deployments", "pods"},
ExcludedNamespaceScopedResources: []string{"configmaps", "secrets"},
IncludedClusterScopedResources: []string{},
ExcludedClusterScopedResources: []string{},
},
verify: func(sie ScopeIncludesExcludes) bool {
return sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
logger := logrus.StandardLogger()
discoveryHelper := setupDiscoveryClientWithResources(apiResources)
sie := GetScopeResourceIncludesExcludes(discoveryHelper, logger, tc.namespaceScopedIncludes, tc.namespaceScopedExcludes, tc.clusterScopedIncludes, tc.clusterScopedExcludes, *NewIncludesExcludes())
sie.CombineWithPolicy(tc.policy)
assert.True(t, tc.verify(*sie))
})
}
}
func TestUseOldResourceFilters(t *testing.T) { func TestUseOldResourceFilters(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@@ -748,9 +844,9 @@ func TestUseOldResourceFilters(t *testing.T) {
useOldResourceFilters bool useOldResourceFilters bool
}{ }{
{ {
name: "backup with no filters should use old filters", name: "backup with no filters should use new filters",
backup: *defaultBackup().Result(), backup: *defaultBackup().Result(),
useOldResourceFilters: true, useOldResourceFilters: false,
}, },
{ {
name: "backup with only old filters should use old filters", name: "backup with only old filters should use old filters",