mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-25 09:24:16 +00:00
Add new resource filters can separate cluster and namespace scope resources.
Signed-off-by: Xun Jiang <blackpiglet@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
+23
-7
@@ -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)
|
||||
|
||||
+930
-6
@@ -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")...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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] != "") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user