Add layout auto detection

This commit is contained in:
Alexander Neumann 2017-04-02 17:25:22 +02:00
parent 3e81dcdfc2
commit c6b8ffbb61
5 changed files with 179 additions and 4 deletions

View file

@ -1,7 +1,13 @@
package backend
import (
"fmt"
"os"
"path/filepath"
"regexp"
"restic"
"restic/errors"
"restic/fs"
)
// Layout computes paths for file name storage.
@ -10,3 +16,135 @@ type Layout interface {
Dirname(restic.Handle) string
Paths() []string
}
// Filesystem is the abstraction of a file system used for a backend.
type Filesystem interface {
Join(...string) string
ReadDir(string) ([]os.FileInfo, error)
}
// ensure statically that *LocalFilesystem implements Filesystem.
var _ Filesystem = &LocalFilesystem{}
// LocalFilesystem implements Filesystem in a local path.
type LocalFilesystem struct {
}
// ReadDir returns all entries of a directory.
func (l *LocalFilesystem) ReadDir(dir string) ([]os.FileInfo, error) {
f, err := fs.Open(dir)
if err != nil {
return nil, err
}
entries, err := f.Readdir(-1)
if err != nil {
return nil, err
}
err = f.Close()
if err != nil {
return nil, err
}
return entries, nil
}
// Join combines several path components to one.
func (l *LocalFilesystem) Join(paths ...string) string {
return filepath.Join(paths...)
}
var backendFilenameLength = len(restic.ID{}) * 2
var backendFilename = regexp.MustCompile(fmt.Sprintf("^[a-fA-F0-9]{%d}$", backendFilenameLength))
func hasBackendFile(fs Filesystem, dir string) (bool, error) {
entries, err := fs.ReadDir(dir)
if err != nil && os.IsNotExist(errors.Cause(err)) {
return false, nil
}
if err != nil {
return false, errors.Wrap(err, "ReadDir")
}
for _, e := range entries {
if backendFilename.MatchString(e.Name()) {
return true, nil
}
}
return false, nil
}
var dataSubdirName = regexp.MustCompile("^[a-fA-F0-9]{2}$")
func hasSubdirBackendFile(fs Filesystem, dir string) (bool, error) {
entries, err := fs.ReadDir(dir)
if err != nil && os.IsNotExist(errors.Cause(err)) {
return false, nil
}
if err != nil {
return false, errors.Wrap(err, "ReadDir")
}
for _, subdir := range entries {
if !dataSubdirName.MatchString(subdir.Name()) {
continue
}
present, err := hasBackendFile(fs, fs.Join(dir, subdir.Name()))
if err != nil {
return false, err
}
if present {
return true, nil
}
}
return false, nil
}
// DetectLayout tries to find out which layout is used in a local (or sftp)
// filesystem at the given path.
func DetectLayout(repo Filesystem, dir string) (Layout, error) {
// key file in the "keys" dir (DefaultLayout or CloudLayout)
foundKeysFile, err := hasBackendFile(repo, repo.Join(dir, defaultLayoutPaths[restic.KeyFile]))
if err != nil {
return nil, err
}
// key file in the "key" dir (S3Layout)
foundKeyFile, err := hasBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.KeyFile]))
if err != nil {
return nil, err
}
// data file in "data" directory (S3Layout or CloudLayout)
foundDataFile, err := hasBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.DataFile]))
if err != nil {
return nil, err
}
// data file in subdir of "data" directory (DefaultLayout)
foundDataSubdirFile, err := hasSubdirBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.DataFile]))
if err != nil {
return nil, err
}
if foundKeysFile && foundDataFile && !foundKeyFile && !foundDataSubdirFile {
return &CloudLayout{}, nil
}
if foundKeysFile && foundDataSubdirFile && !foundKeyFile && !foundDataFile {
return &DefaultLayout{}, nil
}
if foundKeyFile && foundDataFile && !foundKeysFile && !foundDataSubdirFile {
return &S3Layout{}, nil
}
return nil, errors.New("auto-detecting the filesystem layout failed")
}

View file

@ -5,13 +5,13 @@ import (
"path/filepath"
"reflect"
"restic"
"restic/test"
. "restic/test"
"sort"
"testing"
)
func TestDefaultLayout(t *testing.T) {
path, cleanup := test.TempDir(t)
path, cleanup := TempDir(t)
defer cleanup()
var tests = []struct {
@ -79,7 +79,7 @@ func TestDefaultLayout(t *testing.T) {
}
func TestCloudLayout(t *testing.T) {
path, cleanup := test.TempDir(t)
path, cleanup := TempDir(t)
defer cleanup()
var tests = []struct {
@ -147,7 +147,7 @@ func TestCloudLayout(t *testing.T) {
}
func TestS3Layout(t *testing.T) {
path, cleanup := test.TempDir(t)
path, cleanup := TempDir(t)
defer cleanup()
var tests = []struct {
@ -213,3 +213,40 @@ func TestS3Layout(t *testing.T) {
})
}
}
func TestDetectLayout(t *testing.T) {
path, cleanup := TempDir(t)
defer cleanup()
var tests = []struct {
filename string
want string
}{
{"repo-layout-local.tar.gz", "*backend.DefaultLayout"},
{"repo-layout-cloud.tar.gz", "*backend.CloudLayout"},
{"repo-layout-s3-old.tar.gz", "*backend.S3Layout"},
}
var fs = &LocalFilesystem{}
for _, test := range tests {
t.Run(test.filename, func(t *testing.T) {
SetupTarTestFixture(t, path, filepath.Join("testdata", test.filename))
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"))
})
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.