diff --git a/src/restic/backend/layout.go b/src/restic/backend/layout.go index c8b795348..cff77e25a 100644 --- a/src/restic/backend/layout.go +++ b/src/restic/backend/layout.go @@ -108,8 +108,13 @@ func hasSubdirBackendFile(fs Filesystem, dir string) (bool, error) { } // DetectLayout tries to find out which layout is used in a local (or sftp) -// filesystem at the given path. +// filesystem at the given path. If repo is nil, an instance of LocalFilesystem +// is used. func DetectLayout(repo Filesystem, dir string) (Layout, error) { + if repo == nil { + repo = &LocalFilesystem{} + } + // key file in the "keys" dir (DefaultLayout or CloudLayout) foundKeysFile, err := hasBackendFile(repo, repo.Join(dir, defaultLayoutPaths[restic.KeyFile])) if err != nil { @@ -148,3 +153,36 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { return nil, errors.New("auto-detecting the filesystem layout failed") } + +// ParseLayout parses the config string and returns a Layout. When layout is +// the empty string, DetectLayout is used. If repo is nil, an instance of LocalFilesystem +// is used. +func ParseLayout(repo Filesystem, layout, path string) (l Layout, err error) { + if repo == nil { + repo = &LocalFilesystem{} + } + + switch layout { + case "default": + l = &DefaultLayout{ + Path: path, + Join: repo.Join, + } + case "cloud": + l = &CloudLayout{ + Path: path, + Join: repo.Join, + } + case "s3": + l = &S3Layout{ + Path: path, + Join: repo.Join, + } + case "": + return DetectLayout(repo, path) + default: + return nil, errors.Errorf("unknown backend layout string %q, may be one of default/cloud/s3", layout) + } + + return l, nil +} diff --git a/src/restic/backend/layout_test.go b/src/restic/backend/layout_test.go index c08829ea4..3fe9dcf6e 100644 --- a/src/restic/backend/layout_test.go +++ b/src/restic/backend/layout_test.go @@ -229,10 +229,49 @@ func TestDetectLayout(t *testing.T) { var fs = &LocalFilesystem{} for _, test := range tests { - t.Run(test.filename, func(t *testing.T) { - SetupTarTestFixture(t, path, filepath.Join("testdata", test.filename)) + for _, fs := range []Filesystem{fs, nil} { + t.Run(fmt.Sprintf("%v/fs-%T", test.filename, fs), func(t *testing.T) { + SetupTarTestFixture(t, path, filepath.Join("testdata", test.filename)) - layout, err := DetectLayout(fs, filepath.Join(path, "repo")) + layout, err := DetectLayout(fs, filepath.Join(path, "repo")) + if err != nil { + t.Fatal(err) + } + + if layout == nil { + t.Fatal("wanted some layout, but detect returned nil") + } + + layoutName := fmt.Sprintf("%T", layout) + if layoutName != test.want { + t.Fatalf("want layout %v, got %v", test.want, layoutName) + } + + RemoveAll(t, filepath.Join(path, "repo")) + }) + } + } +} + +func TestParseLayout(t *testing.T) { + path, cleanup := TempDir(t) + defer cleanup() + + var tests = []struct { + layoutName string + want string + }{ + {"default", "*backend.DefaultLayout"}, + {"cloud", "*backend.CloudLayout"}, + {"s3", "*backend.S3Layout"}, + {"", "*backend.CloudLayout"}, + } + + SetupTarTestFixture(t, path, filepath.Join("testdata", "repo-layout-cloud.tar.gz")) + + for _, test := range tests { + t.Run(test.layoutName, func(t *testing.T) { + layout, err := ParseLayout(nil, test.layoutName, filepath.Join(path, "repo")) if err != nil { t.Fatal(err) } @@ -245,8 +284,24 @@ func TestDetectLayout(t *testing.T) { if layoutName != test.want { t.Fatalf("want layout %v, got %v", test.want, layoutName) } - - RemoveAll(t, filepath.Join(path, "repo")) + }) + } +} + +func TestParseLayoutInvalid(t *testing.T) { + path, cleanup := TempDir(t) + defer cleanup() + + var invalidNames = []string{ + "foo", "bar", "local", + } + + for _, name := range invalidNames { + t.Run(name, func(t *testing.T) { + layout, err := ParseLayout(nil, name, path) + if err == nil { + t.Fatalf("expected error not found for layout name %v, layout is %v", name, layout) + } }) } } diff --git a/src/restic/backend/local/config.go b/src/restic/backend/local/config.go index 746accd27..4acd57a13 100644 --- a/src/restic/backend/local/config.go +++ b/src/restic/backend/local/config.go @@ -8,7 +8,8 @@ import ( // Config holds all information needed to open a local repository. type Config struct { - Path string + Path string + Layout string } // ParseConfig parses a local backend config. diff --git a/src/restic/backend/local/local.go b/src/restic/backend/local/local.go index 4ddada847..1f536e7d9 100644 --- a/src/restic/backend/local/local.go +++ b/src/restic/backend/local/local.go @@ -24,13 +24,13 @@ var _ restic.Backend = &Local{} // Open opens the local backend as specified by config. func Open(cfg Config) (*Local, error) { - be := &Local{Config: cfg} - - be.Layout = &backend.DefaultLayout{ - Path: cfg.Path, - Join: filepath.Join, + l, err := backend.ParseLayout(nil, cfg.Layout, cfg.Path) + if err != nil { + return nil, err } + be := &Local{Config: cfg, Layout: l} + // test if all necessary dirs are there for _, d := range be.Paths() { if _, err := fs.Stat(d); err != nil {