mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-05 04:55:22 +00:00
Merge pull request #59 from jrnt30/explicit-includes-excludes-restore
Allows explicit include/exclude of namespaces on restores
This commit is contained in:
@@ -14,10 +14,11 @@ ark restore create BACKUP
|
||||
### Options
|
||||
|
||||
```
|
||||
--exclude-namespaces stringArray namespaces to exclude from the restore
|
||||
--include-namespaces stringArray namespaces to include in the restore (use '*' for all namespaces) (default *)
|
||||
--label-columns stringArray a comma-separated list of labels to be displayed as columns
|
||||
--labels mapStringString labels to apply to the restore
|
||||
--namespace-mappings mapStringString namespace mappings from name in the backup to desired restored name in the form src1:dst1,src2:dst2,...
|
||||
--namespaces stringArray comma-separated list of namespaces to restore
|
||||
-o, --output string Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'.
|
||||
--restore-volumes optionalBool[=true] whether to restore volumes from snapshots
|
||||
-l, --selector labelSelector only restore resources matching this label selector (default <none>)
|
||||
|
||||
@@ -24,8 +24,13 @@ type RestoreSpec struct {
|
||||
// from.
|
||||
BackupName string `json:"backupName"`
|
||||
|
||||
// Namespaces is a slice of namespaces in the Ark backup to restore.
|
||||
Namespaces []string `json:"namespaces"`
|
||||
// IncludedNamespaces is a slice of namespace names to include objects
|
||||
// from. If empty, all namespaces are included.
|
||||
IncludedNamespaces []string `json:"includedNamespaces"`
|
||||
|
||||
// ExcludedNamespaces contains a list of namespaces that are not
|
||||
// included in the restore.
|
||||
ExcludedNamespaces []string `json:"excludedNamespaces"`
|
||||
|
||||
// NamespaceMapping is a map of source namespace names
|
||||
// to target namespace names to restore into. Any source
|
||||
|
||||
@@ -57,7 +57,8 @@ type CreateOptions struct {
|
||||
BackupName string
|
||||
RestoreVolumes flag.OptionalBool
|
||||
Labels flag.Map
|
||||
Namespaces flag.StringArray
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
NamespaceMappings flag.Map
|
||||
Selector flag.LabelSelector
|
||||
}
|
||||
@@ -65,15 +66,17 @@ type CreateOptions struct {
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
return &CreateOptions{
|
||||
Labels: flag.NewMap(),
|
||||
IncludeNamespaces: flag.NewStringArray("*"),
|
||||
NamespaceMappings: flag.NewMap().WithEntryDelimiter(",").WithKeyValueDelimiter(":"),
|
||||
RestoreVolumes: flag.NewOptionalBool(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.Var(&o.Labels, "labels", "labels to apply to the restore")
|
||||
flags.Var(&o.Namespaces, "namespaces", "comma-separated list of namespaces to restore")
|
||||
flags.Var(&o.IncludeNamespaces, "include-namespaces", "namespaces to include in the restore (use '*' for all namespaces)")
|
||||
flags.Var(&o.ExcludeNamespaces, "exclude-namespaces", "namespaces to exclude from the restore")
|
||||
flags.Var(&o.NamespaceMappings, "namespace-mappings", "namespace mappings from name in the backup to desired restored name in the form src1:dst1,src2:dst2,...")
|
||||
flags.Var(&o.Labels, "labels", "labels to apply to the restore")
|
||||
flags.VarP(&o.Selector, "selector", "l", "only restore resources matching this label selector")
|
||||
f := flags.VarPF(&o.RestoreVolumes, "restore-volumes", "", "whether to restore volumes from snapshots")
|
||||
// this allows the user to just specify "--restore-volumes" as shorthand for "--restore-volumes=true"
|
||||
@@ -111,11 +114,12 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
Labels: o.Labels.Data(),
|
||||
},
|
||||
Spec: api.RestoreSpec{
|
||||
BackupName: o.BackupName,
|
||||
Namespaces: o.Namespaces,
|
||||
NamespaceMapping: o.NamespaceMappings.Data(),
|
||||
LabelSelector: o.Selector.LabelSelector,
|
||||
RestorePVs: o.RestoreVolumes.Value,
|
||||
BackupName: o.BackupName,
|
||||
IncludedNamespaces: o.IncludeNamespaces,
|
||||
ExcludedNamespaces: o.ExcludeNamespaces,
|
||||
NamespaceMapping: o.NamespaceMappings.Data(),
|
||||
LabelSelector: o.Selector.LabelSelector,
|
||||
RestorePVs: o.RestoreVolumes.Value,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -292,11 +292,11 @@ func cloneBackup(in interface{}) (*api.Backup, error) {
|
||||
func (controller *backupController) getValidationErrors(itm *api.Backup) []string {
|
||||
var validationErrors []string
|
||||
|
||||
for err := range collections.ValidateIncludesExcludes(itm.Spec.IncludedResources, itm.Spec.ExcludedResources) {
|
||||
for _, err := range collections.ValidateIncludesExcludes(itm.Spec.IncludedResources, itm.Spec.ExcludedResources) {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid included/excluded resource lists: %v", err))
|
||||
}
|
||||
|
||||
for err := range collections.ValidateIncludesExcludes(itm.Spec.IncludedNamespaces, itm.Spec.ExcludedNamespaces) {
|
||||
for _, err := range collections.ValidateIncludesExcludes(itm.Spec.IncludedNamespaces, itm.Spec.ExcludedNamespaces) {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1"
|
||||
listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type restoreController struct {
|
||||
@@ -220,6 +221,10 @@ func (controller *restoreController) processRestore(key string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(restore.Spec.IncludedNamespaces) == 0 {
|
||||
restore.Spec.IncludedNamespaces = []string{"*"}
|
||||
}
|
||||
|
||||
// validation
|
||||
if restore.Status.ValidationErrors = controller.getValidationErrors(restore); len(restore.Status.ValidationErrors) > 0 {
|
||||
restore.Status.Phase = api.RestorePhaseFailedValidation
|
||||
@@ -227,10 +232,6 @@ func (controller *restoreController) processRestore(key string) error {
|
||||
restore.Status.Phase = api.RestorePhaseInProgress
|
||||
}
|
||||
|
||||
if len(restore.Spec.Namespaces) == 0 {
|
||||
restore.Spec.Namespaces = []string{"*"}
|
||||
}
|
||||
|
||||
// update status
|
||||
updatedRestore, err := controller.restoreClient.Restores(ns).Update(restore)
|
||||
if err != nil {
|
||||
@@ -279,6 +280,10 @@ func (controller *restoreController) getValidationErrors(itm *api.Restore) []str
|
||||
validationErrors = append(validationErrors, "BackupName must be non-empty and correspond to the name of a backup in object storage.")
|
||||
}
|
||||
|
||||
for _, err := range collections.ValidateIncludesExcludes(itm.Spec.IncludedNamespaces, itm.Spec.ExcludedNamespaces) {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
|
||||
}
|
||||
|
||||
if !controller.pvProviderExists && itm.Spec.RestorePVs != nil && *itm.Spec.RestorePVs {
|
||||
validationErrors = append(validationErrors, "Server is not configured for PV snapshot restores")
|
||||
}
|
||||
|
||||
@@ -138,24 +138,37 @@ func TestProcessRestore(t *testing.T) {
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "new restore with empty backup name fails validation",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithRestorableNamespace("ns-1").Restore,
|
||||
name: "restore with both namespace in both includedNamespaces and excludedNamespaces fails validation",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("another-1").WithExcludedNamespace("another-1").Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).
|
||||
WithRestorableNamespace("ns-1").
|
||||
WithBackup("backup-1").
|
||||
WithIncludedNamespace("another-1").
|
||||
WithExcludedNamespace("another-1").
|
||||
WithValidationError("Invalid included/excluded namespace lists: excludes list cannot contain an item in the includes list: another-1").Restore,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new restore with empty backup name fails validation",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithIncludedNamespace("ns-1").Restore,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).
|
||||
WithIncludedNamespace("ns-1").
|
||||
WithValidationError("BackupName must be non-empty and correspond to the name of a backup in object storage.").Restore,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "restore with non-existent backup name fails",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).
|
||||
WithBackup("backup-1").
|
||||
WithRestorableNamespace("ns-1").
|
||||
WithIncludedNamespace("ns-1").
|
||||
WithErrors(api.RestoreResult{
|
||||
// TODO this is the error msg returned by the fakeBackupService. When we switch to a mock obj,
|
||||
// this will likely need to change.
|
||||
@@ -166,33 +179,33 @@ func TestProcessRestore(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "restorer throwing an error causes the restore to fail",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
restorerError: errors.New("blarg"),
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).
|
||||
WithBackup("backup-1").
|
||||
WithRestorableNamespace("ns-1").
|
||||
WithIncludedNamespace("ns-1").
|
||||
WithErrors(api.RestoreResult{
|
||||
Namespaces: map[string][]string{
|
||||
"ns-1": {"blarg"},
|
||||
},
|
||||
}).Restore,
|
||||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
},
|
||||
{
|
||||
name: "valid restore gets executed",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").Restore,
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").Restore,
|
||||
},
|
||||
{
|
||||
name: "restore with no restorable namespaces gets defaulted to *",
|
||||
@@ -200,30 +213,30 @@ func TestProcessRestore(t *testing.T) {
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("*").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithRestorableNamespace("*").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("*").Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithIncludedNamespace("*").Restore,
|
||||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("*").Restore,
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("*").Restore,
|
||||
},
|
||||
{
|
||||
name: "valid restore with RestorePVs=true gets executed when allowRestoreSnapshots=true",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
allowRestoreSnapshots: true,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
},
|
||||
{
|
||||
name: "restore with RestorePVs=true fails validation when allowRestoreSnapshots=false",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).WithBackup("backup-1").WithIncludedNamespace("ns-1").WithRestorePVs(true).
|
||||
WithValidationError("Server is not configured for PV snapshot restores").Restore,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
arkv1client "github.com/heptio/ark/pkg/generated/clientset/typed/ark/v1"
|
||||
"github.com/heptio/ark/pkg/restore/restorers"
|
||||
"github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
// Restorer knows how to restore a backup.
|
||||
@@ -225,18 +226,18 @@ func (kr *kubernetesRestorer) restoreFromDir(
|
||||
return warnings, errors
|
||||
}
|
||||
|
||||
namespacesToRestore := sets.NewString(restore.Spec.Namespaces...)
|
||||
namespaceFilter := collections.NewIncludesExcludes().Includes(restore.Spec.IncludedNamespaces...).Excludes(restore.Spec.ExcludedNamespaces...)
|
||||
for _, ns := range nses {
|
||||
if !ns.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
nsPath := path.Join(namespacesPath, ns.Name())
|
||||
|
||||
if !namespacesToRestore.Has("*") && !namespacesToRestore.Has(ns.Name()) {
|
||||
if !namespaceFilter.ShouldInclude(ns.Name()) {
|
||||
glog.Infof("Skipping namespace %s", ns.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
w, e := kr.restoreNamespace(restore, ns.Name(), nsPath, prioritizedResources, selector, backup)
|
||||
merge(&warnings, &w)
|
||||
merge(&errors, &e)
|
||||
@@ -453,7 +454,7 @@ func (kr *kubernetesRestorer) restoreResourceForNamespace(
|
||||
// add an ark-restore label to each resource for easy ID
|
||||
addLabel(unstructuredObj, api.RestoreLabelKey, restore.Name)
|
||||
|
||||
glog.Infof("Restoring item %v", unstructuredObj.GetName())
|
||||
glog.Infof("Restoring %s: %v", obj.GroupVersionKind().Kind, unstructuredObj.GetName())
|
||||
_, err = resourceClient.Create(unstructuredObj)
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
addToResult(&warnings, namespace, err)
|
||||
|
||||
@@ -102,16 +102,37 @@ func TestRestoreMethod(t *testing.T) {
|
||||
name: "namespacesToRestore having * restores all namespaces",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{Namespaces: []string{"*"}}},
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{Namespaces: []string{"b", "c"}}},
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"b", "c"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/b", "bak/namespaces/c"},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters with inclusion filter",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"b", "c"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/b", "bak/namespaces/c"},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters with exclusion filter",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}, ExcludedNamespaces: []string{"a"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/b", "bak/namespaces/c"},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters with inclusion & exclusion filters",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"a", "b", "c"}, ExcludedNamespaces: []string{"b"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/a", "bak/namespaces/c"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -37,13 +37,10 @@ func (nsr *namespaceRestorer) Handles(obj runtime.Unstructured, restore *api.Res
|
||||
return false
|
||||
}
|
||||
|
||||
for _, restorableNS := range restore.Spec.Namespaces {
|
||||
if restorableNS == nsName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return collections.NewIncludesExcludes().
|
||||
Includes(restore.Spec.IncludedNamespaces...).
|
||||
Excludes(restore.Spec.ExcludedNamespaces...).
|
||||
ShouldInclude(nsName)
|
||||
}
|
||||
|
||||
func (nsr *namespaceRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error, error) {
|
||||
|
||||
@@ -37,19 +37,31 @@ func TestHandles(t *testing.T) {
|
||||
{
|
||||
name: "restorable NS",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: testutil.NewDefaultTestRestore().WithRestorableNamespace("ns-1").Restore,
|
||||
restore: testutil.NewDefaultTestRestore().WithIncludedNamespace("ns-1").Restore,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "restorable NS via wildcard",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: testutil.NewDefaultTestRestore().WithIncludedNamespace("*").Restore,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "non-restorable NS",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: testutil.NewDefaultTestRestore().WithRestorableNamespace("ns-2").Restore,
|
||||
restore: testutil.NewDefaultTestRestore().WithIncludedNamespace("ns-2").Restore,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "namespace is explicitly excluded",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: testutil.NewDefaultTestRestore().WithIncludedNamespace("*").WithExcludedNamespace("ns-1").Restore,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "namespace obj doesn't have name",
|
||||
obj: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
restore: testutil.NewDefaultTestRestore().WithRestorableNamespace("ns-1").Restore,
|
||||
restore: testutil.NewDefaultTestRestore().WithIncludedNamespace("ns-1").Restore,
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,8 +45,13 @@ func NewDefaultTestRestore() *TestRestore {
|
||||
return NewTestRestore(api.DefaultNamespace, "", api.RestorePhase(""))
|
||||
}
|
||||
|
||||
func (r *TestRestore) WithRestorableNamespace(name string) *TestRestore {
|
||||
r.Spec.Namespaces = append(r.Spec.Namespaces, name)
|
||||
func (r *TestRestore) WithIncludedNamespace(name string) *TestRestore {
|
||||
r.Spec.IncludedNamespaces = append(r.Spec.IncludedNamespaces, name)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *TestRestore) WithExcludedNamespace(name string) *TestRestore {
|
||||
r.Spec.ExcludedNamespaces = append(r.Spec.ExcludedNamespaces, name)
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user