mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-03-27 12:05:05 +00:00
Merge pull request #132 from dgoodwin/ordering
Restructure backup format for resource prioritization
This commit is contained in:
@@ -62,26 +62,38 @@ Note that this file includes detailed info about your volume snapshots in the `s
|
||||
When unzipped, a typical backup directory (e.g. `backup1234.tar.gz`) looks like the following:
|
||||
|
||||
```
|
||||
cluster/
|
||||
resources/
|
||||
persistentvolumes/
|
||||
pv01.json
|
||||
...
|
||||
namespaces/
|
||||
namespace1/
|
||||
configmaps/
|
||||
myconfigmap.json
|
||||
cluster/
|
||||
pv01.json
|
||||
...
|
||||
pods
|
||||
mypod.json
|
||||
...
|
||||
jobs
|
||||
awesome-job.json
|
||||
...
|
||||
deployments
|
||||
cool-deployment.json
|
||||
...
|
||||
...
|
||||
namespace2/
|
||||
...
|
||||
configmaps/
|
||||
namespaces/
|
||||
namespace1/
|
||||
myconfigmap.json
|
||||
...
|
||||
namespace2/
|
||||
...
|
||||
pods/
|
||||
namespaces/
|
||||
namespace1/
|
||||
mypod.json
|
||||
...
|
||||
namespace2/
|
||||
...
|
||||
jobs/
|
||||
namespaces/
|
||||
namespace1/
|
||||
awesome-job.json
|
||||
...
|
||||
namespace2/
|
||||
...
|
||||
deployments/
|
||||
namespaces/
|
||||
namespace1/
|
||||
cool-deployment.json
|
||||
...
|
||||
namespace2/
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
@@ -21,6 +21,10 @@ const (
|
||||
// the Ark server and API objects.
|
||||
DefaultNamespace = "heptio-ark"
|
||||
|
||||
// ResourcesDir is a top-level directory expected in backups which contains sub-directories
|
||||
// for each resource type in the backup.
|
||||
ResourcesDir = "resources"
|
||||
|
||||
// RestoreLabelKey is the label key that's applied to all resources that
|
||||
// are created during a restore. This is applied for ease of identification
|
||||
// of restored resources. The value will be the restore's name.
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -473,9 +474,9 @@ func (ib *realItemBackupper) backupItem(ctx *backupContext, item map[string]inte
|
||||
|
||||
var filePath string
|
||||
if namespace != "" {
|
||||
filePath = strings.Join([]string{api.NamespaceScopedDir, namespace, groupResource.String(), name + ".json"}, "/")
|
||||
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.NamespaceScopedDir, namespace, name+".json")
|
||||
} else {
|
||||
filePath = strings.Join([]string{api.ClusterScopedDir, groupResource.String(), name + ".json"}, "/")
|
||||
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.ClusterScopedDir, name+".json")
|
||||
}
|
||||
|
||||
itemBytes, err := json.Marshal(item)
|
||||
|
||||
@@ -439,14 +439,14 @@ func TestBackupMethod(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedFiles := sets.NewString(
|
||||
"namespaces/a/configmaps/configMap1.json",
|
||||
"namespaces/b/configmaps/configMap2.json",
|
||||
"namespaces/a/roles.rbac.authorization.k8s.io/role1.json",
|
||||
"resources/configmaps/namespaces/a/configMap1.json",
|
||||
"resources/configmaps/namespaces/b/configMap2.json",
|
||||
"resources/roles.rbac.authorization.k8s.io/namespaces/a/role1.json",
|
||||
// CSRs are not expected because they're unrelated cluster-scoped resources
|
||||
)
|
||||
|
||||
expectedData := map[string]string{
|
||||
"namespaces/a/configmaps/configMap1.json": `
|
||||
"resources/configmaps/namespaces/a/configMap1.json": `
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -458,7 +458,7 @@ func TestBackupMethod(t *testing.T) {
|
||||
"a": "b"
|
||||
}
|
||||
}`,
|
||||
"namespaces/b/configmaps/configMap2.json": `
|
||||
"resources/configmaps/namespaces/b/configMap2.json": `
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
@@ -471,7 +471,7 @@ func TestBackupMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
`,
|
||||
"namespaces/a/roles.rbac.authorization.k8s.io/role1.json": `
|
||||
"resources/roles.rbac.authorization.k8s.io/namespaces/a/role1.json": `
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "Role",
|
||||
@@ -1114,7 +1114,7 @@ func TestBackupItem(t *testing.T) {
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("foo"),
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "namespaces/foo/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/namespaces/foo/bar.json",
|
||||
},
|
||||
{
|
||||
name: "* namespace include",
|
||||
@@ -1122,21 +1122,21 @@ func TestBackupItem(t *testing.T) {
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "namespaces/foo/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/namespaces/foo/bar.json",
|
||||
},
|
||||
{
|
||||
name: "cluster-scoped",
|
||||
item: `{"metadata":{"name":"bar"}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "cluster/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/cluster/bar.json",
|
||||
},
|
||||
{
|
||||
name: "make sure status is deleted",
|
||||
item: `{"metadata":{"name":"bar"},"spec":{"color":"green"},"status":{"foo":"bar"}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "cluster/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/cluster/bar.json",
|
||||
},
|
||||
{
|
||||
name: "tar header write error",
|
||||
@@ -1156,7 +1156,7 @@ func TestBackupItem(t *testing.T) {
|
||||
item: `{"metadata":{"name":"bar"}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "cluster/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/cluster/bar.json",
|
||||
customAction: true,
|
||||
expectedActionID: "bar",
|
||||
},
|
||||
@@ -1166,7 +1166,7 @@ func TestBackupItem(t *testing.T) {
|
||||
item: `{"metadata":{"namespace": "myns", "name":"bar"}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "namespaces/myns/resource.group/bar.json",
|
||||
expectedTarHeaderName: "resources/resource.group/namespaces/myns/bar.json",
|
||||
customAction: true,
|
||||
expectedActionID: "myns/bar",
|
||||
},
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
@@ -257,55 +257,107 @@ func (ctx *context) execute() (api.RestoreResult, api.RestoreResult) {
|
||||
// restoreFromDir executes a restore based on backup data contained within a local
|
||||
// directory.
|
||||
func (ctx *context) restoreFromDir(dir string) (api.RestoreResult, api.RestoreResult) {
|
||||
warnings, errors := api.RestoreResult{}, api.RestoreResult{}
|
||||
|
||||
// cluster-scoped
|
||||
clusterPath := path.Join(dir, api.ClusterScopedDir)
|
||||
exists, err := ctx.fileSystem.DirExists(clusterPath)
|
||||
if err != nil {
|
||||
errors.Cluster = []string{err.Error()}
|
||||
}
|
||||
if exists {
|
||||
w, e := ctx.restoreNamespace("", clusterPath)
|
||||
merge(&warnings, &w)
|
||||
merge(&errors, &e)
|
||||
}
|
||||
|
||||
// namespace-scoped
|
||||
namespacesPath := path.Join(dir, api.NamespaceScopedDir)
|
||||
exists, err = ctx.fileSystem.DirExists(namespacesPath)
|
||||
if err != nil {
|
||||
addArkError(&errors, err)
|
||||
return warnings, errors
|
||||
}
|
||||
if !exists {
|
||||
return warnings, errors
|
||||
}
|
||||
|
||||
nses, err := ctx.fileSystem.ReadDir(namespacesPath)
|
||||
if err != nil {
|
||||
addArkError(&errors, err)
|
||||
return warnings, errors
|
||||
}
|
||||
warnings, errs := api.RestoreResult{}, api.RestoreResult{}
|
||||
|
||||
namespaceFilter := collections.NewIncludesExcludes().Includes(ctx.restore.Spec.IncludedNamespaces...).Excludes(ctx.restore.Spec.ExcludedNamespaces...)
|
||||
for _, ns := range nses {
|
||||
if !ns.IsDir() {
|
||||
continue
|
||||
}
|
||||
nsPath := path.Join(namespacesPath, ns.Name())
|
||||
|
||||
if !namespaceFilter.ShouldInclude(ns.Name()) {
|
||||
ctx.infof("Skipping namespace %s", ns.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
w, e := ctx.restoreNamespace(ns.Name(), nsPath)
|
||||
merge(&warnings, &w)
|
||||
merge(&errors, &e)
|
||||
// Make sure the top level "resources" dir exists:
|
||||
resourcesDir := filepath.Join(dir, api.ResourcesDir)
|
||||
rde, err := ctx.fileSystem.DirExists(resourcesDir)
|
||||
if err != nil {
|
||||
addArkError(&errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
if !rde {
|
||||
addArkError(&errs, errors.New("backup does not contain top level resources directory"))
|
||||
}
|
||||
|
||||
return warnings, errors
|
||||
resourceDirs, err := ctx.fileSystem.ReadDir(resourcesDir)
|
||||
if err != nil {
|
||||
addArkError(&errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
resourceDirsMap := make(map[string]os.FileInfo)
|
||||
|
||||
for _, rscDir := range resourceDirs {
|
||||
rscName := rscDir.Name()
|
||||
resourceDirsMap[rscName] = rscDir
|
||||
}
|
||||
|
||||
for _, resource := range ctx.prioritizedResources {
|
||||
rscDir := resourceDirsMap[resource.String()]
|
||||
if rscDir == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resourcePath := filepath.Join(resourcesDir, rscDir.Name())
|
||||
|
||||
clusterSubDir := filepath.Join(resourcePath, api.ClusterScopedDir)
|
||||
clusterSubDirExists, err := ctx.fileSystem.DirExists(clusterSubDir)
|
||||
if err != nil {
|
||||
addArkError(&errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
if clusterSubDirExists {
|
||||
w, e := ctx.restoreResource(resource.String(), "", clusterSubDir)
|
||||
merge(&warnings, &w)
|
||||
merge(&errs, &e)
|
||||
continue
|
||||
}
|
||||
|
||||
nsSubDir := filepath.Join(resourcePath, api.NamespaceScopedDir)
|
||||
nsSubDirExists, err := ctx.fileSystem.DirExists(nsSubDir)
|
||||
if err != nil {
|
||||
addArkError(&errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
if !nsSubDirExists {
|
||||
continue
|
||||
}
|
||||
|
||||
nsDirs, err := ctx.fileSystem.ReadDir(nsSubDir)
|
||||
if err != nil {
|
||||
addArkError(&errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
for _, nsDir := range nsDirs {
|
||||
if !nsDir.IsDir() {
|
||||
continue
|
||||
}
|
||||
nsName := nsDir.Name()
|
||||
nsPath := filepath.Join(nsSubDir, nsName)
|
||||
|
||||
if !namespaceFilter.ShouldInclude(nsName) {
|
||||
ctx.infof("Skipping namespace %s", nsName)
|
||||
continue
|
||||
}
|
||||
|
||||
// fetch mapped NS name
|
||||
mappedNsName := nsName
|
||||
if target, ok := ctx.restore.Spec.NamespaceMapping[nsName]; ok {
|
||||
mappedNsName = target
|
||||
}
|
||||
|
||||
// ensure namespace exists
|
||||
ns := &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: mappedNsName,
|
||||
},
|
||||
}
|
||||
if _, err := kube.EnsureNamespaceExists(ns, ctx.namespaceClient); err != nil {
|
||||
addArkError(&errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
w, e := ctx.restoreResource(resource.String(), nsName, nsPath)
|
||||
merge(&warnings, &w)
|
||||
merge(&errs, &e)
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
// merge combines two RestoreResult objects into one
|
||||
@@ -340,94 +392,38 @@ func addToResult(r *api.RestoreResult, ns string, e error) {
|
||||
}
|
||||
}
|
||||
|
||||
// restoreNamespace restores the resources from a specified namespace directory in the backup,
|
||||
// or from the cluster-scoped directory if no namespace is specified.
|
||||
func (ctx *context) restoreNamespace(nsName, nsPath string) (api.RestoreResult, api.RestoreResult) {
|
||||
warnings, errors := api.RestoreResult{}, api.RestoreResult{}
|
||||
// restoreResource restores the specified cluster or namespace scoped resource. If namespace is
|
||||
// empty we are restoring a cluster level resource, otherwise into the specified namespace.
|
||||
func (ctx *context) restoreResource(resource, namespace, resourcePath string) (api.RestoreResult, api.RestoreResult) {
|
||||
warnings, errs := api.RestoreResult{}, api.RestoreResult{}
|
||||
|
||||
if nsName == "" {
|
||||
ctx.infof("Restoring cluster-scoped resources")
|
||||
if namespace != "" {
|
||||
ctx.infof("Restoring resource '%s' into namespace '%s' from: %s", resource, namespace, resourcePath)
|
||||
} else {
|
||||
ctx.infof("Restoring namespace %s", nsName)
|
||||
ctx.infof("Restoring cluster level resource '%s' from: %s", resource, resourcePath)
|
||||
}
|
||||
|
||||
resourceDirs, err := ctx.fileSystem.ReadDir(nsPath)
|
||||
if err != nil {
|
||||
addToResult(&errors, nsName, err)
|
||||
return warnings, errors
|
||||
}
|
||||
|
||||
resourceDirsMap := make(map[string]os.FileInfo)
|
||||
|
||||
for _, rscDir := range resourceDirs {
|
||||
rscName := rscDir.Name()
|
||||
resourceDirsMap[rscName] = rscDir
|
||||
}
|
||||
|
||||
if nsName != "" {
|
||||
// fetch mapped NS name
|
||||
if target, ok := ctx.restore.Spec.NamespaceMapping[nsName]; ok {
|
||||
nsName = target
|
||||
}
|
||||
|
||||
// ensure namespace exists
|
||||
ns := &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nsName,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := kube.EnsureNamespaceExists(ns, ctx.namespaceClient); err != nil {
|
||||
addArkError(&errors, err)
|
||||
return warnings, errors
|
||||
}
|
||||
}
|
||||
|
||||
for _, resource := range ctx.prioritizedResources {
|
||||
rscDir := resourceDirsMap[resource.String()]
|
||||
if rscDir == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resourcePath := path.Join(nsPath, rscDir.Name())
|
||||
|
||||
w, e := ctx.restoreResourceForNamespace(nsName, resourcePath)
|
||||
merge(&warnings, &w)
|
||||
merge(&errors, &e)
|
||||
}
|
||||
|
||||
return warnings, errors
|
||||
}
|
||||
|
||||
// restoreResourceForNamespace restores the specified resource type for the specified
|
||||
// namespace (or blank for cluster-scoped resources).
|
||||
func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath string) (api.RestoreResult, api.RestoreResult) {
|
||||
warnings, errors := api.RestoreResult{}, api.RestoreResult{}
|
||||
resource := path.Base(resourcePath)
|
||||
|
||||
ctx.infof("Restoring resource %v into namespace %v", resource, namespace)
|
||||
|
||||
files, err := ctx.fileSystem.ReadDir(resourcePath)
|
||||
if err != nil {
|
||||
addToResult(&errors, namespace, fmt.Errorf("error reading %q resource directory: %v", resource, err))
|
||||
return warnings, errors
|
||||
addToResult(&errs, namespace, fmt.Errorf("error reading %q resource directory: %v", resource, err))
|
||||
return warnings, errs
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return warnings, errors
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
var (
|
||||
resourceClient client.Dynamic
|
||||
restorer restorers.ResourceRestorer
|
||||
waiter *resourceWaiter
|
||||
groupResource = schema.ParseGroupResource(path.Base(resourcePath))
|
||||
groupResource = schema.ParseGroupResource(resource)
|
||||
)
|
||||
|
||||
for _, file := range files {
|
||||
fullPath := filepath.Join(resourcePath, file.Name())
|
||||
obj, err := ctx.unmarshal(fullPath)
|
||||
if err != nil {
|
||||
addToResult(&errors, namespace, fmt.Errorf("error decoding %q: %v", fullPath, err))
|
||||
addToResult(&errs, namespace, fmt.Errorf("error decoding %q: %v", fullPath, err))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -448,8 +444,8 @@ func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath s
|
||||
var err error
|
||||
resourceClient, err = ctx.dynamicFactory.ClientForGroupVersionKind(obj.GroupVersionKind(), resource, namespace)
|
||||
if err != nil {
|
||||
addArkError(&errors, fmt.Errorf("error getting resource client for namespace %q, resource %q: %v", namespace, &groupResource, err))
|
||||
return warnings, errors
|
||||
addArkError(&errs, fmt.Errorf("error getting resource client for namespace %q, resource %q: %v", namespace, &groupResource, err))
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
restorer = ctx.restorers[groupResource]
|
||||
@@ -463,8 +459,8 @@ func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath s
|
||||
if restorer.Wait() {
|
||||
itmWatch, err := resourceClient.Watch(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
addArkError(&errors, fmt.Errorf("error watching for namespace %q, resource %q: %v", namespace, &groupResource, err))
|
||||
return warnings, errors
|
||||
addArkError(&errs, fmt.Errorf("error watching for namespace %q, resource %q: %v", namespace, &groupResource, err))
|
||||
return warnings, errs
|
||||
}
|
||||
watchChan := itmWatch.ResultChan()
|
||||
defer itmWatch.Stop()
|
||||
@@ -487,13 +483,13 @@ func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath s
|
||||
addToResult(&warnings, namespace, fmt.Errorf("warning preparing %s: %v", fullPath, warning))
|
||||
}
|
||||
if err != nil {
|
||||
addToResult(&errors, namespace, fmt.Errorf("error preparing %s: %v", fullPath, err))
|
||||
addToResult(&errs, namespace, fmt.Errorf("error preparing %s: %v", fullPath, err))
|
||||
continue
|
||||
}
|
||||
|
||||
unstructuredObj, ok := preparedObj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
addToResult(&errors, namespace, fmt.Errorf("%s: unexpected type %T", fullPath, preparedObj))
|
||||
addToResult(&errs, namespace, fmt.Errorf("%s: unexpected type %T", fullPath, preparedObj))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -511,7 +507,7 @@ func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath s
|
||||
}
|
||||
if err != nil {
|
||||
ctx.infof("error restoring %s: %v", unstructuredObj.GetName(), err)
|
||||
addToResult(&errors, namespace, fmt.Errorf("error restoring %s: %v", fullPath, err))
|
||||
addToResult(&errs, namespace, fmt.Errorf("error restoring %s: %v", fullPath, err))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -522,11 +518,11 @@ func (ctx *context) restoreResourceForNamespace(namespace string, resourcePath s
|
||||
|
||||
if waiter != nil {
|
||||
if err := waiter.Wait(); err != nil {
|
||||
addArkError(&errors, fmt.Errorf("error waiting for all %v resources to be created in namespace %s: %v", &groupResource, namespace, err))
|
||||
addArkError(&errs, fmt.Errorf("error waiting for all %v resources to be created in namespace %s: %v", &groupResource, namespace, err))
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, errors
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
// addLabel applies the specified key/value to an object as a label.
|
||||
@@ -604,7 +600,7 @@ func (ctx *context) readBackup(tarRdr *tar.Reader) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
target := path.Join(dir, header.Name)
|
||||
target := filepath.Join(dir, header.Name)
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
@@ -616,7 +612,7 @@ func (ctx *context) readBackup(tarRdr *tar.Reader) (string, error) {
|
||||
|
||||
case tar.TypeReg:
|
||||
// make sure we have the directory created
|
||||
err := ctx.fileSystem.MkdirAll(path.Dir(target), header.FileInfo().Mode())
|
||||
err := ctx.fileSystem.MkdirAll(filepath.Dir(target), header.FileInfo().Mode())
|
||||
if err != nil {
|
||||
ctx.infof("mkdirall error: %v", err)
|
||||
return "", err
|
||||
|
||||
@@ -116,62 +116,58 @@ func TestPrioritizeResources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreMethod(t *testing.T) {
|
||||
func TestRestoreNamespaceFiltering(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fileSystem *fakeFileSystem
|
||||
baseDir string
|
||||
restore *api.Restore
|
||||
expectedReadDirs []string
|
||||
name string
|
||||
fileSystem *fakeFileSystem
|
||||
baseDir string
|
||||
restore *api.Restore
|
||||
expectedReadDirs []string
|
||||
prioritizedResources []schema.GroupResource
|
||||
}{
|
||||
{
|
||||
name: "cluster comes before namespaced",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces"},
|
||||
},
|
||||
{
|
||||
name: "namespaces dir is not read & does not error if it does not exist",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{}},
|
||||
expectedReadDirs: []string{"bak/cluster"},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore having * restores all namespaces",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
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{"*"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"},
|
||||
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"},
|
||||
schema.GroupResource{Resource: "secrets"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
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"}}},
|
||||
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"},
|
||||
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"},
|
||||
schema.GroupResource{Resource: "secrets"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters with exclusion filter",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
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"}}},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/namespaces", "bak/namespaces/b", "bak/namespaces/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"},
|
||||
schema.GroupResource{Resource: "secrets"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespacesToRestore properly filters with inclusion & exclusion filters",
|
||||
fileSystem: newFakeFileSystem().WithDirectories("bak/cluster", "bak/namespaces/a", "bak/namespaces/b", "bak/namespaces/c"),
|
||||
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/cluster", "bak/namespaces", "bak/namespaces/a", "bak/namespaces/c"},
|
||||
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"},
|
||||
schema.GroupResource{Resource: "secrets"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -180,10 +176,11 @@ func TestRestoreMethod(t *testing.T) {
|
||||
log, _ := testlogger.NewNullLogger()
|
||||
|
||||
ctx := &context{
|
||||
restore: test.restore,
|
||||
namespaceClient: &fakeNamespaceClient{},
|
||||
fileSystem: test.fileSystem,
|
||||
logger: log,
|
||||
restore: test.restore,
|
||||
namespaceClient: &fakeNamespaceClient{},
|
||||
fileSystem: test.fileSystem,
|
||||
logger: log,
|
||||
prioritizedResources: test.prioritizedResources,
|
||||
}
|
||||
|
||||
warnings, errors := ctx.restoreFromDir(test.baseDir)
|
||||
@@ -199,62 +196,59 @@ func TestRestoreMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreNamespace(t *testing.T) {
|
||||
func TestRestorePriority(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fileSystem *fakeFileSystem
|
||||
restore *api.Restore
|
||||
namespace string
|
||||
path string
|
||||
baseDir string
|
||||
prioritizedResources []schema.GroupResource
|
||||
expectedErrors api.RestoreResult
|
||||
expectedReadDirs []string
|
||||
}{
|
||||
{
|
||||
name: "cluster test",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/cluster/a").WithDirectory("bak/cluster/c"),
|
||||
namespace: "",
|
||||
path: "bak/cluster",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"),
|
||||
baseDir: "bak",
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
||||
prioritizedResources: []schema.GroupResource{
|
||||
schema.GroupResource{Resource: "a"},
|
||||
schema.GroupResource{Resource: "b"},
|
||||
schema.GroupResource{Resource: "c"},
|
||||
},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/cluster/a", "bak/cluster/c"},
|
||||
expectedReadDirs: []string{"bak/resources", "bak/resources/a/cluster", "bak/resources/c/cluster"},
|
||||
},
|
||||
{
|
||||
name: "resource priorities are applied",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/cluster/a").WithDirectory("bak/cluster/c"),
|
||||
namespace: "",
|
||||
path: "bak/cluster",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"),
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
||||
baseDir: "bak",
|
||||
prioritizedResources: []schema.GroupResource{
|
||||
schema.GroupResource{Resource: "c"},
|
||||
schema.GroupResource{Resource: "b"},
|
||||
schema.GroupResource{Resource: "a"},
|
||||
},
|
||||
expectedReadDirs: []string{"bak/cluster", "bak/cluster/c", "bak/cluster/a"},
|
||||
expectedReadDirs: []string{"bak/resources", "bak/resources/c/cluster", "bak/resources/a/cluster"},
|
||||
},
|
||||
{
|
||||
name: "basic namespace",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/namespaces/ns-1/a").WithDirectory("bak/namespaces/ns-1/c"),
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{NamespaceMapping: make(map[string]string)}},
|
||||
namespace: "ns-1",
|
||||
path: "bak/namespaces/ns-1",
|
||||
fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/namespaces/ns-1").WithDirectory("bak/resources/c/namespaces/ns-1"),
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}},
|
||||
baseDir: "bak",
|
||||
prioritizedResources: []schema.GroupResource{
|
||||
schema.GroupResource{Resource: "a"},
|
||||
schema.GroupResource{Resource: "b"},
|
||||
schema.GroupResource{Resource: "c"},
|
||||
},
|
||||
expectedReadDirs: []string{"bak/namespaces/ns-1", "bak/namespaces/ns-1/a", "bak/namespaces/ns-1/c"},
|
||||
expectedReadDirs: []string{"bak/resources", "bak/resources/a/namespaces", "bak/resources/a/namespaces/ns-1", "bak/resources/c/namespaces", "bak/resources/c/namespaces/ns-1"},
|
||||
},
|
||||
{
|
||||
name: "error in a single resource doesn't terminate restore immediately, but is returned",
|
||||
fileSystem: newFakeFileSystem().
|
||||
WithFile("bak/namespaces/ns-1/a/invalid-json.json", []byte("invalid json")).
|
||||
WithDirectory("bak/namespaces/ns-1/c"),
|
||||
restore: &api.Restore{Spec: api.RestoreSpec{NamespaceMapping: make(map[string]string)}},
|
||||
namespace: "ns-1",
|
||||
path: "bak/namespaces/ns-1",
|
||||
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{"*"}}},
|
||||
baseDir: "bak",
|
||||
prioritizedResources: []schema.GroupResource{
|
||||
schema.GroupResource{Resource: "a"},
|
||||
schema.GroupResource{Resource: "b"},
|
||||
@@ -262,10 +256,10 @@ func TestRestoreNamespace(t *testing.T) {
|
||||
},
|
||||
expectedErrors: api.RestoreResult{
|
||||
Namespaces: map[string][]string{
|
||||
"ns-1": {"error decoding \"bak/namespaces/ns-1/a/invalid-json.json\": invalid character 'i' looking for beginning of value"},
|
||||
"ns-1": {"error decoding \"bak/resources/a/namespaces/ns-1/invalid-json.json\": invalid character 'i' looking for beginning of value"},
|
||||
},
|
||||
},
|
||||
expectedReadDirs: []string{"bak/namespaces/ns-1", "bak/namespaces/ns-1/a", "bak/namespaces/ns-1/c"},
|
||||
expectedReadDirs: []string{"bak/resources", "bak/resources/a/namespaces", "bak/resources/a/namespaces/ns-1", "bak/resources/c/namespaces", "bak/resources/c/namespaces/ns-1"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -281,7 +275,7 @@ func TestRestoreNamespace(t *testing.T) {
|
||||
logger: log,
|
||||
}
|
||||
|
||||
warnings, errors := ctx.restoreNamespace(test.namespace, test.path)
|
||||
warnings, errors := ctx.restoreFromDir(test.baseDir)
|
||||
|
||||
assert.Empty(t, warnings.Ark)
|
||||
assert.Empty(t, warnings.Cluster)
|
||||
@@ -431,7 +425,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
|
||||
logger: log,
|
||||
}
|
||||
|
||||
warnings, errors := ctx.restoreResourceForNamespace(test.namespace, test.resourcePath)
|
||||
warnings, errors := ctx.restoreResource("configmaps", test.namespace, test.resourcePath)
|
||||
|
||||
assert.Empty(t, warnings.Ark)
|
||||
assert.Empty(t, warnings.Cluster)
|
||||
|
||||
Reference in New Issue
Block a user