From e2561f9073cee846f28b6f024d937cbb0bf221fd Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Thu, 7 Jun 2018 09:52:55 -0700 Subject: [PATCH 1/2] ark restic init-repository: use key data if provided Signed-off-by: Steve Kriss --- pkg/cmd/cli/restic/init_repository.go | 20 ++++--- pkg/cmd/cli/restic/init_repository_test.go | 67 ++++++++++++++++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 pkg/cmd/cli/restic/init_repository_test.go diff --git a/pkg/cmd/cli/restic/init_repository.go b/pkg/cmd/cli/restic/init_repository.go index cfeb0dff2..51b1b8b29 100644 --- a/pkg/cmd/cli/restic/init_repository.go +++ b/pkg/cmd/cli/restic/init_repository.go @@ -65,6 +65,11 @@ func NewInitRepositoryOptions() *InitRepositoryOptions { } } +var ( + errKeyFileAndKeyDataProvided = errors.Errorf("only one of --key-file and --key-data may be specified") + errKeySizeTooSmall = errors.Errorf("--key-size must be at least 1") +) + func (o *InitRepositoryOptions) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.KeyFile, "key-file", o.KeyFile, "Path to file containing the encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.") flags.StringVar(&o.KeyData, "key-data", o.KeyData, "Encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.") @@ -73,27 +78,28 @@ func (o *InitRepositoryOptions) BindFlags(flags *pflag.FlagSet) { func (o *InitRepositoryOptions) Complete(f client.Factory) error { if o.KeyFile != "" && o.KeyData != "" { - return errors.Errorf("only one of --key-file and --key-data may be specified") + return errKeyFileAndKeyDataProvided } if o.KeyFile == "" && o.KeyData == "" && o.KeySize < 1 { - return errors.Errorf("--key-size must be at least 1") + return errKeySizeTooSmall } o.Namespace = f.Namespace() - if o.KeyFile != "" { + switch { + case o.KeyFile != "": data, err := ioutil.ReadFile(o.KeyFile) if err != nil { return err } o.keyBytes = data - } - - if len(o.KeyData) == 0 { + case o.KeyData != "": + o.keyBytes = []byte(o.KeyData) + case o.KeySize > 0: o.keyBytes = make([]byte, o.KeySize) // rand.Reader always returns a nil error - _, _ = rand.Read(o.keyBytes) + rand.Read(o.keyBytes) } return nil diff --git a/pkg/cmd/cli/restic/init_repository_test.go b/pkg/cmd/cli/restic/init_repository_test.go new file mode 100644 index 000000000..1b38eaefb --- /dev/null +++ b/pkg/cmd/cli/restic/init_repository_test.go @@ -0,0 +1,67 @@ +package restic + +import ( + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" + + "k8s.io/client-go/kubernetes" + + "github.com/heptio/ark/pkg/client" + clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" +) + +type fakeFactory struct{} + +var _ client.Factory = &fakeFactory{} + +func (f *fakeFactory) BindFlags(flags *pflag.FlagSet) { + panic("not implemented") +} + +func (f *fakeFactory) Client() (clientset.Interface, error) { + panic("not implemented") +} + +func (f *fakeFactory) KubeClient() (kubernetes.Interface, error) { + panic("not implemented") +} + +func (f *fakeFactory) Namespace() string { + return "" +} + +func TestComplete(t *testing.T) { + // no key options provided should error + o := &InitRepositoryOptions{} + err := o.Complete(&fakeFactory{}) + assert.EqualError(t, err, errKeySizeTooSmall.Error()) + + // both KeyFile and KeyData provided should error + o = &InitRepositoryOptions{ + KeyFile: "/foo", + KeyData: "bar", + } + err = o.Complete(&fakeFactory{}) + assert.EqualError(t, err, errKeyFileAndKeyDataProvided.Error()) + + // TODO test that if KeyFile is provided, the data is used + // (should move the pkg/restore/FileSystem interface to + // a common location and use it here so ioutil.ReadFile + // can be easily mocked) + + // if KeyData is provided, it's used + o = &InitRepositoryOptions{ + KeyData: "bar", + } + assert.NoError(t, o.Complete(&fakeFactory{})) + assert.Equal(t, []byte(o.KeyData), o.keyBytes) + + // if KeySize is provided, a random key is generated + o = &InitRepositoryOptions{ + KeySize: 10, + } + assert.NoError(t, o.Complete(&fakeFactory{})) + assert.Equal(t, o.KeySize, len(o.keyBytes)) +} From 67b40c7fc8ccf5bb1eba29ccd383659455e4b509 Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Thu, 7 Jun 2018 10:29:59 -0700 Subject: [PATCH 2/2] use fake filesystem to test key file flag for ark restic init-repo Signed-off-by: Steve Kriss --- pkg/cmd/cli/restic/init_repository.go | 8 +- pkg/cmd/cli/restic/init_repository_test.go | 13 ++- pkg/restore/restore_test.go | 125 +++++---------------- pkg/util/filesystem/file_system.go | 76 +++++++++++++ pkg/util/test/fake_file_system.go | 70 ++++++++++++ 5 files changed, 190 insertions(+), 102 deletions(-) create mode 100644 pkg/util/filesystem/file_system.go create mode 100644 pkg/util/test/fake_file_system.go diff --git a/pkg/cmd/cli/restic/init_repository.go b/pkg/cmd/cli/restic/init_repository.go index 51b1b8b29..18fd77f82 100644 --- a/pkg/cmd/cli/restic/init_repository.go +++ b/pkg/cmd/cli/restic/init_repository.go @@ -18,11 +18,11 @@ package restic import ( "crypto/rand" - "io/ioutil" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" "github.com/heptio/ark/pkg/restic" + "github.com/heptio/ark/pkg/util/filesystem" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -55,13 +55,15 @@ type InitRepositoryOptions struct { KeyData string KeySize int + fileSystem filesystem.Interface kubeClient kclientset.Interface keyBytes []byte } func NewInitRepositoryOptions() *InitRepositoryOptions { return &InitRepositoryOptions{ - KeySize: 1024, + KeySize: 1024, + fileSystem: filesystem.NewFileSystem(), } } @@ -89,7 +91,7 @@ func (o *InitRepositoryOptions) Complete(f client.Factory) error { switch { case o.KeyFile != "": - data, err := ioutil.ReadFile(o.KeyFile) + data, err := o.fileSystem.ReadFile(o.KeyFile) if err != nil { return err } diff --git a/pkg/cmd/cli/restic/init_repository_test.go b/pkg/cmd/cli/restic/init_repository_test.go index 1b38eaefb..b8d3929e9 100644 --- a/pkg/cmd/cli/restic/init_repository_test.go +++ b/pkg/cmd/cli/restic/init_repository_test.go @@ -10,6 +10,7 @@ import ( "github.com/heptio/ark/pkg/client" clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" + arktest "github.com/heptio/ark/pkg/util/test" ) type fakeFactory struct{} @@ -46,10 +47,14 @@ func TestComplete(t *testing.T) { err = o.Complete(&fakeFactory{}) assert.EqualError(t, err, errKeyFileAndKeyDataProvided.Error()) - // TODO test that if KeyFile is provided, the data is used - // (should move the pkg/restore/FileSystem interface to - // a common location and use it here so ioutil.ReadFile - // can be easily mocked) + // if KeyFile is provided, its contents are used + fileContents := []byte("bar") + o = &InitRepositoryOptions{ + KeyFile: "/foo", + fileSystem: arktest.NewFakeFileSystem().WithFile("/foo", fileContents), + } + assert.NoError(t, o.Complete(&fakeFactory{})) + assert.Equal(t, fileContents, o.keyBytes) // if KeyData is provided, it's used o = &InitRepositoryOptions{ diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index ad5f19da7..0174c98d9 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -18,12 +18,9 @@ package restore import ( "encoding/json" - "io" - "os" "testing" "github.com/pkg/errors" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -120,7 +117,7 @@ func TestPrioritizeResources(t *testing.T) { func TestRestoreNamespaceFiltering(t *testing.T) { tests := []struct { name string - fileSystem *fakeFileSystem + fileSystem *arktest.FakeFileSystem baseDir string restore *api.Restore expectedReadDirs []string @@ -128,7 +125,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"), + fileSystem: arktest.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/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/a", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, @@ -139,7 +136,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"), + fileSystem: arktest.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/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, @@ -150,7 +147,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"), + fileSystem: arktest.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/resources", "bak/resources/nodes/cluster", "bak/resources/secrets/namespaces", "bak/resources/secrets/namespaces/b", "bak/resources/secrets/namespaces/c"}, @@ -161,7 +158,7 @@ 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"), + fileSystem: arktest.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{ @@ -197,7 +194,7 @@ func TestRestoreNamespaceFiltering(t *testing.T) { assert.Empty(t, errors.Ark) assert.Empty(t, errors.Cluster) assert.Empty(t, errors.Namespaces) - assert.Equal(t, test.expectedReadDirs, test.fileSystem.readDirCalls) + assert.Equal(t, test.expectedReadDirs, test.fileSystem.ReadDirCalls) }) } } @@ -205,7 +202,7 @@ func TestRestoreNamespaceFiltering(t *testing.T) { func TestRestorePriority(t *testing.T) { tests := []struct { name string - fileSystem *fakeFileSystem + fileSystem *arktest.FakeFileSystem restore *api.Restore baseDir string prioritizedResources []schema.GroupResource @@ -214,7 +211,7 @@ func TestRestorePriority(t *testing.T) { }{ { name: "cluster test", - fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), + fileSystem: arktest.NewFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), baseDir: "bak", restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, prioritizedResources: []schema.GroupResource{ @@ -226,7 +223,7 @@ func TestRestorePriority(t *testing.T) { }, { name: "resource priorities are applied", - fileSystem: newFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), + fileSystem: arktest.NewFakeFileSystem().WithDirectory("bak/resources/a/cluster").WithDirectory("bak/resources/c/cluster"), restore: &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}}}, baseDir: "bak", prioritizedResources: []schema.GroupResource{ @@ -238,7 +235,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"), + fileSystem: arktest.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{ @@ -250,7 +247,7 @@ func TestRestorePriority(t *testing.T) { }, { name: "error in a single resource doesn't terminate restore immediately, but is returned", - fileSystem: newFakeFileSystem(). + fileSystem: arktest.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{"*"}}}, @@ -288,7 +285,7 @@ func TestRestorePriority(t *testing.T) { assert.Empty(t, warnings.Namespaces) assert.Equal(t, test.expectedErrors, errors) - assert.Equal(t, test.expectedReadDirs, test.fileSystem.readDirCalls) + assert.Equal(t, test.expectedReadDirs, test.fileSystem.ReadDirCalls) }) } } @@ -299,7 +296,7 @@ func TestNamespaceRemapping(t *testing.T) { restore = &api.Restore{Spec: api.RestoreSpec{IncludedNamespaces: []string{"*"}, NamespaceMapping: map[string]string{"ns-1": "ns-2"}}} prioritizedResources = []schema.GroupResource{{Resource: "namespaces"}, {Resource: "configmaps"}} labelSelector = labels.NewSelector() - fileSystem = newFakeFileSystem(). + fileSystem = arktest.NewFakeFileSystem(). WithFile("bak/resources/configmaps/namespaces/ns-1/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()). WithFile("bak/resources/namespaces/cluster/ns-1.json", newTestNamespace("ns-1").ToJSON()) expectedNS = "ns-2" @@ -363,7 +360,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath string labelSelector labels.Selector includeClusterResources *bool - fileSystem *fakeFileSystem + fileSystem *arktest.FakeFileSystem actions []resolvedAction expectedErrors api.RestoreResult expectedObjs []unstructured.Unstructured @@ -373,7 +370,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem(). + fileSystem: arktest.NewFakeFileSystem(). WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()). WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()), expectedObjs: toUnstructured( @@ -385,7 +382,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { name: "no such directory causes error", namespace: "ns-1", resourcePath: "configmaps", - fileSystem: newFakeFileSystem(), + fileSystem: arktest.NewFakeFileSystem(), expectedErrors: api.RestoreResult{ Namespaces: map[string][]string{ "ns-1": {"error reading \"configmaps\" resource directory: open configmaps: file does not exist"}, @@ -396,14 +393,14 @@ func TestRestoreResourceForNamespace(t *testing.T) { name: "empty directory is no-op", namespace: "ns-1", resourcePath: "configmaps", - fileSystem: newFakeFileSystem().WithDirectory("configmaps"), + fileSystem: arktest.NewFakeFileSystem().WithDirectory("configmaps"), }, { name: "unmarshall failure does not cause immediate return", namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem(). + fileSystem: arktest.NewFakeFileSystem(). WithFile("configmaps/cm-1-invalid.json", []byte("this is not valid json")). WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()), expectedErrors: api.RestoreResult{ @@ -418,7 +415,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "bar"})), - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()), expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).WithArkLabel("my-restore").ConfigMap), }, { @@ -426,14 +423,14 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "not-bar"})), - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()), }, { name: "items with controller owner are skipped", namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem(). + fileSystem: arktest.NewFakeFileSystem(). WithFile("configmaps/cm-1.json", newTestConfigMap().WithControllerOwner().ToJSON()). WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()), expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").WithArkLabel("my-restore").ConfigMap), @@ -443,7 +440,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-2", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()), expectedObjs: toUnstructured(newTestConfigMap().WithNamespace("ns-2").WithArkLabel("my-restore").ConfigMap), }, { @@ -451,7 +448,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), actions: []resolvedAction{ { ItemAction: newFakeAction("configmaps"), @@ -467,7 +464,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { namespace: "ns-1", resourcePath: "configmaps", labelSelector: labels.NewSelector(), - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), actions: []resolvedAction{ { ItemAction: newFakeAction("foo-resource"), @@ -484,7 +481,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "persistentvolumes", labelSelector: labels.NewSelector(), includeClusterResources: falsePtr, - fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), }, { name: "namespaced resources are not skipped when IncludeClusterResources=false", @@ -492,7 +489,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "configmaps", labelSelector: labels.NewSelector(), includeClusterResources: falsePtr, - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), }, { @@ -501,7 +498,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "persistentvolumes", labelSelector: labels.NewSelector(), includeClusterResources: truePtr, - fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume), }, { @@ -510,7 +507,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "configmaps", labelSelector: labels.NewSelector(), includeClusterResources: truePtr, - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), }, { @@ -519,7 +516,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "persistentvolumes", labelSelector: labels.NewSelector(), includeClusterResources: nil, - fileSystem: newFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume), }, { @@ -528,7 +525,7 @@ func TestRestoreResourceForNamespace(t *testing.T) { resourcePath: "configmaps", labelSelector: labels.NewSelector(), includeClusterResources: nil, - fileSystem: newFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), + fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), }, } @@ -1210,68 +1207,6 @@ func (cm *testConfigMap) ToJSON() []byte { return bytes } -type fakeFileSystem struct { - fs afero.Fs - - readDirCalls []string -} - -func newFakeFileSystem() *fakeFileSystem { - return &fakeFileSystem{ - fs: afero.NewMemMapFs(), - } -} - -func (fs *fakeFileSystem) WithFile(path string, data []byte) *fakeFileSystem { - file, _ := fs.fs.Create(path) - file.Write(data) - file.Close() - - return fs -} - -func (fs *fakeFileSystem) WithDirectory(path string) *fakeFileSystem { - fs.fs.MkdirAll(path, 0755) - return fs -} - -func (fs *fakeFileSystem) WithDirectories(path ...string) *fakeFileSystem { - for _, dir := range path { - fs = fs.WithDirectory(dir) - } - - return fs -} - -func (fs *fakeFileSystem) TempDir(dir, prefix string) (string, error) { - return afero.TempDir(fs.fs, dir, prefix) -} - -func (fs *fakeFileSystem) MkdirAll(path string, perm os.FileMode) error { - return fs.fs.MkdirAll(path, perm) -} - -func (fs *fakeFileSystem) Create(name string) (io.WriteCloser, error) { - return fs.fs.Create(name) -} - -func (fs *fakeFileSystem) RemoveAll(path string) error { - return fs.fs.RemoveAll(path) -} - -func (fs *fakeFileSystem) ReadDir(dirname string) ([]os.FileInfo, error) { - fs.readDirCalls = append(fs.readDirCalls, dirname) - return afero.ReadDir(fs.fs, dirname) -} - -func (fs *fakeFileSystem) ReadFile(filename string) ([]byte, error) { - return afero.ReadFile(fs.fs, filename) -} - -func (fs *fakeFileSystem) DirExists(path string) (bool, error) { - return afero.DirExists(fs.fs, path) -} - type fakeAction struct { resource string } diff --git a/pkg/util/filesystem/file_system.go b/pkg/util/filesystem/file_system.go new file mode 100644 index 000000000..b1b9d857f --- /dev/null +++ b/pkg/util/filesystem/file_system.go @@ -0,0 +1,76 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "io" + "io/ioutil" + "os" +) + +// Interface defines methods for interacting with an +// underlying file system. +type Interface interface { + TempDir(dir, prefix string) (string, error) + MkdirAll(path string, perm os.FileMode) error + Create(name string) (io.WriteCloser, error) + RemoveAll(path string) error + ReadDir(dirname string) ([]os.FileInfo, error) + ReadFile(filename string) ([]byte, error) + DirExists(path string) (bool, error) +} + +func NewFileSystem() Interface { + return &osFileSystem{} +} + +type osFileSystem struct{} + +func (fs *osFileSystem) TempDir(dir, prefix string) (string, error) { + return ioutil.TempDir(dir, prefix) +} + +func (fs *osFileSystem) MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} + +func (fs *osFileSystem) Create(name string) (io.WriteCloser, error) { + return os.Create(name) +} + +func (fs *osFileSystem) RemoveAll(path string) error { + return os.RemoveAll(path) +} + +func (fs *osFileSystem) ReadDir(dirname string) ([]os.FileInfo, error) { + return ioutil.ReadDir(dirname) +} + +func (fs *osFileSystem) ReadFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filename) +} + +func (fs *osFileSystem) DirExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} diff --git a/pkg/util/test/fake_file_system.go b/pkg/util/test/fake_file_system.go new file mode 100644 index 000000000..0603bba30 --- /dev/null +++ b/pkg/util/test/fake_file_system.go @@ -0,0 +1,70 @@ +package test + +import ( + "io" + "os" + + "github.com/spf13/afero" +) + +type FakeFileSystem struct { + fs afero.Fs + + ReadDirCalls []string +} + +func NewFakeFileSystem() *FakeFileSystem { + return &FakeFileSystem{ + fs: afero.NewMemMapFs(), + } +} + +func (fs *FakeFileSystem) TempDir(dir, prefix string) (string, error) { + return afero.TempDir(fs.fs, dir, prefix) +} + +func (fs *FakeFileSystem) MkdirAll(path string, perm os.FileMode) error { + return fs.fs.MkdirAll(path, perm) +} + +func (fs *FakeFileSystem) Create(name string) (io.WriteCloser, error) { + return fs.fs.Create(name) +} + +func (fs *FakeFileSystem) RemoveAll(path string) error { + return fs.fs.RemoveAll(path) +} + +func (fs *FakeFileSystem) ReadDir(dirname string) ([]os.FileInfo, error) { + fs.ReadDirCalls = append(fs.ReadDirCalls, dirname) + return afero.ReadDir(fs.fs, dirname) +} + +func (fs *FakeFileSystem) ReadFile(filename string) ([]byte, error) { + return afero.ReadFile(fs.fs, filename) +} + +func (fs *FakeFileSystem) DirExists(path string) (bool, error) { + return afero.DirExists(fs.fs, path) +} + +func (fs *FakeFileSystem) WithFile(path string, data []byte) *FakeFileSystem { + file, _ := fs.fs.Create(path) + file.Write(data) + file.Close() + + return fs +} + +func (fs *FakeFileSystem) WithDirectory(path string) *FakeFileSystem { + fs.fs.MkdirAll(path, 0755) + return fs +} + +func (fs *FakeFileSystem) WithDirectories(path ...string) *FakeFileSystem { + for _, dir := range path { + fs = fs.WithDirectory(dir) + } + + return fs +}