add --include-cluster-resources flag to restores (optional, default true)

Signed-off-by: Steve Kriss <steve@heptio.com>
pull/147/head
Steve Kriss 2017-10-20 12:51:54 -07:00
parent e460199536
commit a7cc58730e
6 changed files with 186 additions and 58 deletions

View File

@ -14,18 +14,19 @@ ark create restore BACKUP [flags]
### Options
```
--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-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
--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,...
-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>)
--show-labels show labels in the last column
--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 optionalBool[=true] include cluster-scoped resources in the restore
--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
--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,...
-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>)
--show-labels show labels in the last column
```
### Options inherited from parent commands

View File

@ -14,18 +14,19 @@ ark restore create BACKUP [flags]
### Options
```
--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-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
--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,...
-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>)
--show-labels show labels in the last column
--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 optionalBool[=true] include cluster-scoped resources in the restore
--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
--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,...
-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>)
--show-labels show labels in the last column
```
### Options inherited from parent commands

View File

@ -54,6 +54,11 @@ 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. If null, defaults
// to true.
IncludeClusterResources *bool `json:"includeClusterResources"`
}
// RestorePhase is a string representation of the lifecycle phase

View File

@ -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 flag.OptionalBool
}
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: flag.NewOptionalBool(nil),
}
}
@ -86,6 +88,9 @@ 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"
f = flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "include cluster-scoped resources in the restore")
f.NoOptDefVal = "true"
}
func (o *CreateOptions) Validate(c *cobra.Command, args []string) error {
@ -118,14 +123,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.Value,
},
}

View File

@ -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 != nil && !*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 {

View File

@ -159,10 +159,15 @@ 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{
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"},
@ -288,15 +293,23 @@ func TestRestorePriority(t *testing.T) {
}
func TestRestoreResourceForNamespace(t *testing.T) {
var (
trueVal = true
falseVal = false
truePtr = &trueVal
falsePtr = &falseVal
)
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
includeClusterResources *bool
fileSystem *fakeFileSystem
restorers map[schema.GroupResource]restorers.ResourceRestorer
expectedErrors api.RestoreResult
expectedObjs []unstructured.Unstructured
}{
{
name: "basic normal case",
@ -394,6 +407,59 @@ func TestRestoreResourceForNamespace(t *testing.T) {
restorers: map[schema.GroupResource]restorers.ResourceRestorer{schema.GroupResource{Resource: "foo-resource"}: newFakeCustomRestorer()},
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap),
},
{
name: "cluster-scoped resources are skipped when IncludeClusterResources=false",
namespace: "",
resourcePath: "persistentvolumes",
labelSelector: labels.NewSelector(),
includeClusterResources: falsePtr,
fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
},
{
name: "namespaced resources are not skipped when IncludeClusterResources=false",
namespace: "ns-1",
resourcePath: "configmaps",
labelSelector: labels.NewSelector(),
includeClusterResources: falsePtr,
fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap),
},
{
name: "cluster-scoped resources are not skipped when IncludeClusterResources=true",
namespace: "",
resourcePath: "persistentvolumes",
labelSelector: labels.NewSelector(),
includeClusterResources: truePtr,
fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume),
},
{
name: "namespaced resources are not skipped when IncludeClusterResources=true",
namespace: "ns-1",
resourcePath: "configmaps",
labelSelector: labels.NewSelector(),
includeClusterResources: truePtr,
fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap),
},
{
name: "cluster-scoped resources are not skipped when IncludeClusterResources=nil",
namespace: "",
resourcePath: "persistentvolumes",
labelSelector: labels.NewSelector(),
includeClusterResources: nil,
fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume),
},
{
name: "namespaced resources are not skipped when IncludeClusterResources=nil",
namespace: "ns-1",
resourcePath: "configmaps",
labelSelector: labels.NewSelector(),
includeClusterResources: nil,
fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap),
},
}
for _, test := range tests {
@ -408,6 +474,9 @@ func TestRestoreResourceForNamespace(t *testing.T) {
gv := schema.GroupVersion{Group: "", Version: "v1"}
dynamicFactory.On("ClientForGroupVersionResource", gv, resource, test.namespace).Return(resourceClient, nil)
pvResource := metav1.APIResource{Name: "persistentvolumes", Namespaced: false}
dynamicFactory.On("ClientForGroupVersionResource", gv, pvResource, test.namespace).Return(resourceClient, nil)
log, _ := testlogger.NewNullLogger()
ctx := &context{
@ -420,12 +489,15 @@ func TestRestoreResourceForNamespace(t *testing.T) {
Namespace: api.DefaultNamespace,
Name: "my-restore",
},
Spec: api.RestoreSpec{
IncludeClusterResources: test.includeClusterResources,
},
},
backup: &api.Backup{},
logger: log,
}
warnings, errors := ctx.restoreResource("configmaps", test.namespace, test.resourcePath)
warnings, errors := ctx.restoreResource(test.resourcePath, test.namespace, test.resourcePath)
assert.Empty(t, warnings.Ark)
assert.Empty(t, warnings.Cluster)
@ -517,12 +589,50 @@ func toUnstructured(objs ...runtime.Object) []unstructured.Unstructured {
delete(metadata, "creationTimestamp")
if _, exists := metadata["namespace"]; !exists {
metadata["namespace"] = ""
}
delete(unstructuredObj.Object, "status")
res = append(res, unstructuredObj)
}
return res
}
type testPersistentVolume struct {
*v1.PersistentVolume
}
func newTestPV() *testPersistentVolume {
return &testPersistentVolume{
PersistentVolume: &v1.PersistentVolume{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "PersistentVolume",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pv",
},
Status: v1.PersistentVolumeStatus{},
},
}
}
func (pv *testPersistentVolume) WithArkLabel(restoreName string) *testPersistentVolume {
if pv.Labels == nil {
pv.Labels = make(map[string]string)
}
pv.Labels[api.RestoreLabelKey] = restoreName
return pv
}
func (pv *testPersistentVolume) ToJSON() []byte {
bytes, _ := json.Marshal(pv.PersistentVolume)
return bytes
}
type testConfigMap struct {
*v1.ConfigMap
}