forked from TrueCloudLab/restic
Add layout auto detection
This commit is contained in:
parent
3e81dcdfc2
commit
c6b8ffbb61
5 changed files with 179 additions and 4 deletions
|
@ -1,7 +1,13 @@
|
||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"restic"
|
"restic"
|
||||||
|
"restic/errors"
|
||||||
|
"restic/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Layout computes paths for file name storage.
|
// Layout computes paths for file name storage.
|
||||||
|
@ -10,3 +16,135 @@ type Layout interface {
|
||||||
Dirname(restic.Handle) string
|
Dirname(restic.Handle) string
|
||||||
Paths() []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")
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"restic"
|
"restic"
|
||||||
"restic/test"
|
. "restic/test"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultLayout(t *testing.T) {
|
func TestDefaultLayout(t *testing.T) {
|
||||||
path, cleanup := test.TempDir(t)
|
path, cleanup := TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
|
@ -79,7 +79,7 @@ func TestDefaultLayout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCloudLayout(t *testing.T) {
|
func TestCloudLayout(t *testing.T) {
|
||||||
path, cleanup := test.TempDir(t)
|
path, cleanup := TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
|
@ -147,7 +147,7 @@ func TestCloudLayout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestS3Layout(t *testing.T) {
|
func TestS3Layout(t *testing.T) {
|
||||||
path, cleanup := test.TempDir(t)
|
path, cleanup := TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
var tests = []struct {
|
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"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
src/restic/backend/testdata/repo-layout-cloud.tar.gz
vendored
Normal file
BIN
src/restic/backend/testdata/repo-layout-cloud.tar.gz
vendored
Normal file
Binary file not shown.
BIN
src/restic/backend/testdata/repo-layout-local.tar.gz
vendored
Normal file
BIN
src/restic/backend/testdata/repo-layout-local.tar.gz
vendored
Normal file
Binary file not shown.
BIN
src/restic/backend/testdata/repo-layout-s3-old.tar.gz
vendored
Normal file
BIN
src/restic/backend/testdata/repo-layout-s3-old.tar.gz
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue