Merge pull request #5838 from blackpiglet/add_new_resource_filters

Add new resource filters can separate cluster and namespace scope res…
pull/5916/head
Daniel Jiang 2023-03-15 08:57:10 +08:00 committed by GitHub
commit d6a3da2929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2351 additions and 137 deletions

View File

@ -0,0 +1 @@
Add new resource filters can separate cluster and namespace scope resources.

View File

@ -54,6 +54,22 @@ spec:
Use DefaultVolumesToFsBackup instead."
nullable: true
type: boolean
excludedClusterScopeResources:
description: ExcludedClusterScopeResources is a slice of cluster scope
resource type names to exclude from the backup. If set to "*", all
cluster scope resource types are excluded.
items:
type: string
nullable: true
type: array
excludedNamespacedResources:
description: ExcludedNamespacedResources is a slice of namespace scope
resource type names to exclude from the backup. If set to "*", all
namespace scope resource types are excluded.
items:
type: string
nullable: true
type: array
excludedNamespaces:
description: ExcludedNamespaces contains a list of namespaces that
are not included in the backup.
@ -259,6 +275,23 @@ spec:
resources should be included for consideration in the backup.
nullable: true
type: boolean
includedClusterScopeResources:
description: IncludedClusterScopeResources is a slice of cluster scope
resource type names to include in the backup. If set to "*", all
cluster scope resource types are included. The default value is
empty, which means only related cluster scope resources are included.
items:
type: string
nullable: true
type: array
includedNamespacedResources:
description: IncludedNamespacedResources is a slice of namespace scope
resource type names to include in the backup. The default value
is "*".
items:
type: string
nullable: true
type: array
includedNamespaces:
description: IncludedNamespaces is a slice of namespace names to include
objects from. If empty, all namespaces are included.

View File

@ -84,6 +84,22 @@ spec:
entirely in future. Use DefaultVolumesToFsBackup instead."
nullable: true
type: boolean
excludedClusterScopeResources:
description: ExcludedClusterScopeResources is a slice of cluster
scope resource type names to exclude from the backup. If set
to "*", all cluster scope resource types are excluded.
items:
type: string
nullable: true
type: array
excludedNamespacedResources:
description: ExcludedNamespacedResources is a slice of namespace
scope resource type names to exclude from the backup. If set
to "*", all namespace scope resource types are excluded.
items:
type: string
nullable: true
type: array
excludedNamespaces:
description: ExcludedNamespaces contains a list of namespaces
that are not included in the backup.
@ -294,6 +310,24 @@ spec:
resources should be included for consideration in the backup.
nullable: true
type: boolean
includedClusterScopeResources:
description: IncludedClusterScopeResources is a slice of cluster
scope resource type names to include in the backup. If set to
"*", all cluster scope resource types are included. The default
value is empty, which means only related cluster scope resources
are included.
items:
type: string
nullable: true
type: array
includedNamespacedResources:
description: IncludedNamespacedResources is a slice of namespace
scope resource type names to include in the backup. The default
value is "*".
items:
type: string
nullable: true
type: array
includedNamespaces:
description: IncludedNamespaces is a slice of namespace names
to include objects from. If empty, all namespaces are included.

File diff suppressed because one or more lines are too long

View File

@ -8,13 +8,26 @@
- [High-Level Design](#high-level-design)
- [Parameters Rules](#parameters-rules)
- [Using scenarios:](#using-scenarios)
- [no namespaced resources + no cluster resources](#no-namespaced-resources--no-cluster-resources)
- [no namespaced resources + some cluster resources](#no-namespaced-resources--some-cluster-resources)
- [no namespaced resources + all cluster resources](#no-namespaced-resources--all-cluster-resources)
- [some namespaced resources + no cluster resources](#some-namespaced-resources--no-cluster-resources)
- [scenario 1](#scenario-1)
- [scenario 2](#scenario-2)
- [scenario 3](#scenario-3)
- [scenario 4](#scenario-4)
- [some namespaced resources + only related cluster resources](#some-namespaced-resources--only-related-cluster-resources)
- [scenario 1](#scenario-1-1)
- [scenario 2](#scenario-2-1)
- [scenario 3](#scenario-3-1)
- [some namespaced resources + some additional cluster resources](#some-namespaced-resources--some-additional-cluster-resources)
- [scenario 1](#scenario-1-2)
- [scenario 2](#scenario-2-2)
- [scenario 3](#scenario-3-2)
- [scenario 4](#scenario-4-1)
- [some namespaced resources + all cluster resources](#some-namespaced-resources--all-cluster-resources)
- [scenario 1](#scenario-1-3)
- [scenario 2](#scenario-2-3)
- [scenario 3](#scenario-3-3)
- [all namespaced resources + no cluster resources](#all-namespaced-resources--no-cluster-resources)
- [all namespaced resources + some additional cluster resources](#all-namespaced-resources--some-additional-cluster-resources)
- [all namespaced resources + all cluster resources](#all-namespaced-resources--all-cluster-resources)
@ -67,14 +80,9 @@ Restore and other code pieces also use resource filtering will be handled in fut
* If both `--include-cluster-scope-resources` and `--exclude-cluster-scope-resources` are not present, it means no additional cluster resource is included per resource type, just as the existing `--include-cluster-resources` parameter not setting value. Cluster resources are related to the namespace scope resources, which means those are returned in the namespace resources' BackupItemAction's result AdditionalItems array, are still included in backup by default. Taking backing up PVC scenario as an example, PVC is namespaced, PV is in cluster scope. PVC's BIA will include PVC related PV into backup too.
* If the backup contains no resource, validation failure should be returned.
### Using scenarios:
Please notice, if the scenario give the example of using old filtering parameters (`--include-cluster-resources`, `--include-resources` and `--exclude-resources`), that means the old parameters also work for this case. If old parameters example is not given, that means they don't work for this scenario, only new parameters (`--include-cluster-scope-resources`, `--include-namespaced-resources`, `--exclude-cluster-scope-resources` and `--exclude-namespaced-resources`) work.
#### no namespaced resources + no cluster resources
This is not allowed. Backup or restore cannot contain no resource.
#### no namespaced resources + some cluster resources
The following command means backup no namespaced resources and some cluster resources.
@ -94,6 +102,7 @@ velero backup create <backup-name>
```
#### some namespaced resources + no cluster resources
##### scenario 1
The following commands mean backup all resources in namespaces default and kube-system, and no cluster resources.
Example of new parameters:
@ -109,7 +118,7 @@ velero backup create <backup-name>
--include-namespaces=default,kube-system
--include-cluster-resources=false
```
##### scenario 2
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and no cluster resources. Although PVC's related PV should be included, due to no cluster resources are included, so they are ruled out too.
Example of new parameters:
@ -125,7 +134,7 @@ velero backup create <backup-name>
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
--include-cluster-resources=false
```
##### scenario 3
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in namespace default and kube-system, and no cluster resources. Although PVC's related PV should be included, due to no cluster resources are included, so they are ruled out too.
Example of new parameters:
@ -143,7 +152,7 @@ velero backup create <backup-name>
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
--include-cluster-resources=false
```
##### scenario 4
The following commands mean backup all resources except Ingress type resources in all namespaces, and no cluster resources.
Example of new parameters:
@ -161,42 +170,22 @@ velero backup create <backup-name>
```
#### some namespaced resources + only related cluster resources
##### scenario 1
This means backup all resources in namespaces default and kube-system, and related cluster resources.
``` bash
velero backup create <backup-name>
--include-namespaces=default,kube-system
```
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and related cluster resources (PVC's related PV).
Example of new parameters:
``` bash
velero backup create <backup-name>
--include-namespaced-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
```
Example of old parameters:
``` bash
velero backup create <backup-name>
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
```
The following commands mean backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in namespaces default and kube-system, and related cluster resources. PVC related PV is included too.
Example of new parameters:
##### scenario 2
This means backup pods and configmaps in namespaces default and kube-system, and related cluster resources.
``` bash
velero backup create <backup-name>
--include-namespaces=default,kube-system
--include-namespaced-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
```
Example of old parameters:
``` bash
velero backup create <backup-name>
--include-namespaces=default,kube-system
--include-resources=persistentvolumeclaim,deployment,service,endpoint,pod,replicaset
--include-namespaced-resources=pods,configmaps
```
##### scenario 3
This means backup all resources except Ingress type resources in all namespaces, and related cluster resources.
Example of new parameters:
@ -212,6 +201,7 @@ velero backup create <backup-name>
```
#### some namespaced resources + some additional cluster resources
##### scenario 1
This means backup all resources in namespace in default, kube-system, and related cluster resources, plus all StorageClass cluster resources.
``` bash
velero backup create <backup-name>
@ -219,6 +209,7 @@ velero backup create <backup-name>
--include-cluster-scope-resources=storageclass
```
##### scenario 2
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and related cluster resources, plus all StorageClass cluster resources, and PVC related PV.
``` bash
velero backup create <backup-name>
@ -226,6 +217,7 @@ velero backup create <backup-name>
--include-cluster-scope-resources=storageclass
```
##### scenario 3
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and related cluster resources, plus all StorageClass cluster resources, and PVC related PV.
``` bash
velero backup create <backup-name>
@ -234,6 +226,7 @@ velero backup create <backup-name>
--include-cluster-scope-resources=storageclass
```
##### scenario 4
This means backup PVC, Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and related cluster resources, plus all cluster scope resources except StorageClass type resources.
``` bash
velero backup create <backup-name>
@ -243,6 +236,7 @@ velero backup create <backup-name>
```
#### some namespaced resources + all cluster resources
##### scenario 1
The following commands mean backup all resources in namespace in default, kube-system, and all cluster resources.
Example of new parameters:
@ -259,6 +253,7 @@ velero backup create <backup-name>
--include-cluster-resources=true
```
##### scenario 2
This means backup Deployment, Service, Endpoint, Pod and ReplicaSet resources in all namespaces, and all cluster resources.
``` bash
velero backup create <backup-name>
@ -266,6 +261,7 @@ velero backup create <backup-name>
--include-cluster-scope-resources=*
```
##### scenario 3
This means backup Deployment, Service, Endpoint, Pod and ReplicaSet resources in default and kube-system namespaces, and all cluster resources.
``` bash
velero backup create <backup-name>

View File

@ -1332,8 +1332,8 @@ func TestGetRestoreHooksFromSpec(t *testing.T) {
{
Name: "h1",
Selector: ResourceHookSelector{
Namespaces: collections.NewIncludesExcludes().Includes([]string{"ns1", "ns2", "ns3"}...).Excludes([]string{"ns4", "ns5", "ns6"}...),
Resources: collections.NewIncludesExcludes().Includes([]string{kuberesource.Pods.Resource}...),
Namespaces: collections.NewIncludesExcludes().Includes("ns1", "ns2", "ns3").Excludes("ns4", "ns5", "ns6"),
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
},
RestoreHooks: []velerov1api.RestoreResourceHook{
{

View File

@ -52,6 +52,36 @@ type BackupSpec struct {
// +nullable
ExcludedResources []string `json:"excludedResources,omitempty"`
// IncludedClusterScopeResources is a slice of cluster scope
// resource type names to include in the backup.
// If set to "*", all cluster scope resource types are included.
// The default value is empty, which means only related cluster
// scope resources are included.
// +optional
// +nullable
IncludedClusterScopeResources []string `json:"includedClusterScopeResources,omitempty"`
// ExcludedClusterScopeResources is a slice of cluster scope
// resource type names to exclude from the backup.
// If set to "*", all cluster scope resource types are excluded.
// +optional
// +nullable
ExcludedClusterScopeResources []string `json:"excludedClusterScopeResources,omitempty"`
// IncludedNamespacedResources is a slice of namespace scope
// resource type names to include in the backup.
// The default value is "*".
// +optional
// +nullable
IncludedNamespacedResources []string `json:"includedNamespacedResources,omitempty"`
// ExcludedNamespacedResources is a slice of namespace scope
// resource type names to exclude from the backup.
// If set to "*", all namespace scope resource types are excluded.
// +optional
// +nullable
ExcludedNamespacedResources []string `json:"excludedNamespacedResources,omitempty"`
// LabelSelector is a metav1.LabelSelector to filter with
// when adding individual objects to the backup. If empty
// or nil, all objects are included. Optional.

View File

@ -299,6 +299,26 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IncludedClusterScopeResources != nil {
in, out := &in.IncludedClusterScopeResources, &out.IncludedClusterScopeResources
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ExcludedClusterScopeResources != nil {
in, out := &in.ExcludedClusterScopeResources, &out.ExcludedClusterScopeResources
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IncludedNamespacedResources != nil {
in, out := &in.IncludedNamespacedResources, &out.IncludedNamespacedResources
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ExcludedNamespacedResources != nil {
in, out := &in.ExcludedNamespacedResources, &out.ExcludedNamespacedResources
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.LabelSelector != nil {
in, out := &in.LabelSelector, &out.LabelSelector
*out = new(metav1.LabelSelector)

View File

@ -209,9 +209,22 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
log.Infof("Including namespaces: %s", backupRequest.NamespaceIncludesExcludes.IncludesString())
log.Infof("Excluding namespaces: %s", backupRequest.NamespaceIncludesExcludes.ExcludesString())
backupRequest.ResourceIncludesExcludes = collections.GetResourceIncludesExcludes(kb.discoveryHelper, backupRequest.Spec.IncludedResources, backupRequest.Spec.ExcludedResources)
log.Infof("Including resources: %s", backupRequest.ResourceIncludesExcludes.IncludesString())
log.Infof("Excluding resources: %s", backupRequest.ResourceIncludesExcludes.ExcludesString())
if collections.UseOldResourceFilters(backupRequest.Spec) {
backupRequest.ResourceIncludesExcludes = collections.GetGlobalResourceIncludesExcludes(kb.discoveryHelper, log,
backupRequest.Spec.IncludedResources,
backupRequest.Spec.ExcludedResources,
backupRequest.Spec.IncludeClusterResources,
*backupRequest.NamespaceIncludesExcludes)
} else {
backupRequest.ResourceIncludesExcludes = collections.GetScopeResourceIncludesExcludes(kb.discoveryHelper, log,
backupRequest.Spec.IncludedNamespacedResources,
backupRequest.Spec.ExcludedNamespacedResources,
backupRequest.Spec.IncludedClusterScopeResources,
backupRequest.Spec.ExcludedClusterScopeResources,
*backupRequest.NamespaceIncludesExcludes,
)
}
log.Infof("Backing up all volumes using pod volume backup: %t", boolptr.IsSetToTrue(backupRequest.Backup.Spec.DefaultVolumesToFsBackup))
var err error
@ -398,10 +411,12 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
// no more progress updates will be sent on the 'update' channel
quit <- struct{}{}
// back up CRD for resource if found. We should only need to do this if we've backed up at least
// one item for the resource and IncludeClusterResources is nil. If IncludeClusterResources is false
// we don't want to back it up, and if it's true it will already be included.
if backupRequest.Spec.IncludeClusterResources == nil {
// back up CRD(this is a CRD definition of the resource, it's a CRD instance) for resource if found.
// We should only need to do this if we've backed up at least one item for the resource
// and the CRD type(this is the CRD type itself) is neither included or excluded.
// When it's included, the resource's CRD is already handled. When it's excluded, no need to check.
if !backupRequest.ResourceIncludesExcludes.ShouldExclude(kuberesource.CustomResourceDefinitions.String()) &&
!backupRequest.ResourceIncludesExcludes.ShouldInclude(kuberesource.CustomResourceDefinitions.String()) {
for gr := range backedUpGroupResources {
kb.backupCRD(log, gr, itemBackupper)
}
@ -492,6 +507,7 @@ func (kb *kubernetesBackupper) backupCRD(log logrus.FieldLogger, gr schema.Group
log.WithError(errors.WithStack(err)).Errorf("Error getting CRD %s", gr.String())
return
}
log.Infof("Found associated CRD %s to add to backup", gr.String())
kb.backupItem(log, gvr.GroupResource(), itemBackupper, unstructured, gvr)

View File

@ -157,12 +157,13 @@ func TestBackupProgressIsUpdated(t *testing.T) {
// verifies that the set of items written to the backup tarball are
// correct. Validation is done by looking at the names of the files in
// the backup tarball; the contents of the files are not checked.
func TestBackupResourceFiltering(t *testing.T) {
func TestBackupOldResourceFiltering(t *testing.T) {
tests := []struct {
name string
backup *velerov1.Backup
apiResources []*test.APIResource
want []string
actions []biav2.BackupItemAction
}{
{
name: "no filters backs up everything",
@ -760,6 +761,95 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
},
},
{
name: "new filters' default value should not impact the old filters' function",
backup: defaultBackup().IncludedNamespaces("foo").IncludeClusterResources(true).Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Volumes(builder.ForVolume("foo").PersistentVolumeClaimSource("test-1").Result()).Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "Resource's CRD should be included",
backup: defaultBackup().IncludedNamespaces("foo").Result(),
apiResources: []*test.APIResource{
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("volumesnapshotlocations.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "bar").Result(),
),
test.Backups(
builder.ForBackup("zoo", "raz").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/bar.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "Resource's CRD is not included, when CRD is excluded.",
backup: defaultBackup().IncludedNamespaces("foo").ExcludedResources("customresourcedefinitions.apiextensions.k8s.io").Result(),
apiResources: []*test.APIResource{
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("volumesnapshotlocations.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "bar").Result(),
),
test.Backups(
builder.ForBackup("zoo", "raz").Result(),
),
},
want: []string{
"resources/volumesnapshotlocations.velero.io/namespaces/foo/bar.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/bar.json",
},
},
}
for _, tc := range tests {
@ -774,7 +864,7 @@ func TestBackupResourceFiltering(t *testing.T) {
h.addItems(t, resource)
}
h.backupper.Backup(h.log, req, backupFile, nil, nil)
h.backupper.Backup(h.log, req, backupFile, tc.actions, nil)
assertTarballContents(t, backupFile, append(tc.want, "metadata/version")...)
})
@ -887,7 +977,7 @@ func TestCRDInclusion(t *testing.T) {
},
},
{
name: "include cluster resources=false excludes all CRDs when backing up selected namespaces",
name: "include-cluster-resources=false excludes all CRDs when backing up selected namespaces",
backup: defaultBackup().
IncludeClusterResources(false).
IncludedNamespaces("foo").
@ -899,12 +989,12 @@ func TestCRDInclusion(t *testing.T) {
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "vsl-1").Result(),
builder.ForVolumeSnapshotLocation("foo", "bar").Result(),
),
},
want: []string{
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/bar.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
@ -3179,3 +3269,837 @@ func assertTarballOrdering(t *testing.T, backupFile io.Reader, orderedResources
lastSeen = current
}
}
func TestBackupNewResourceFiltering(t *testing.T) {
tests := []struct {
name string
backup *velerov1.Backup
apiResources []*test.APIResource
want []string
actions []biav2.BackupItemAction
}{
{
name: "no namespaced resources + some cluster resources",
backup: defaultBackup().IncludedClusterScopeResources("persistentvolumes").ExcludedNamespacedResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("testing").Result(),
),
},
want: []string{
"resources/persistentvolumes/cluster/testing.json",
"resources/persistentvolumes/v1-preferredversion/cluster/testing.json",
},
},
{
name: "no namespaced resources + all cluster resources",
backup: defaultBackup().IncludedClusterScopeResources("*").ExcludedNamespacedResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
},
},
{
name: "some namespaced resources + no cluster resources 1",
backup: defaultBackup().ExcludedClusterScopeResources("*").IncludedNamespaces("foo", "zoo").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "some namespaced resources + no cluster resources 2",
backup: defaultBackup().ExcludedClusterScopeResources("*").IncludedNamespacedResources("pods", "deployments").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "some namespaced resources + no cluster resources 3",
backup: defaultBackup().ExcludedClusterScopeResources("*").IncludedNamespaces("foo").IncludedNamespacedResources("pods", "deployments").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "some namespaced resources + no cluster resources 4",
backup: defaultBackup().ExcludedClusterScopeResources("*").ExcludedNamespacedResources("pods").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "some namespaced resources + only related cluster resources 2",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("pods", "persistentvolumeclaims").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Volumes(builder.ForVolume("foo").PersistentVolumeClaimSource("test-1").Result()).Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
},
want: []string{
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + only related cluster resources 3",
backup: defaultBackup().IncludedNamespaces("foo").ExcludedNamespacedResources("deployments").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Volumes(builder.ForVolume("foo").PersistentVolumeClaimSource("test-1").Result()).Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
},
want: []string{
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + some additional cluster resources 1",
backup: defaultBackup().IncludedNamespaces("foo").IncludedClusterScopeResources("customresourcedefinitions").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + some additional cluster resources 2",
backup: defaultBackup().IncludedNamespacedResources("persistentvolumeclaims").IncludedClusterScopeResources("customresourcedefinitions").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + some additional cluster resources 3",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("pods", "persistentvolumeclaims").IncludedClusterScopeResources("customresourcedefinitions").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + some additional cluster resources 4",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("pods", "persistentvolumeclaims").IncludedClusterScopeResources("*").ExcludedClusterScopeResources("customresourcedefinitions.apiextensions.k8s.io").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
actions: []biav2.BackupItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedResources: []string{"persistentvolumeclaims"}},
executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
additionalItems := []velero.ResourceIdentifier{
{GroupResource: kuberesource.PersistentVolumes, Name: "test1"},
}
return item, additionalItems, "", nil, nil
},
},
},
},
{
name: "some namespaced resources + all cluster resources 1",
backup: defaultBackup().IncludedNamespaces("foo").IncludedClusterScopeResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("foo", "test-1").VolumeName("test1").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/persistentvolumeclaims/namespaces/foo/test-1.json",
"resources/persistentvolumeclaims/v1-preferredversion/namespaces/foo/test-1.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "some namespaced resources + all cluster resources 2",
backup: defaultBackup().IncludedNamespacedResources("pods").IncludedClusterScopeResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "some namespaced resources + all cluster resources 3",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("pods").IncludedClusterScopeResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "all namespaced resources + no cluster resources",
backup: defaultBackup().ExcludedClusterScopeResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "all namespaced resources + all cluster resources",
backup: defaultBackup().IncludedClusterScopeResources("*").Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("test1").Result(),
builder.ForPersistentVolume("test2").Result(),
),
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/persistentvolumes/cluster/test1.json",
"resources/persistentvolumes/cluster/test2.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/test2.json",
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "namespace resource should be included even it's not specified in the include list, when IncludedNamespaces has specified value 1",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("Secrets").Result(),
apiResources: []*test.APIResource{
test.Secrets(
builder.ForSecret("foo", "bar").Result(),
builder.ForSecret("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("foo").Result(),
),
test.Namespaces(
builder.ForNamespace("foo").Result(),
),
},
want: []string{
"resources/namespaces/cluster/foo.json",
"resources/namespaces/v1-preferredversion/cluster/foo.json",
"resources/secrets/namespaces/foo/bar.json",
"resources/secrets/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "namespace resource should be included even it's not specified in the include list, when IncludedNamespaces has specified value 2",
backup: defaultBackup().IncludedNamespaces("foo").IncludedClusterScopeResources("persistentvolumes").Result(),
apiResources: []*test.APIResource{
test.Secrets(
builder.ForSecret("foo", "bar").Result(),
builder.ForSecret("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("foo").Result(),
),
test.Namespaces(
builder.ForNamespace("foo").Result(),
),
},
want: []string{
"resources/namespaces/cluster/foo.json",
"resources/namespaces/v1-preferredversion/cluster/foo.json",
"resources/secrets/namespaces/foo/bar.json",
"resources/secrets/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/persistentvolumes/cluster/foo.json",
"resources/persistentvolumes/v1-preferredversion/cluster/foo.json",
},
},
{
name: "namespace resource should be included even it's not specified in the include list, when IncludedNamespaces is asterisk.",
backup: defaultBackup().IncludedNamespaces("*").IncludedClusterScopeResources("persistentvolumes").Result(),
apiResources: []*test.APIResource{
test.Secrets(
builder.ForSecret("foo", "bar").Result(),
builder.ForSecret("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("foo").Result(),
),
test.Namespaces(
builder.ForNamespace("foo").Result(),
builder.ForNamespace("zoo").Result(),
),
},
want: []string{
"resources/namespaces/cluster/foo.json",
"resources/namespaces/v1-preferredversion/cluster/foo.json",
"resources/namespaces/cluster/zoo.json",
"resources/namespaces/v1-preferredversion/cluster/zoo.json",
"resources/secrets/namespaces/foo/bar.json",
"resources/secrets/namespaces/zoo/raz.json",
"resources/secrets/v1-preferredversion/namespaces/foo/bar.json",
"resources/secrets/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/persistentvolumes/cluster/foo.json",
"resources/persistentvolumes/v1-preferredversion/cluster/foo.json",
},
},
{
name: "when all namespace resources are involved, cluster resources should be included too",
backup: defaultBackup().IncludedNamespaces("*").IncludedNamespacedResources("*").Result(),
apiResources: []*test.APIResource{
test.Secrets(
builder.ForSecret("foo", "bar").Result(),
builder.ForSecret("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("foo").Result(),
builder.ForPersistentVolume("bar").Result(),
),
test.Namespaces(
builder.ForNamespace("foo").Result(),
builder.ForNamespace("zoo").Result(),
),
},
want: []string{
"resources/namespaces/cluster/foo.json",
"resources/namespaces/v1-preferredversion/cluster/foo.json",
"resources/namespaces/cluster/zoo.json",
"resources/namespaces/v1-preferredversion/cluster/zoo.json",
"resources/secrets/namespaces/foo/bar.json",
"resources/secrets/namespaces/zoo/raz.json",
"resources/secrets/v1-preferredversion/namespaces/foo/bar.json",
"resources/secrets/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/persistentvolumes/cluster/foo.json",
"resources/persistentvolumes/v1-preferredversion/cluster/foo.json",
"resources/persistentvolumes/cluster/bar.json",
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
},
},
{
name: "IncludedNamespaces is asterisk, but not all namespaced types are include, additional cluster resource should not be included.",
backup: defaultBackup().IncludedNamespaces("*").IncludedNamespacedResources("secrets").Result(),
apiResources: []*test.APIResource{
test.Secrets(
builder.ForSecret("foo", "bar").Result(),
builder.ForSecret("zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("foo").Result(),
builder.ForPersistentVolume("bar").Result(),
),
test.Namespaces(
builder.ForNamespace("foo").Result(),
builder.ForNamespace("zoo").Result(),
),
},
want: []string{
"resources/namespaces/cluster/foo.json",
"resources/namespaces/v1-preferredversion/cluster/foo.json",
"resources/namespaces/cluster/zoo.json",
"resources/namespaces/v1-preferredversion/cluster/zoo.json",
"resources/secrets/namespaces/foo/bar.json",
"resources/secrets/namespaces/zoo/raz.json",
"resources/secrets/v1-preferredversion/namespaces/foo/bar.json",
"resources/secrets/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "Resource's CRD should be included",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("volumesnapshotlocations.velero.io", "backups.velero.io").Result(),
apiResources: []*test.APIResource{
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("volumesnapshotlocations.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "bar").Result(),
),
test.Backups(
builder.ForBackup("zoo", "raz").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/bar.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
name: "Resource's CRD is not included, when CRD is excluded.",
backup: defaultBackup().IncludedNamespaces("foo").IncludedNamespacedResources("volumesnapshotlocations.velero.io", "backups.velero.io").ExcludedClusterScopeResources("customresourcedefinitions.apiextensions.k8s.io").Result(),
apiResources: []*test.APIResource{
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("volumesnapshotlocations.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "bar").Result(),
),
test.Backups(
builder.ForBackup("zoo", "raz").Result(),
),
},
want: []string{
"resources/volumesnapshotlocations.velero.io/namespaces/foo/bar.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/bar.json",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
h = newHarness(t)
req = &Request{Backup: tc.backup}
backupFile = bytes.NewBuffer([]byte{})
)
for _, resource := range tc.apiResources {
h.addItems(t, resource)
}
h.backupper.Backup(h.log, req, backupFile, tc.actions, nil)
assertTarballContents(t, backupFile, append(tc.want, "metadata/version")...)
})
}
}

View File

@ -128,17 +128,24 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
log.Info("Excluding item because namespace is excluded")
return false, itemFiles, nil
}
// NOTE: we specifically allow namespaces to be backed up even if IncludeClusterResources is
// false.
if namespace == "" && groupResource != kuberesource.Namespaces && ib.backupRequest.Spec.IncludeClusterResources != nil && !*ib.backupRequest.Spec.IncludeClusterResources {
log.Info("Excluding item because resource is cluster-scoped and backup.spec.includeClusterResources is false")
// NOTE: we specifically allow namespaces to be backed up even if it's excluded.
// This check is more permissive for cluster resources to let those passed in by
// plugins' additional items to get involved.
// Only expel cluster resource when it's specifically listed in the excluded list here.
if namespace == "" && groupResource != kuberesource.Namespaces &&
ib.backupRequest.ResourceIncludesExcludes.ShouldExclude(groupResource.String()) {
log.Info("Excluding item because resource is cluster-scoped and is excluded by cluster filter.")
return false, itemFiles, nil
}
if !ib.backupRequest.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
// Only check namespace-scoped resource to avoid expelling cluster resources
// are not specified in included list.
if namespace != "" && !ib.backupRequest.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
log.Info("Excluding item because resource is excluded")
return false, itemFiles, nil
}
}
if metadata.GetDeletionTimestamp() != nil {

View File

@ -249,26 +249,6 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
return items, nil
}
// If the resource we are backing up is NOT namespaces, and it is cluster-scoped, check to see if
// we should include it based on the IncludeClusterResources setting.
if gr != kuberesource.Namespaces && clusterScoped {
if r.backupRequest.Spec.IncludeClusterResources == nil {
if !r.backupRequest.NamespaceIncludesExcludes.IncludeEverything() {
// when IncludeClusterResources == nil (auto), only directly
// back up cluster-scoped resources if we're doing a full-cluster
// (all namespaces) backup. Note that in the case of a subset of
// namespaces being backed up, some related cluster-scoped resources
// may still be backed up if triggered by a custom action (e.g. PVC->PV).
// If we're processing namespaces themselves, we will not skip here, they may be
// filtered out later.
log.Info("Skipping resource because it's cluster-scoped and only specific namespaces are included in the backup")
return nil, nil
}
} else if !*r.backupRequest.Spec.IncludeClusterResources {
log.Info("Skipping resource because it's cluster-scoped")
return nil, nil
}
}
if !r.backupRequest.ResourceIncludesExcludes.ShouldInclude(gr.String()) {
log.Infof("Skipping resource because it's excluded")
@ -293,7 +273,7 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
namespacesToList := getNamespacesToList(r.backupRequest.NamespaceIncludesExcludes)
// Check if we're backing up namespaces for a less-than-full backup.
// We enter this block if resource is Namespaces and the namespae list is either empty or contains
// We enter this block if resource is Namespaces and the namespace list is either empty or contains
// an explicit namespace list. (We skip this block if the list contains "" since that indicates
// a full-cluster backup
if gr == kuberesource.Namespaces && (len(namespacesToList) == 0 || namespacesToList[0] != "") {

View File

@ -44,7 +44,7 @@ type Request struct {
StorageLocation *velerov1api.BackupStorageLocation
SnapshotLocations []*velerov1api.VolumeSnapshotLocation
NamespaceIncludesExcludes *collections.IncludesExcludes
ResourceIncludesExcludes *collections.IncludesExcludes
ResourceIncludesExcludes collections.IncludesExcludesInterface
ResourceHooks []hook.ResourceHook
ResolvedActions []framework.BackupItemResolvedActionV2
ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction

View File

@ -150,6 +150,30 @@ func (b *BackupBuilder) ExcludedResources(resources ...string) *BackupBuilder {
return b
}
// IncludedClusterScopeResources sets the Backup's included cluster resources.
func (b *BackupBuilder) IncludedClusterScopeResources(resources ...string) *BackupBuilder {
b.object.Spec.IncludedClusterScopeResources = resources
return b
}
// ExcludedClusterScopeResources sets the Backup's excluded cluster resources.
func (b *BackupBuilder) ExcludedClusterScopeResources(resources ...string) *BackupBuilder {
b.object.Spec.ExcludedClusterScopeResources = resources
return b
}
// IncludedNamespacedResources sets the Backup's included namespaced resources.
func (b *BackupBuilder) IncludedNamespacedResources(resources ...string) *BackupBuilder {
b.object.Spec.IncludedNamespacedResources = resources
return b
}
// ExcludedNamespacedResources sets the Backup's excluded namespaced resources.
func (b *BackupBuilder) ExcludedNamespacedResources(resources ...string) *BackupBuilder {
b.object.Spec.ExcludedNamespacedResources = resources
return b
}
// IncludeClusterResources sets the Backup's "include cluster resources" flag.
func (b *BackupBuilder) IncludeClusterResources(val bool) *BackupBuilder {
b.object.Spec.IncludeClusterResources = &val

View File

@ -82,26 +82,29 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command {
}
type CreateOptions struct {
Name string
TTL time.Duration
SnapshotVolumes flag.OptionalBool
DefaultVolumesToFsBackup flag.OptionalBool
IncludeNamespaces flag.StringArray
ExcludeNamespaces flag.StringArray
IncludeResources flag.StringArray
ExcludeResources flag.StringArray
Labels flag.Map
Selector flag.LabelSelector
IncludeClusterResources flag.OptionalBool
Wait bool
StorageLocation string
SnapshotLocations []string
FromSchedule string
OrderedResources string
CSISnapshotTimeout time.Duration
ItemOperationTimeout time.Duration
client veleroclient.Interface
Name string
TTL time.Duration
SnapshotVolumes flag.OptionalBool
DefaultVolumesToFsBackup flag.OptionalBool
IncludeNamespaces flag.StringArray
ExcludeNamespaces flag.StringArray
IncludeResources flag.StringArray
ExcludeResources flag.StringArray
IncludeClusterScopeResources flag.StringArray
ExcludeClusterScopeResources flag.StringArray
IncludeNamespacedResources flag.StringArray
ExcludeNamespacedResources flag.StringArray
Labels flag.Map
Selector flag.LabelSelector
IncludeClusterResources flag.OptionalBool
Wait bool
StorageLocation string
SnapshotLocations []string
FromSchedule string
OrderedResources string
CSISnapshotTimeout time.Duration
ItemOperationTimeout time.Duration
client veleroclient.Interface
}
func NewCreateOptions() *CreateOptions {
@ -117,8 +120,12 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
flags.DurationVar(&o.TTL, "ttl", o.TTL, "How long before the backup can be garbage collected.")
flags.Var(&o.IncludeNamespaces, "include-namespaces", "Namespaces to include in the backup (use '*' for all namespaces).")
flags.Var(&o.ExcludeNamespaces, "exclude-namespaces", "Namespaces to exclude from the backup.")
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources).")
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io.")
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources). Cannot work with include-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources.")
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io. Cannot work with include-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources.")
flags.Var(&o.IncludeClusterScopeResources, "include-cluster-scope-resources", "Cluster-scoped resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
flags.Var(&o.ExcludeClusterScopeResources, "exclude-cluster-scope-resources", "Cluster-scoped resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
flags.Var(&o.IncludeNamespacedResources, "include-namespaced-resources", "Namespaced resources to include in the backup, formatted as resource.group, such as deployments.apps(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
flags.Var(&o.ExcludeNamespacedResources, "exclude-namespaced-resources", "Namespaced resources to exclude from the backup, formatted as resource.group, such as deployments.apps(use '*' for all resources). Cannot work with include-resources, exclude-resources and include-cluster-resources.")
flags.Var(&o.Labels, "labels", "Labels to apply to the backup.")
flags.StringVar(&o.StorageLocation, "storage-location", "", "Location in which to store the backup.")
flags.StringSliceVar(&o.SnapshotLocations, "volume-snapshot-locations", o.SnapshotLocations, "List of locations (at most one per provider) where volume snapshots should be stored.")
@ -131,7 +138,7 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
// like a normal bool flag
f.NoOptDefVal = "true"
f = flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "Include cluster-scoped resources in the backup")
f = flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "Include cluster-scoped resources in the backup. Cannot work with include-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources.")
f.NoOptDefVal = "true"
f = flags.VarPF(&o.DefaultVolumesToFsBackup, "default-volumes-to-fs-backup", "", "Use pod volume file system backup by default for volumes")
@ -162,7 +169,7 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
// Ensure that unless FromSchedule is set, args contains a backup name
if o.FromSchedule == "" && len(args) != 1 {
return fmt.Errorf("A backup name is required, unless you are creating based on a schedule.")
return fmt.Errorf("a backup name is required, unless you are creating based on a schedule")
}
errs := collections.ValidateNamespaceIncludesExcludes(o.IncludeNamespaces, o.ExcludeNamespaces)
@ -170,6 +177,12 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
return kubeerrs.NewAggregate(errs)
}
if o.oldAndNewFilterParametersUsedTogether() {
return fmt.Errorf("include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n" +
"include-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources are new filter parameters.\n" +
"They cannot be used together")
}
if o.StorageLocation != "" {
location := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
@ -300,13 +313,13 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
func ParseOrderedResources(orderMapStr string) (map[string]string, error) {
entries := strings.Split(orderMapStr, ";")
if len(entries) == 0 {
return nil, fmt.Errorf("Invalid OrderedResources '%s'.", orderMapStr)
return nil, fmt.Errorf("invalid OrderedResources '%s'", orderMapStr)
}
orderedResources := make(map[string]string)
for _, entry := range entries {
kv := strings.Split(entry, "=")
if len(kv) != 2 {
return nil, fmt.Errorf("Invalid OrderedResources '%s'.", entry)
return nil, fmt.Errorf("invalid OrderedResources '%s'", entry)
}
kind := strings.TrimSpace(kv[0])
order := strings.TrimSpace(kv[1])
@ -334,6 +347,10 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
ExcludedNamespaces(o.ExcludeNamespaces...).
IncludedResources(o.IncludeResources...).
ExcludedResources(o.ExcludeResources...).
IncludedClusterScopeResources(o.IncludeClusterScopeResources...).
ExcludedClusterScopeResources(o.ExcludeClusterScopeResources...).
IncludedNamespacedResources(o.IncludeNamespacedResources...).
ExcludedNamespacedResources(o.ExcludeNamespacedResources...).
LabelSelector(o.Selector.LabelSelector).
TTL(o.TTL).
StorageLocation(o.StorageLocation).
@ -362,3 +379,15 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
backup := backupBuilder.ObjectMeta(builder.WithLabelsMap(o.Labels.Data())).Result()
return backup, nil
}
func (o *CreateOptions) oldAndNewFilterParametersUsedTogether() bool {
haveOldResourceFilterParameters := len(o.IncludeResources) > 0 ||
len(o.ExcludeResources) > 0 ||
o.IncludeClusterResources.Value != nil
haveNewResourceFilterParameters := len(o.IncludeClusterScopeResources) > 0 ||
(len(o.ExcludeClusterScopeResources) > 0) ||
(len(o.IncludeNamespacedResources) > 0) ||
(len(o.ExcludeNamespacedResources) > 0)
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
}

View File

@ -133,20 +133,24 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
},
Spec: api.ScheduleSpec{
Template: api.BackupSpec{
IncludedNamespaces: o.BackupOptions.IncludeNamespaces,
ExcludedNamespaces: o.BackupOptions.ExcludeNamespaces,
IncludedResources: o.BackupOptions.IncludeResources,
ExcludedResources: o.BackupOptions.ExcludeResources,
IncludeClusterResources: o.BackupOptions.IncludeClusterResources.Value,
LabelSelector: o.BackupOptions.Selector.LabelSelector,
SnapshotVolumes: o.BackupOptions.SnapshotVolumes.Value,
TTL: metav1.Duration{Duration: o.BackupOptions.TTL},
StorageLocation: o.BackupOptions.StorageLocation,
VolumeSnapshotLocations: o.BackupOptions.SnapshotLocations,
DefaultVolumesToFsBackup: o.BackupOptions.DefaultVolumesToFsBackup.Value,
OrderedResources: orders,
CSISnapshotTimeout: metav1.Duration{Duration: o.BackupOptions.CSISnapshotTimeout},
ItemOperationTimeout: metav1.Duration{Duration: o.BackupOptions.ItemOperationTimeout},
IncludedNamespaces: o.BackupOptions.IncludeNamespaces,
ExcludedNamespaces: o.BackupOptions.ExcludeNamespaces,
IncludedResources: o.BackupOptions.IncludeResources,
ExcludedResources: o.BackupOptions.ExcludeResources,
IncludedClusterScopeResources: o.BackupOptions.IncludeClusterScopeResources,
ExcludedClusterScopeResources: o.BackupOptions.ExcludeClusterScopeResources,
IncludedNamespacedResources: o.BackupOptions.IncludeNamespacedResources,
ExcludedNamespacedResources: o.BackupOptions.ExcludeNamespacedResources,
IncludeClusterResources: o.BackupOptions.IncludeClusterResources.Value,
LabelSelector: o.BackupOptions.Selector.LabelSelector,
SnapshotVolumes: o.BackupOptions.SnapshotVolumes.Value,
TTL: metav1.Duration{Duration: o.BackupOptions.TTL},
StorageLocation: o.BackupOptions.StorageLocation,
VolumeSnapshotLocations: o.BackupOptions.SnapshotLocations,
DefaultVolumesToFsBackup: o.BackupOptions.DefaultVolumesToFsBackup.Value,
OrderedResources: orders,
CSISnapshotTimeout: metav1.Duration{Duration: o.BackupOptions.CSISnapshotTimeout},
ItemOperationTimeout: metav1.Duration{Duration: o.BackupOptions.ItemOperationTimeout},
},
Schedule: o.Schedule,
UseOwnerReferencesInBackup: &o.UseOwnerReferencesInBackup,

View File

@ -36,6 +36,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/features"
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/volume"
)
@ -135,21 +136,48 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) {
d.Printf("\tExcluded:\t%s\n", s)
d.Println()
d.Printf("Resources:\n")
if len(spec.IncludedResources) == 0 {
s = "*"
if collections.UseOldResourceFilters(spec) {
d.Printf("Resources:\n")
if len(spec.IncludedResources) == 0 {
s = "*"
} else {
s = strings.Join(spec.IncludedResources, ", ")
}
d.Printf("\tIncluded:\t%s\n", s)
if len(spec.ExcludedResources) == 0 {
s = emptyDisplay
} else {
s = strings.Join(spec.ExcludedResources, ", ")
}
d.Printf("\tExcluded:\t%s\n", s)
d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(spec.IncludeClusterResources, "excluded", "included", "auto"))
} else {
s = strings.Join(spec.IncludedResources, ", ")
}
d.Printf("\tIncluded:\t%s\n", s)
if len(spec.ExcludedResources) == 0 {
s = emptyDisplay
} else {
s = strings.Join(spec.ExcludedResources, ", ")
}
d.Printf("\tExcluded:\t%s\n", s)
if len(spec.IncludedClusterScopeResources) == 0 {
s = emptyDisplay
} else {
s = strings.Join(spec.IncludedClusterScopeResources, ", ")
}
d.Printf("\tIncluded cluster-scoped:\t%s\n", s)
if len(spec.ExcludedClusterScopeResources) == 0 {
s = emptyDisplay
} else {
s = strings.Join(spec.ExcludedClusterScopeResources, ", ")
}
d.Printf("\tExcluded cluster-scoped:\t%s\n", s)
d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(spec.IncludeClusterResources, "excluded", "included", "auto"))
if len(spec.IncludedNamespacedResources) == 0 {
s = "*"
} else {
s = strings.Join(spec.IncludedNamespacedResources, ", ")
}
d.Printf("\tIncluded namespaced:\t%s\n", s)
if len(spec.ExcludedNamespacedResources) == 0 {
s = emptyDisplay
} else {
s = strings.Join(spec.ExcludedNamespacedResources, ", ")
}
d.Printf("\tExcluded namespaced:\t%s\n", s)
}
d.Println()
s = emptyDisplay

View File

@ -426,11 +426,30 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("error getting namespace list: %v", err))
}
// validate whether Included/Excluded resources and IncludedClusterResource are mixed with
// Included/Excluded cluster-scoped/namespaced resources.
if oldAndNewFilterParametersUsedTogether(request.Spec) {
validatedError := fmt.Sprintf("include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n" +
"include-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources are new filter parameters.\n" +
"They cannot be used together")
request.Status.ValidationErrors = append(request.Status.ValidationErrors, validatedError)
}
// validate the included/excluded resources
for _, err := range collections.ValidateIncludesExcludes(request.Spec.IncludedResources, request.Spec.ExcludedResources) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded resource lists: %v", err))
}
// validate the cluster-scoped included/excluded resources
for _, err := range collections.ValidateScopedIncludesExcludes(request.Spec.IncludedClusterScopeResources, request.Spec.ExcludedClusterScopeResources) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid cluster-scoped included/excluded resource lists: %s", err))
}
// validate the namespaced included/excluded resources
for _, err := range collections.ValidateScopedIncludesExcludes(request.Spec.IncludedNamespacedResources, request.Spec.ExcludedNamespacedResources) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid namespaced included/excluded resource lists: %s", err))
}
// validate the included/excluded namespaces
for _, err := range collections.ValidateNamespaceIncludesExcludes(request.Spec.IncludedNamespaces, request.Spec.ExcludedNamespaces) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
@ -1082,3 +1101,15 @@ func (b *backupReconciler) recreateVolumeSnapshotContent(vsc snapshotv1api.Volum
return nil
}
func oldAndNewFilterParametersUsedTogether(backupSpec velerov1api.BackupSpec) bool {
haveOldResourceFilterParameters := len(backupSpec.IncludedResources) > 0 ||
(len(backupSpec.ExcludedResources) > 0) ||
(backupSpec.IncludeClusterResources != nil)
haveNewResourceFilterParameters := len(backupSpec.IncludedClusterScopeResources) > 0 ||
(len(backupSpec.ExcludedClusterScopeResources) > 0) ||
(len(backupSpec.IncludedNamespacedResources) > 0) ||
(len(backupSpec.ExcludedNamespacedResources) > 0)
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
}

View File

@ -181,6 +181,12 @@ func TestProcessBackupValidationFailures(t *testing.T) {
backupLocation: defaultBackupLocation,
expectedErrs: []string{"encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified"},
},
{
name: "use old filter parameters and new filter parameters together",
backup: defaultBackup().IncludeClusterResources(true).IncludedNamespacedResources("Deployment").IncludedNamespaces("default").Result(),
backupLocation: defaultBackupLocation,
expectedErrs: []string{"include-resources, exclude-resources and include-cluster-resources are old filter parameters.\ninclude-cluster-scope-resources, exclude-cluster-scope-resources, include-namespaced-resources and exclude-namespaced-resources are new filter parameters.\nThey cannot be used together"},
},
}
for _, test := range tests {

View File

@ -56,6 +56,7 @@ func NewAPIServer(t *testing.T) *APIServer {
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentsList",
{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"}: "CRDList",
{Group: "velero.io", Version: "v1", Resource: "volumesnapshotlocations"}: "VSLList",
{Group: "velero.io", Version: "v1", Resource: "backups"}: "BackupList",
{Group: "extensions", Version: "v1", Resource: "deployments"}: "ExtDeploymentsList",
{Group: "velero.io", Version: "v1", Resource: "deployments"}: "VeleroDeploymentsList",
})

View File

@ -163,6 +163,16 @@ func VSLs(items ...metav1.Object) *APIResource {
}
}
func Backups(items ...metav1.Object) *APIResource {
return &APIResource{
Group: "velero.io",
Version: "v1",
Name: "backups",
Namespaced: true,
Items: items,
}
}
func Services(items ...metav1.Object) *APIResource {
return &APIResource{
Group: "",

View File

@ -21,11 +21,15 @@ import (
"github.com/gobwas/glob"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
)
type globStringSet struct {
@ -101,6 +105,160 @@ func (ie *IncludesExcludes) ShouldInclude(s string) bool {
return ie.includes.Len() == 0 || ie.includes.Has("*") || ie.includes.match(s)
}
// IncludesExcludesInterface is used as polymorphic IncludesExcludes for Global and scope
// resources Include/Exclude.
type IncludesExcludesInterface interface {
// Check whether the type name passed in by parameter should be included.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldInclude(typeName string) bool
// Check whether the type name passed in by parameter should be excluded.
// typeName should be k8s.io/apimachinery/pkg/runtime/schema GroupResource's String() result.
ShouldExclude(typeName string) bool
}
type GlobalIncludesExcludes struct {
resourceFilter IncludesExcludes
includeClusterResources *bool
namespaceFilter IncludesExcludes
helper discovery.Helper
logger logrus.FieldLogger
}
// ShouldInclude returns whether the specified item should be
// included or not. Everything in the includes list except those
// items in the excludes list should be included.
// It has some exceptional cases. When IncludeClusterResources is set to false,
// no need to check the filter, all cluster resources are excluded.
func (ie *GlobalIncludesExcludes) ShouldInclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return false
}
if resource.Namespaced == false && boolptr.IsSetToFalse(ie.includeClusterResources) {
ie.logger.Info("Skipping resource %s, because it's cluster-scoped, and IncludeClusterResources is set to false.", typeName)
return false
}
// when IncludeClusterResources == nil (auto), only directly
// back up cluster-scoped resources if we're doing a full-cluster
// (all namespaces and all namespace scope types) backup. Note that in the case of a subset of
// namespaces being backed up, some related cluster-scoped resources
// may still be backed up if triggered by a custom action (e.g. PVC->PV).
// If we're processing namespaces themselves, we will not skip here, they may be
// filtered out later.
if typeName != kuberesource.Namespaces.String() && resource.Namespaced == false &&
ie.includeClusterResources == nil && !ie.namespaceFilter.IncludeEverything() {
ie.logger.Infof("Skipping resource %s, because it's cluster-scoped and only specific namespaces or namespace scope types are included in the backup.", typeName)
return false
}
return ie.resourceFilter.ShouldInclude(typeName)
}
// ShouldExclude returns whether the resource type should be excluded or not.
func (ie *GlobalIncludesExcludes) ShouldExclude(typeName string) bool {
// if the type name is specified in excluded list, it's excluded.
if ie.resourceFilter.excludes.match(typeName) {
return true
}
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return true
}
// the resource type is cluster scope
if !resource.Namespaced {
// if includeClusterResources is set to false, cluster resource should be excluded.
if boolptr.IsSetToFalse(ie.includeClusterResources) {
return true
}
// if includeClusterResources is set to nil, check whether it's included by resource
// filter.
if ie.includeClusterResources == nil && !ie.resourceFilter.ShouldInclude(typeName) {
return true
}
}
return false
}
type ScopeIncludesExcludes struct {
namespaceResourceFilter IncludesExcludes // namespace scope resource filter
clusterResourceFilter IncludesExcludes // cluster scope resource filter
namespaceFilter IncludesExcludes // namespace filter
helper discovery.Helper
logger logrus.FieldLogger
}
// ShouldInclude returns whether the specified resource should be included or not.
// The function will check whether the resource is namespaced resource first.
// For namespaced resource, except resources listed in excludes, other things should be included.
// For cluster resource, except resources listed in excludes, only include the resource specified by the included.
// It also has some exceptional checks. For namespace, as long as it's not excluded, it is involved.
// If all namespace resources are included, all cluster resource are returned to get a full backup.
func (ie *ScopeIncludesExcludes) ShouldInclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return false
}
if resource.Namespaced {
if ie.namespaceResourceFilter.excludes.Has("*") || ie.namespaceResourceFilter.excludes.match(typeName) {
return false
}
// len=0 means include everything
return ie.namespaceResourceFilter.includes.Len() == 0 || ie.namespaceResourceFilter.includes.Has("*") || ie.namespaceResourceFilter.includes.match(typeName)
}
if ie.clusterResourceFilter.excludes.Has("*") || ie.clusterResourceFilter.excludes.match(typeName) {
return false
}
// when IncludedClusterScopeResources and ExcludedClusterScopeResources are not specified,
// only directly back up cluster-scoped resources if we're doing a full-cluster
// (all namespaces and all namespace scope types) backup.
if len(ie.clusterResourceFilter.includes.List()) == 0 &&
len(ie.clusterResourceFilter.excludes.List()) == 0 &&
ie.namespaceFilter.IncludeEverything() &&
ie.namespaceResourceFilter.IncludeEverything() {
return true
}
// Also include namespace resource by default.
return ie.clusterResourceFilter.includes.Has("*") || ie.clusterResourceFilter.includes.match(typeName) || typeName == kuberesource.Namespaces.String()
}
// ShouldExclude returns whether the resource type should be excluded or not.
// For ScopeIncludesExcludes, if the resource type is specified in the exclude
// list, it should be excluded.
func (ie *ScopeIncludesExcludes) ShouldExclude(typeName string) bool {
_, resource, err := ie.helper.ResourceFor(schema.ParseGroupResource(typeName).WithVersion(""))
if err != nil {
ie.logger.Errorf("fail to get resource %s. %s", typeName, err.Error())
return true
}
if resource.Namespaced {
if ie.namespaceResourceFilter.excludes.match(typeName) {
return true
}
} else {
if ie.clusterResourceFilter.excludes.match(typeName) {
return true
}
}
return false
}
// IncludesString returns a string containing all of the includes, separated by commas, or * if the
// list is empty.
func (ie *IncludesExcludes) IncludesString() string {
@ -126,6 +284,24 @@ func (ie *IncludesExcludes) IncludeEverything() bool {
return ie.excludes.Len() == 0 && (ie.includes.Len() == 0 || (ie.includes.Len() == 1 && ie.includes.Has("*")))
}
func newScopeIncludesExcludes(nsIncludesExcludes IncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes {
ret := &ScopeIncludesExcludes{
namespaceResourceFilter: IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
},
clusterResourceFilter: IncludesExcludes{
includes: newGlobStringSet(),
excludes: newGlobStringSet(),
},
namespaceFilter: nsIncludesExcludes,
helper: helper,
logger: logger,
}
return ret
}
// ValidateIncludesExcludes checks provided lists of included and excluded
// items to ensure they are a valid set of IncludesExcludes data.
func ValidateIncludesExcludes(includesList, excludesList []string) []error {
@ -177,6 +353,35 @@ func ValidateNamespaceIncludesExcludes(includesList, excludesList []string) []er
return errs
}
// ValidateScopedIncludesExcludes checks provided lists of namespaced or cluster-scoped
// included and excluded items to ensure they are a valid set of IncludesExcludes data.
func ValidateScopedIncludesExcludes(includesList, excludesList []string) []error {
var errs []error
includes := sets.NewString(includesList...)
excludes := sets.NewString(excludesList...)
if includes.Len() > 1 && includes.Has("*") {
errs = append(errs, errors.New("includes list must either contain '*' only, or a non-empty list of items"))
}
if excludes.Len() > 1 && excludes.Has("*") {
errs = append(errs, errors.New("excludes list must either contain '*' only, or a non-empty list of items"))
}
if includes.Len() > 0 && excludes.Has("*") {
errs = append(errs, errors.New("when exclude is '*', include cannot have value"))
}
for _, itm := range excludes.List() {
if includes.Has(itm) {
errs = append(errs, errors.Errorf("excludes list cannot contain an item in the includes list: %v", itm))
}
}
return errs
}
func validateNamespaceName(ns string) []error {
var errs []error
@ -200,11 +405,11 @@ func validateNamespaceName(ns string) []error {
return errs
}
// GenerateIncludesExcludes constructs an IncludesExcludes struct by taking the provided
// generateIncludesExcludes constructs an IncludesExcludes struct by taking the provided
// include/exclude slices, applying the specified mapping function to each item in them,
// and adding the output of the function to the new struct. If the mapping function returns
// an empty string for an item, it is omitted from the result.
func GenerateIncludesExcludes(includes, excludes []string, mapFunc func(string) string) *IncludesExcludes {
func generateIncludesExcludes(includes, excludes []string, mapFunc func(string) string) *IncludesExcludes {
res := NewIncludesExcludes()
for _, item := range includes {
@ -237,11 +442,39 @@ func GenerateIncludesExcludes(includes, excludes []string, mapFunc func(string)
return res
}
// generateScopedIncludesExcludes's function is similar with generateIncludesExcludes,
// but it's used for scoped Includes/Excludes.
func generateScopedIncludesExcludes(namespacedIncludes, namespacedExcludes, clusterIncludes, clusterExcludes []string, mapFunc func(string, bool) string, nsIncludesExcludes IncludesExcludes, helper discovery.Helper, logger logrus.FieldLogger) *ScopeIncludesExcludes {
res := newScopeIncludesExcludes(nsIncludesExcludes, helper, logger)
generateFilter(res.namespaceResourceFilter.includes, namespacedIncludes, mapFunc, true)
generateFilter(res.namespaceResourceFilter.excludes, namespacedExcludes, mapFunc, true)
generateFilter(res.clusterResourceFilter.includes, clusterIncludes, mapFunc, false)
generateFilter(res.clusterResourceFilter.excludes, clusterExcludes, mapFunc, false)
return res
}
func generateFilter(filter globStringSet, resources []string, mapFunc func(string, bool) string, namespaced bool) {
for _, item := range resources {
if item == "*" {
filter.Insert(item)
continue
}
key := mapFunc(item, namespaced)
if key == "" {
continue
}
filter.Insert(key)
}
}
// 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(
resources := generateIncludesExcludes(
includes,
excludes,
func(item string) string {
@ -260,3 +493,74 @@ func GetResourceIncludesExcludes(helper discovery.Helper, includes, excludes []s
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's function is similar with GetResourceIncludesExcludes,
// but it's used for scoped Includes/Excludes, and can handle both cluster and namespace 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 scope resources: %s", ret.namespaceResourceFilter.IncludesString())
logger.Infof("Excluding namespace scope resources: %s", ret.namespaceResourceFilter.ExcludesString())
logger.Infof("Including cluster scope resources: %s", ret.clusterResourceFilter.GetIncludes())
logger.Infof("Excluding cluster scope resources: %s", ret.clusterResourceFilter.ExcludesString())
return ret
}
// UseOldResourceFilters checks whether to use old resource filters (IncludeClusterResources,
// IncludedResources and ExcludedResources), depending the backup's filters setting.
// New filters are IncludeClusterScopedResources, ExcludeClusterScopedResources,
// IncludeNamespacedResources and ExcludeNamespacedResources.
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.IncludedClusterScopeResources) == 0 &&
len(backupSpec.ExcludedClusterScopeResources) == 0 &&
len(backupSpec.IncludedNamespacedResources) == 0 &&
len(backupSpec.ExcludedNamespacedResources) == 0 {
return true
}
if backupSpec.IncludeClusterResources != nil ||
len(backupSpec.IncludedResources) > 0 ||
len(backupSpec.ExcludedResources) > 0 {
return true
}
return false
}

View File

@ -20,8 +20,15 @@ import (
"testing"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/test"
)
func TestShouldInclude(t *testing.T) {
@ -295,3 +302,626 @@ func TestValidateNamespaceIncludesExcludes(t *testing.T) {
})
}
}
func TestValidateScopedIncludesExcludes(t *testing.T) {
tests := []struct {
name string
includes []string
excludes []string
wantErr []error
}{
// includes testing
{
name: "empty includes is valid",
includes: []string{},
wantErr: []error{},
},
{
name: "asterisk includes is valid",
includes: []string{"*"},
wantErr: []error{},
},
{
name: "include everything not allowed with other includes",
includes: []string{"*", "foo"},
wantErr: []error{errors.New("includes list must either contain '*' only, or a non-empty list of items")},
},
// excludes testing
{
name: "empty excludes is valid",
excludes: []string{},
wantErr: []error{},
},
{
name: "asterisk excludes is valid",
excludes: []string{"*"},
wantErr: []error{},
},
{
name: "exclude everything not allowed with other excludes",
excludes: []string{"*", "foo"},
wantErr: []error{errors.New("excludes list must either contain '*' only, or a non-empty list of items")},
},
// includes and excludes combination testing
{
name: "asterisk excludes doesn't work with non-empty includes",
includes: []string{"foo"},
excludes: []string{"*"},
wantErr: []error{errors.New("when exclude is '*', include cannot have value")},
},
{
name: "excludes cannot contain items in includes",
includes: []string{"foo", "bar"},
excludes: []string{"bar"},
wantErr: []error{errors.New("excludes list cannot contain an item in the includes list: bar")},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
errs := ValidateScopedIncludesExcludes(tc.includes, tc.excludes)
require.Equal(t, len(tc.wantErr), len(errs))
for i := 0; i < len(tc.wantErr); i++ {
assert.Equal(t, tc.wantErr[i].Error(), errs[i].Error())
}
})
}
}
func TestNamespaceScopeShouldInclude(t *testing.T) {
tests := []struct {
name string
namespaceIncludes []string
namespaceExcludes []string
item string
want bool
apiResources []*test.APIResource
}{
{
name: "empty string should include every item",
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "include * should include every item",
namespaceIncludes: []string{"*"},
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "item in includes list should include item",
namespaceIncludes: []string{"foo", "bar", "pods"},
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "item not in includes list should not include item",
namespaceIncludes: []string{"foo", "baz"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "include *, excluded item should not include item",
namespaceIncludes: []string{"*"},
namespaceExcludes: []string{"pods"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "include *, exclude foo, bar should be included",
namespaceIncludes: []string{"*"},
namespaceExcludes: []string{"foo"},
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "an item both included and excluded should not be included",
namespaceIncludes: []string{"pods"},
namespaceExcludes: []string{"pods"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "wildcard should include item",
namespaceIncludes: []string{"*s"},
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "wildcard mismatch should not include item",
namespaceIncludes: []string{"*.bar"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "exclude * should include nothing",
namespaceExcludes: []string{"*"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "wildcard exclude should not include item",
namespaceIncludes: []string{"*"},
namespaceExcludes: []string{"*s"},
item: "pods",
want: false,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "wildcard exclude mismatch should include item",
namespaceExcludes: []string{"*.bar"},
item: "pods",
want: true,
apiResources: []*test.APIResource{
test.Pods(),
},
},
{
name: "resource cannot be found by discovery client should not be include",
item: "pods",
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
discoveryHelper := setupDiscoveryClientWithResources(tc.apiResources)
logger := logrus.StandardLogger()
scopeIncludesExcludes := GetScopeResourceIncludesExcludes(discoveryHelper, logger, tc.namespaceIncludes, tc.namespaceExcludes, []string{}, []string{}, *NewIncludesExcludes())
if got := scopeIncludesExcludes.ShouldInclude((tc.item)); got != tc.want {
t.Errorf("want %t, got %t", tc.want, got)
}
})
}
}
func TestClusterScopedShouldInclude(t *testing.T) {
tests := []struct {
name string
clusterIncludes []string
clusterExcludes []string
nsIncludes []string
item string
want bool
apiResources []*test.APIResource
}{
{
name: "empty string should include nothing",
nsIncludes: []string{"default"},
item: "persistentvolumes",
want: false,
apiResources: []*test.APIResource{
test.PVs(),
},
},
{
name: "include * should include every item",
clusterIncludes: []string{"*"},
item: "persistentvolumes",
want: true,
apiResources: []*test.APIResource{
test.PVs(),
},
},
{
name: "item in includes list should include item",
clusterIncludes: []string{"namespaces", "bar", "baz"},
item: "namespaces",
want: true,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "item not in includes list should not include item",
clusterIncludes: []string{"foo", "baz"},
nsIncludes: []string{"default"},
item: "persistentvolumes",
want: false,
apiResources: []*test.APIResource{
test.PVs(),
},
},
{
name: "include *, excluded item should not include item",
clusterIncludes: []string{"*"},
clusterExcludes: []string{"namespaces"},
item: "namespaces",
want: false,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "include *, exclude foo, bar should be included",
clusterIncludes: []string{"*"},
clusterExcludes: []string{"foo"},
item: "namespaces",
want: true,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "an item both included and excluded should not be included",
clusterIncludes: []string{"namespaces"},
clusterExcludes: []string{"namespaces"},
item: "namespaces",
want: false,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "wildcard should include item",
clusterIncludes: []string{"*spaces"},
item: "namespaces",
want: true,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "wildcard mismatch should not include item",
clusterIncludes: []string{"*.bar"},
nsIncludes: []string{"default"},
item: "persistentvolumes",
want: false,
apiResources: []*test.APIResource{
test.PVs(),
},
},
{
name: "exclude * should include nothing",
clusterExcludes: []string{"*"},
item: "namespaces",
want: false,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "wildcard exclude should not include item",
clusterIncludes: []string{"*"},
clusterExcludes: []string{"*spaces"},
item: "namespaces",
want: false,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "wildcard exclude mismatch should not include item",
clusterExcludes: []string{"*spaces"},
item: "namespaces",
want: false,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "resource cannot be found by discovery client should not be include",
item: "namespaces",
want: false,
},
{
name: "even namespaces is not in the include list, it should also be involved.",
clusterIncludes: []string{"foo", "baz"},
item: "namespaces",
want: true,
apiResources: []*test.APIResource{
test.Namespaces(),
},
},
{
name: "When all namespaces and namespace scope resources are included, cluster resource should be included.",
clusterIncludes: []string{},
nsIncludes: []string{"*"},
item: "persistentvolumes",
want: true,
apiResources: []*test.APIResource{
test.PVs(),
},
},
{
name: "When all namespaces and namespace scope resources are included, but cluster resource is excluded.",
clusterIncludes: []string{},
clusterExcludes: []string{"persistentvolumes"},
nsIncludes: []string{"*"},
item: "persistentvolumes",
want: false,
apiResources: []*test.APIResource{
test.PVs(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
discoveryHelper := setupDiscoveryClientWithResources(tc.apiResources)
logger := logrus.StandardLogger()
nsIncludeExclude := NewIncludesExcludes().Includes(tc.nsIncludes...)
scopeIncludesExcludes := GetScopeResourceIncludesExcludes(discoveryHelper, logger, []string{}, []string{}, tc.clusterIncludes, tc.clusterExcludes, *nsIncludeExclude)
if got := scopeIncludesExcludes.ShouldInclude((tc.item)); got != tc.want {
t.Errorf("want %t, got %t", tc.want, got)
}
})
}
}
func TestGetScopedResourceIncludesExcludes(t *testing.T) {
tests := []struct {
name string
namespaceIncludes []string
namespaceExcludes []string
clusterIncludes []string
clusterExcludes []string
expectedNamespaceIncludes []string
expectedNamespaceExcludes []string
expectedClusterIncludes []string
expectedClusterExcludes []string
apiResources []*test.APIResource
}{
{
name: "only include namespace resources in IncludesExcludes, when namespaced is set to true",
namespaceIncludes: []string{"deployments.apps", "persistentvolumes"},
namespaceExcludes: []string{"pods", "persistentvolumes"},
expectedNamespaceIncludes: []string{"deployments.apps"},
expectedNamespaceExcludes: []string{"pods"},
expectedClusterIncludes: []string{},
expectedClusterExcludes: []string{},
apiResources: []*test.APIResource{
test.Deployments(),
test.PVs(),
test.Pods(),
},
},
{
name: "only include cluster-scoped resources in IncludesExcludes, when namespaced is set to false",
clusterIncludes: []string{"deployments.apps", "persistentvolumes"},
clusterExcludes: []string{"pods", "persistentvolumes"},
expectedNamespaceIncludes: []string{},
expectedNamespaceExcludes: []string{},
expectedClusterIncludes: []string{"persistentvolumes"},
expectedClusterExcludes: []string{"persistentvolumes"},
apiResources: []*test.APIResource{
test.Deployments(),
test.PVs(),
test.Pods(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
logger := logrus.StandardLogger()
nsIncludeExclude := NewIncludesExcludes()
resources := GetScopeResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, tc.namespaceIncludes, tc.namespaceExcludes, tc.clusterIncludes, tc.clusterExcludes, *nsIncludeExclude)
assert.Equal(t, tc.expectedNamespaceIncludes, resources.namespaceResourceFilter.includes.List())
assert.Equal(t, tc.expectedNamespaceExcludes, resources.namespaceResourceFilter.excludes.List())
assert.Equal(t, tc.expectedClusterIncludes, resources.clusterResourceFilter.includes.List())
assert.Equal(t, tc.expectedClusterExcludes, resources.clusterResourceFilter.excludes.List())
})
}
}
func TestUseOldResourceFilters(t *testing.T) {
tests := []struct {
name string
backup velerov1api.Backup
useOldResourceFilters bool
}{
{
name: "backup with no filters should use old filters",
backup: *defaultBackup().Result(),
useOldResourceFilters: true,
},
{
name: "backup with only old filters should use old filters",
backup: *defaultBackup().IncludeClusterResources(true).Result(),
useOldResourceFilters: true,
},
{
name: "backup with only new filters should use new filters",
backup: *defaultBackup().IncludedClusterScopeResources("StorageClass").Result(),
useOldResourceFilters: false,
},
{
// This case should not happen in Velero workflow, because filter validation not old and new
// filters used together. So this is only used for UT checking, and I assume old filters
// have higher priority, because old parameter should be the default one.
name: "backup with both old and new filters should use old filters",
backup: *defaultBackup().IncludeClusterResources(true).IncludedClusterScopeResources("StorageClass").Result(),
useOldResourceFilters: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.useOldResourceFilters, UseOldResourceFilters(test.backup.Spec))
})
}
}
func defaultBackup() *builder.BackupBuilder {
return builder.ForBackup(velerov1api.DefaultNamespace, "backup-1").DefaultVolumesToFsBackup(false)
}
func TestShouldExcluded(t *testing.T) {
falseBoolean := false
trueBoolean := true
tests := []struct {
name string
clusterIncludes []string
clusterExcludes []string
includeClusterResources *bool
filterType string
resourceName string
apiResources []*test.APIResource
resourceIsExcluded bool
}{
{
name: "GlobalResourceIncludesExcludes: filters are all default",
clusterIncludes: []string{},
clusterExcludes: []string{},
includeClusterResources: nil,
filterType: "global",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
{
name: "GlobalResourceIncludesExcludes: IncludeClusterResources is set to true",
clusterIncludes: []string{},
clusterExcludes: []string{},
includeClusterResources: &trueBoolean,
filterType: "global",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
{
name: "GlobalResourceIncludesExcludes: IncludeClusterResources is set to false",
clusterIncludes: []string{"persistentvolumes"},
clusterExcludes: []string{},
includeClusterResources: &falseBoolean,
filterType: "global",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: true,
},
{
name: "GlobalResourceIncludesExcludes: resource is in the include list",
clusterIncludes: []string{"persistentvolumes"},
clusterExcludes: []string{},
includeClusterResources: nil,
filterType: "global",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
{
name: "ScopeResourceIncludesExcludes: resource is in the include list",
clusterIncludes: []string{"persistentvolumes"},
clusterExcludes: []string{},
filterType: "scope",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
{
name: "ScopeResourceIncludesExcludes: filters are all default",
clusterIncludes: []string{},
clusterExcludes: []string{},
filterType: "scope",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
{
name: "ScopeResourceIncludesExcludes: resource is not in the exclude list",
clusterIncludes: []string{},
clusterExcludes: []string{"namespaces"},
filterType: "scope",
resourceName: "persistentvolumes",
apiResources: []*test.APIResource{
test.PVs(),
},
resourceIsExcluded: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
logger := logrus.StandardLogger()
var ie IncludesExcludesInterface
if tc.filterType == "global" {
ie = GetGlobalResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, tc.clusterIncludes, tc.clusterExcludes, tc.includeClusterResources, *NewIncludesExcludes())
} else if tc.filterType == "scope" {
ie = GetScopeResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, []string{}, []string{}, tc.clusterIncludes, tc.clusterExcludes, *NewIncludesExcludes())
}
assert.Equal(t, tc.resourceIsExcluded, ie.ShouldExclude(tc.resourceName))
})
}
}
func setupDiscoveryClientWithResources(APIResources []*test.APIResource) *test.FakeDiscoveryHelper {
resourcesMap := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
resourceList := make([]*metav1.APIResourceList, 0)
for _, resource := range APIResources {
gvr := schema.GroupVersionResource{
Group: resource.Group,
Version: resource.Version,
Resource: resource.Name,
}
resourcesMap[gvr] = gvr
resourceList = append(resourceList,
&metav1.APIResourceList{
GroupVersion: gvr.GroupVersion().String(),
APIResources: []metav1.APIResource{
{
Name: resource.Name,
Kind: resource.Name,
Namespaced: resource.Namespaced,
},
},
},
)
}
discoveryHelper := test.NewFakeDiscoveryHelper(false, resourcesMap)
discoveryHelper.ResourceList = resourceList
return discoveryHelper
}

View File

@ -68,6 +68,26 @@ spec:
# PersistentVolumeClaim is included in the backup, its associated PersistentVolume (which is
# cluster-scoped) would also be backed up.
includeClusterResources: null
# Array of cluster-scoped resources to exclude from the backup. Resources may be shortcuts
# (for example 'sc' for 'storageclasses'), or fully-qualified. If unspecified,
# no additional cluster-scoped resources are excluded. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
excludedClusterScopeResources: {}
# Array of cluster-scoped resources to include from the backup. Resources may be shortcuts
# (for example 'sc' for 'storageclasses'), or fully-qualified. If unspecified,
# no additional cluster-scoped resources are included. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
includedClusterScopeResources: {}
# Array of namespace resources to exclude from the backup. Resources may be shortcuts
# (for example 'cm' for 'configmaps'), or fully-qualified. If unspecified,
# no namespace resources are excluded. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
excludedNamespacedResources: {}
# Array of namespace resources to include from the backup. Resources may be shortcuts
# (for example 'cm' for 'configmaps'), or fully-qualified. If unspecified,
# all namespace resources are included. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
includedNamespacedResources: {}
# Individual objects must match this label selector to be included in the backup. Optional.
labelSelector:
matchLabels:

View File

@ -69,6 +69,26 @@ spec:
# PersistentVolumeClaim is included in the backup, its associated PersistentVolume (which is
# cluster-scoped) would also be backed up.
includeClusterResources: null
# Array of cluster-scoped resources to exclude from the backup. Resources may be shortcuts
# (for example 'sc' for 'storageclasses'), or fully-qualified. If unspecified,
# no additional cluster-scoped resources are excluded. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
excludedClusterScopeResources: {}
# Array of cluster-scoped resources to include from the backup. Resources may be shortcuts
# (for example 'sc' for 'storageclasses'), or fully-qualified. If unspecified,
# no additional cluster-scoped resources are included. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
includedClusterScopeResources: {}
# Array of namespace resources to exclude from the backup. Resources may be shortcuts
# (for example 'cm' for 'configmaps'), or fully-qualified. If unspecified,
# no namespace resources are excluded. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
excludedNamespacedResources: {}
# Array of namespace resources to include from the backup. Resources may be shortcuts
# (for example 'cm' for 'configmaps'), or fully-qualified. If unspecified,
# all namespace resources are included. Optional.
# Cannot work with include-resources, exclude-resources and include-cluster-resources.
includedNamespacedResources: {}
# Individual objects must match this label selector to be included in the scheduled backup. Optional.
labelSelector:
matchLabels:

View File

@ -90,3 +90,9 @@ The following are test cases that are not currently performed as part of a Veler
- `--exclude-namespaces`
- `--exclude-resources`
- `velero.io/exclude-from-backup=true` label
- Since v1.11, new resource filters are added. The new filters only work for backup, and cannot work with old filters (`--include-resources`, `--exclude-resources` and `--include-cluster-resources`). Need to verify backups correctly apply the following new resource filters:
- `--exclude-cluster-scope-resources`
- `--include-cluster-scope-resources`
- `--exclude-namespaced-resources`
- `--include-namespaced-resources`

View File

@ -31,7 +31,7 @@ Namespaces to include. Default is `*`, all namespaces.
### --include-resources
Kubernetes resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use `*` for all resources).
Kubernetes resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use `*` for all resources). Cannot work with `--include-cluster-scope-resources`, `--exclude-cluster-scope-resources`, `--include-namespaced-resources` and `--exclude-namespaced-resources`.
* Backup all deployments in the cluster.
@ -53,7 +53,7 @@ Kubernetes resources to include in the backup, formatted as resource.group, such
### --include-cluster-resources
Includes cluster-scoped resources. This option can have three possible values:
Includes cluster-scoped resources. Cannot work with `--include-cluster-scope-resources`, `--exclude-cluster-scope-resources`, `--include-namespaced-resources` and `--exclude-namespaced-resources`. This option can have three possible values:
* `true`: all cluster-scoped resources are included.
@ -99,6 +99,36 @@ Includes cluster-scoped resources. This option can have three possible values:
For more information read the [Kubernetes label selector documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)
### --include-cluster-scope-resources
Kubernetes cluster-scoped resources to include in the backup, formatted as resource.group, such as `storageclasses.storage.k8s.io`(use '*' for all resources). Cannot work with `--include-resources`, `--exclude-resources` and `--include-cluster-resources`. This parameter only works for backup, not for restore.
* Backup all StorageClasses and ClusterRoles in the cluster.
```bash
velero backup create <backup-name> --include-cluster-scope-resources="storageclasses,clusterroles"
```
* Backup all cluster-scoped resources in the cluster.
```bash
velero backup create <backup-name> --include-cluster-scope-resources="*"
```
### --include-namespaced-resources
Kubernetes namespace resources to include in the backup, formatted as resource.group, such as `deployments.apps`(use '*' for all resources). Cannot work with `--include-resources`, `--exclude-resources` and `--include-cluster-resources`. This parameter only works for backup, not for restore.
* Backup all Deployments and ConfigMaps in the cluster.
```bash
velero backup create <backup-name> --include-namespaced-resources="deployments.apps,configmaps"
```
* Backup all namespace resources in the cluster.
```bash
velero backup create <backup-name> --include-namespaced-resources="*"
```
## Excludes
@ -124,7 +154,7 @@ Namespaces to exclude.
### --exclude-resources
Kubernetes resources to exclude, formatted as resource.group, such as storageclasses.storage.k8s.io.
Kubernetes resources to exclude, formatted as resource.group, such as storageclasses.storage.k8s.io. Cannot work with `--include-cluster-scope-resources`, `--exclude-cluster-scope-resources`, `--include-namespaced-resources` and `--exclude-namespaced-resources`.
* Exclude secrets from the backup.
@ -141,3 +171,33 @@ Kubernetes resources to exclude, formatted as resource.group, such as storagecla
### velero.io/exclude-from-backup=true
* Resources with the label `velero.io/exclude-from-backup=true` are not included in backup, even if it contains a matching selector label.
### --exclude-cluster-scope-resources
Kubernetes cluster-scoped resources to exclude from the backup, formatted as resource.group, such as `storageclasses.storage.k8s.io`(use '*' for all resources). Cannot work with `--include-resources`, `--exclude-resources` and `--include-cluster-resources`. This parameter only works for backup, not for restore.
* Exclude StorageClasses and ClusterRoles from the backup.
```bash
velero backup create <backup-name> --exclude-cluster-scope-resources="storageclasses,clusterroles"
```
* Exclude all cluster-scoped resources from the backup.
```bash
velero backup create <backup-name> --exclude-cluster-scope-resources="*"
```
### --exclude-namespaced-resources
Kubernetes namespace resources to exclude from the backup, formatted as resource.group, such as `deployments.apps`(use '*' for all resources). Cannot work with `--include-resources`, `--exclude-resources` and `--include-cluster-resources`. This parameter only works for backup, not for restore.
* Exclude all Deployments and ConfigMaps from the backup.
```bash
velero backup create <backup-name> --exclude-namespaced-resources="deployments.apps,configmaps"
```
* Exclude all namespace resources from the backup.
```bash
velero backup create <backup-name> --exclude-namespaced-resources="*"
```