diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index da915ed7e55..4a6656b8802 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -464,7 +464,7 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg } // validate the included/excluded namespaces - for _, err := range collections.ValidateNamespaceIncludesExcludes(request.Spec.IncludedNamespaces, request.Spec.ExcludedNamespaces) { + for _, err := range b.validateNamespaceIncludesExcludes(request.Spec.IncludedNamespaces, request.Spec.ExcludedNamespaces) { request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err)) } @@ -601,6 +601,24 @@ func (b *backupReconciler) validateAndGetSnapshotLocations(backup *velerov1api.B return providerLocations, nil } +func (b *backupReconciler) validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces []string) []error { + var errs []error + if errs = collections.ValidateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces); len(errs) > 0 { + return errs + } + + namespace := &corev1api.Namespace{} + for _, name := range collections.NewIncludesExcludes().Includes(includedNamespaces...).GetIncludes() { + if name == "" || name == "*" { + continue + } + if err := b.kbClient.Get(context.Background(), kbclient.ObjectKey{Name: name}, namespace); err != nil { + errs = append(errs, err) + } + } + return errs +} + // runBackup runs and uploads a validated backup. Any error returned from this function // causes the backup to be Failed; if no error is returned, the backup's status's Errors // field is checked to see if the backup was a partial failure. diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 7dd39229967..ccec0f899fb 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -1563,6 +1563,43 @@ func TestValidateAndGetSnapshotLocations(t *testing.T) { } } +func TestValidateNamespaceIncludesExcludes(t *testing.T) { + namespace := builder.ForNamespace("default").Result() + reconciler := &backupReconciler{ + kbClient: velerotest.NewFakeControllerRuntimeClient(t, namespace), + } + + // empty string as includedNamespaces + includedNamespaces := []string{""} + excludedNamespaces := []string{"test"} + errs := reconciler.validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces) + assert.Empty(t, errs) + + // "*" as includedNamespaces + includedNamespaces = []string{"*"} + excludedNamespaces = []string{"test"} + errs = reconciler.validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces) + assert.Empty(t, errs) + + // invalid namespaces + includedNamespaces = []string{"1@#"} + excludedNamespaces = []string{"2@#"} + errs = reconciler.validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces) + assert.Len(t, errs, 2) + + // not exist namespaces + includedNamespaces = []string{"non-existing-namespace"} + excludedNamespaces = []string{} + errs = reconciler.validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces) + assert.Len(t, errs, 1) + + // valid namespaces + includedNamespaces = []string{"default"} + excludedNamespaces = []string{} + errs = reconciler.validateNamespaceIncludesExcludes(includedNamespaces, excludedNamespaces) + assert.Empty(t, errs) +} + // Test_getLastSuccessBySchedule verifies that the getLastSuccessBySchedule helper function correctly returns // the completion timestamp of the most recent completed backup for each schedule, including an entry for ad-hoc // or non-scheduled backups.