From 9c4e15a2ccd85e5e5f526dc75a2ae0bb40e6ffec Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Fri, 20 Oct 2017 12:51:54 -0700 Subject: [PATCH] add --include-cluster-resources flag to restores Signed-off-by: Steve Kriss --- docs/cli-reference/ark_create_restore.md | 1 + docs/cli-reference/ark_restore_create.md | 1 + pkg/apis/ark/v1/restore.go | 4 ++ pkg/cmd/cli/restore/create.go | 47 +++++++++-------- pkg/restore/restore.go | 5 ++ pkg/restore/restore_test.go | 66 +++++++++++++++++------- 6 files changed, 84 insertions(+), 40 deletions(-) diff --git a/docs/cli-reference/ark_create_restore.md b/docs/cli-reference/ark_create_restore.md index b0422c175e..e73ce35d85 100644 --- a/docs/cli-reference/ark_create_restore.md +++ b/docs/cli-reference/ark_create_restore.md @@ -17,6 +17,7 @@ ark create restore BACKUP [flags] --exclude-namespaces stringArray namespaces to exclude from the restore --exclude-resources stringArray resources to exclude from the restore, formatted as resource.group, such as storageclasses.storage.k8s.io -h, --help help for restore + --include-cluster-resources include cluster-scoped resources in the restore (default true) --include-namespaces stringArray namespaces to include in the restore (use '*' for all namespaces) (default *) --include-resources stringArray resources to include in the restore, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources) --label-columns stringArray a comma-separated list of labels to be displayed as columns diff --git a/docs/cli-reference/ark_restore_create.md b/docs/cli-reference/ark_restore_create.md index 395dcbe3f0..d3ee4d27ec 100644 --- a/docs/cli-reference/ark_restore_create.md +++ b/docs/cli-reference/ark_restore_create.md @@ -17,6 +17,7 @@ ark restore create BACKUP [flags] --exclude-namespaces stringArray namespaces to exclude from the restore --exclude-resources stringArray resources to exclude from the restore, formatted as resource.group, such as storageclasses.storage.k8s.io -h, --help help for create + --include-cluster-resources include cluster-scoped resources in the restore (default true) --include-namespaces stringArray namespaces to include in the restore (use '*' for all namespaces) (default *) --include-resources stringArray resources to include in the restore, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources) --label-columns stringArray a comma-separated list of labels to be displayed as columns diff --git a/pkg/apis/ark/v1/restore.go b/pkg/apis/ark/v1/restore.go index 17835966f2..b3d6d9df35 100644 --- a/pkg/apis/ark/v1/restore.go +++ b/pkg/apis/ark/v1/restore.go @@ -54,6 +54,10 @@ type RestoreSpec struct { // RestorePVs specifies whether to restore all included // PVs from snapshot (via the cloudprovider). RestorePVs *bool `json:"restorePVs"` + + // IncludeClusterResources specifies whether cluster-scoped resources + // should be included for consideration in the restore. + IncludeClusterResources bool `json:"includeClusterResources"` } // RestorePhase is a string representation of the lifecycle phase diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index d0caf20540..2dcd8dcdae 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -54,23 +54,25 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command { } type CreateOptions struct { - BackupName string - RestoreVolumes flag.OptionalBool - Labels flag.Map - IncludeNamespaces flag.StringArray - ExcludeNamespaces flag.StringArray - IncludeResources flag.StringArray - ExcludeResources flag.StringArray - NamespaceMappings flag.Map - Selector flag.LabelSelector + BackupName string + RestoreVolumes flag.OptionalBool + Labels flag.Map + IncludeNamespaces flag.StringArray + ExcludeNamespaces flag.StringArray + IncludeResources flag.StringArray + ExcludeResources flag.StringArray + NamespaceMappings flag.Map + Selector flag.LabelSelector + IncludeClusterResources bool } func NewCreateOptions() *CreateOptions { return &CreateOptions{ - Labels: flag.NewMap(), - IncludeNamespaces: flag.NewStringArray("*"), - NamespaceMappings: flag.NewMap().WithEntryDelimiter(",").WithKeyValueDelimiter(":"), - RestoreVolumes: flag.NewOptionalBool(nil), + Labels: flag.NewMap(), + IncludeNamespaces: flag.NewStringArray("*"), + NamespaceMappings: flag.NewMap().WithEntryDelimiter(",").WithKeyValueDelimiter(":"), + RestoreVolumes: flag.NewOptionalBool(nil), + IncludeClusterResources: true, } } @@ -86,6 +88,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) { // this allows the user to just specify "--restore-volumes" as shorthand for "--restore-volumes=true" // like a normal bool flag f.NoOptDefVal = "true" + + flags.BoolVar(&o.IncludeClusterResources, "include-cluster-resources", o.IncludeClusterResources, "include cluster-scoped resources in the restore") } func (o *CreateOptions) Validate(c *cobra.Command, args []string) error { @@ -118,14 +122,15 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { Labels: o.Labels.Data(), }, Spec: api.RestoreSpec{ - BackupName: o.BackupName, - IncludedNamespaces: o.IncludeNamespaces, - ExcludedNamespaces: o.ExcludeNamespaces, - IncludedResources: o.IncludeResources, - ExcludedResources: o.ExcludeResources, - NamespaceMapping: o.NamespaceMappings.Data(), - LabelSelector: o.Selector.LabelSelector, - RestorePVs: o.RestoreVolumes.Value, + BackupName: o.BackupName, + IncludedNamespaces: o.IncludeNamespaces, + ExcludedNamespaces: o.ExcludeNamespaces, + IncludedResources: o.IncludeResources, + ExcludedResources: o.ExcludeResources, + NamespaceMapping: o.NamespaceMappings.Data(), + LabelSelector: o.Selector.LabelSelector, + RestorePVs: o.RestoreVolumes.Value, + IncludeClusterResources: o.IncludeClusterResources, }, } diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index bac2d563ec..c644911f75 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -397,6 +397,11 @@ func addToResult(r *api.RestoreResult, ns string, e error) { func (ctx *context) restoreResource(resource, namespace, resourcePath string) (api.RestoreResult, api.RestoreResult) { warnings, errs := api.RestoreResult{}, api.RestoreResult{} + if !ctx.restore.Spec.IncludeClusterResources && namespace == "" { + ctx.infof("Skipping resource %s because it's cluster-scoped", resource) + return warnings, errs + } + if namespace != "" { ctx.infof("Restoring resource '%s' into namespace '%s' from: %s", resource, namespace, resourcePath) } else { diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index 184e741f9a..e472e087a8 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -129,7 +129,7 @@ func TestRestoreNamespaceFiltering(t *testing.T) { name: "namespacesToRestore having * restores all namespaces", fileSystem: newFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"), baseDir: "bak", - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}}}, expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "nodes"}, @@ -140,7 +140,7 @@ func TestRestoreNamespaceFiltering(t *testing.T) { name: "namespacesToRestore properly filters", fileSystem: newFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"), baseDir: "bak", - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"b", "c"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"b", "c"}}}, expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "nodes"}, @@ -151,7 +151,7 @@ func TestRestoreNamespaceFiltering(t *testing.T) { name: "namespacesToRestore properly filters with exclusion filter", fileSystem: newFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"), baseDir: "bak", - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}, ExcludedNamespaces: []string{"a"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}, ExcludedNamespaces: []string{"a"}}}, expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "nodes"}, @@ -159,10 +159,16 @@ func TestRestoreNamespaceFiltering(t *testing.T) { }, }, { - name: "namespacesToRestore properly filters with inclusion & exclusion filters", - fileSystem: newFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"), - baseDir: "bak", - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"a", "b", "c"}, ExcludedNamespaces: []string{"b"}}}, + name: "namespacesToRestore properly filters with inclusion & exclusion filters", + fileSystem: newFakeFileSystem().WithDirectories("bak/resources/nodes/cluster", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"), + baseDir: "bak", + restore: &api.Restore{ + Spec: api.RestoreSpec{ + IncludeClusterResources: true, + IncludedNamespaces: []string{"a", "b", "c"}, + ExcludedNamespaces: []string{"b"}, + }, + }, expectedReadDirs: []string{"bak/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/c"}, prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "nodes"}, @@ -210,7 +216,7 @@ func TestRestorePriority(t *testing.T) { name: "cluster test", fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), baseDir: "bak", - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}}}, prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "a"}, schema.GroupResource{Resource: "b"}, @@ -221,7 +227,7 @@ func TestRestorePriority(t *testing.T) { { name: "resource priorities are applied", fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}}}, baseDir: "bak", prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "c"}, @@ -233,7 +239,7 @@ func TestRestorePriority(t *testing.T) { { name: "basic namespace", fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/namespaces/ns-1").WithDirectory("bak/resources/c/namespaces/ns-1"), - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}}}, baseDir: "bak", prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "a"}, @@ -247,7 +253,7 @@ func TestRestorePriority(t *testing.T) { fileSystem: newFakeFileSystem(). WithFile("bak/resources/a/namespaces/ns-1/invalid-json.json", []byte("invalid json")). WithDirectory("bak/resources/c/namespaces/ns-1"), - restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, + restore: &api.Restore{Spec: api.RestoreSpec{IncludeClusterResources: true, IncludedNamespaces: []string{"*"}}}, baseDir: "bak", prioritizedResources: []schema.GroupResource{ schema.GroupResource{Resource: "a"}, @@ -289,14 +295,15 @@ func TestRestorePriority(t *testing.T) { func TestRestoreResourceForNamespace(t *testing.T) { tests := []struct { - name string - namespace string - resourcePath string - labelSelector labels.Selector - fileSystem *fakeFileSystem - restorers map[schema.GroupResource]restorers.ResourceRestorer - expectedErrors api.RestoreResult - expectedObjs []unstructured.Unstructured + name string + namespace string + resourcePath string + labelSelector labels.Selector + excludeClusterResources bool + fileSystem *fakeFileSystem + restorers map[schema.GroupResource]restorers.ResourceRestorer + expectedErrors api.RestoreResult + expectedObjs []unstructured.Unstructured }{ { name: "basic normal case", @@ -394,6 +401,24 @@ func TestRestoreResourceForNamespace(t *testing.T) { restorers: map[schema.GroupResource]restorers.ResourceRestorer{schema.GroupResource{Resource: "foo-resource"}: newFakeCustomRestorer()}, expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), }, + // NEW + { + name: "cluster-scoped resources are skipped when IncludeClusterResources=false", + namespace: "", + resourcePath: "persistentvolumes", + labelSelector: labels.NewSelector(), + excludeClusterResources: true, + fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestConfigMap().ToJSON()), + }, + { + name: "namespaced resources are not skipped when IncludeClusterResources=false", + namespace: "ns-1", + resourcePath: "configmaps", + labelSelector: labels.NewSelector(), + excludeClusterResources: true, + fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), + }, } for _, test := range tests { @@ -420,6 +445,9 @@ func TestRestoreResourceForNamespace(t *testing.T) { Namespace: api.DefaultNamespace, Name: "my-restore", }, + Spec: api.RestoreSpec{ + IncludeClusterResources: !test.excludeClusterResources, + }, }, backup: &api.Backup{}, logger: log,