From a4e3a0dd97d2dad13c64ef8965c62915b520db1c Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 21:51:19 +0200 Subject: [PATCH 01/10] design: Remove cloud layout --- doc/design.rst | 53 +++++++++++++------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/doc/design.rst b/doc/design.rst index c3f32a67c..02b0992fb 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -76,8 +76,8 @@ identifies the repository, regardless if it is accessed via SFTP or locally. The field ``chunker_polynomial`` contains a parameter that is used for splitting large files into smaller chunks (see below). -Filesystem-Based Repositories -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Repository Layout +~~~~~~~~~~~~~~~~~ The ``local`` and ``sftp`` backends are implemented using files and directories stored in a file system. The directory layout is the same @@ -117,44 +117,19 @@ e.g.: $ restic -r /tmp/restic-repo init -The local and sftp backends will also accept the repository layout -described in the following section, so that remote repositories mounted -locally e.g. via fuse can be accessed. The layout auto-detection can be -overridden by specifying the option ``-o local.layout=default``, valid -values are ``default``, ``cloud`` and ``s3``. The option for the sftp -backend is named ``sftp.layout``. +The local and sftp backends will auto-detect and accept all layouts described +in the following sections, so that remote repositories mounted locally e.g. via +fuse can be accessed. The layout auto-detection can be overridden by specifying +the option ``-o local.layout=default``, valid values are ``default`` and +``s3legacy``. The option for the sftp backend is named ``sftp.layout``. -Object-Storage-Based Repositories -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +S3 Legacy Layout +~~~~~~~~~~~~~~~~ -Repositories in a backend based on an object store (e.g. Amazon s3) have -the same basic layout, with the exception that all data pack files are -directly saved in the ``data`` path, without the sub-directories listed -for the filesystem-based backends as listed in the previous section. The -layout looks like this: - -:: - - /config - /data - ├── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1 - ├── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5 - ├── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426 - ├── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c - [...] - /index - ├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d - └── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd - /keys - └── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7 - /locks - /snapshots - └── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec - -Unfortunately during development the s3 backend uses slightly different +Unfortunately during development the AWS S3 backend uses slightly different paths (directory names use singular instead of plural for ``key``, -``lock``, and ``snapshot`` files), for s3 the repository layout looks -like this: +``lock``, and ``snapshot`` files), and the data files are stored directly below +the ``data`` directory. The S3 Legacy repository layout looks like this: :: @@ -174,8 +149,8 @@ like this: /snapshot └── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec -The s3 backend understands and accepts both forms, new backends are -always created with the former layout for compatibility reasons. +The S3 backend understands and accepts both forms, new backends are +always created with the default layout for compatibility reasons. Pack Format ----------- From 61cade62229a3537ff1a8381eac59a690232328b Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 21:58:03 +0200 Subject: [PATCH 02/10] Rename layout s3 -> s3legacy --- src/restic/backend/layout.go | 8 ++++---- .../{layout_s3.go => layout_s3legacy.go} | 14 ++++++------- src/restic/backend/layout_test.go | 20 +++++++++---------- src/restic/backend/s3/s3.go | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) rename src/restic/backend/{layout_s3.go => layout_s3legacy.go} (74%) diff --git a/src/restic/backend/layout.go b/src/restic/backend/layout.go index de72f07d4..1fa110760 100644 --- a/src/restic/backend/layout.go +++ b/src/restic/backend/layout.go @@ -170,7 +170,7 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { if foundKeyFile && foundDataFile && !foundKeysFile && !foundDataSubdirFile { debug.Log("found s3 layout at %v", dir) - return &S3Layout{ + return &S3LegacyLayout{ Path: dir, Join: repo.Join, }, nil @@ -195,8 +195,8 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, Path: path, Join: repo.Join, } - case "s3": - l = &S3Layout{ + case "s3legacy": + l = &S3LegacyLayout{ Path: path, Join: repo.Join, } @@ -214,7 +214,7 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, } debug.Log("layout detected: %v", l) default: - return nil, errors.Errorf("unknown backend layout string %q, may be one of: default, cloud, s3", layout) + return nil, errors.Errorf("unknown backend layout string %q, may be one of: default, cloud, s3legacy", layout) } return l, nil diff --git a/src/restic/backend/layout_s3.go b/src/restic/backend/layout_s3legacy.go similarity index 74% rename from src/restic/backend/layout_s3.go rename to src/restic/backend/layout_s3legacy.go index 91273d63a..601d29bc5 100644 --- a/src/restic/backend/layout_s3.go +++ b/src/restic/backend/layout_s3legacy.go @@ -2,9 +2,9 @@ package backend import "restic" -// S3Layout implements the old layout used for s3 cloud storage backends, as +// S3LegacyLayout implements the old layout used for s3 cloud storage backends, as // described in the Design document. -type S3Layout struct { +type S3LegacyLayout struct { URL string Path string Join func(...string) string @@ -19,7 +19,7 @@ var s3LayoutPaths = map[restic.FileType]string{ } // join calls Join with the first empty elements removed. -func (l *S3Layout) join(url string, items ...string) string { +func (l *S3LegacyLayout) join(url string, items ...string) string { for len(items) > 0 && items[0] == "" { items = items[1:] } @@ -35,7 +35,7 @@ func (l *S3Layout) join(url string, items ...string) string { } // Dirname returns the directory path for a given file type and name. -func (l *S3Layout) Dirname(h restic.Handle) string { +func (l *S3LegacyLayout) Dirname(h restic.Handle) string { if h.Type == restic.ConfigFile { return l.URL + l.Join(l.Path, "/") } @@ -44,7 +44,7 @@ func (l *S3Layout) Dirname(h restic.Handle) string { } // Filename returns a path to a file, including its name. -func (l *S3Layout) Filename(h restic.Handle) string { +func (l *S3LegacyLayout) Filename(h restic.Handle) string { name := h.Name if h.Type == restic.ConfigFile { @@ -55,7 +55,7 @@ func (l *S3Layout) Filename(h restic.Handle) string { } // Paths returns all directory names -func (l *S3Layout) Paths() (dirs []string) { +func (l *S3LegacyLayout) Paths() (dirs []string) { for _, p := range s3LayoutPaths { dirs = append(dirs, l.Join(l.Path, p)) } @@ -63,6 +63,6 @@ func (l *S3Layout) Paths() (dirs []string) { } // Basedir returns the base dir name for type t. -func (l *S3Layout) Basedir(t restic.FileType) string { +func (l *S3LegacyLayout) Basedir(t restic.FileType) string { return l.Join(l.Path, s3LayoutPaths[t]) } diff --git a/src/restic/backend/layout_test.go b/src/restic/backend/layout_test.go index 606da3241..4c296f29e 100644 --- a/src/restic/backend/layout_test.go +++ b/src/restic/backend/layout_test.go @@ -173,37 +173,37 @@ func TestCloudLayoutURLs(t *testing.T) { "https://hostname.foo:1234/prefix/repo/", }, { - &S3Layout{URL: "https://hostname.foo", Path: "/", Join: path.Join}, + &S3LegacyLayout{URL: "https://hostname.foo", Path: "/", Join: path.Join}, restic.Handle{Type: restic.DataFile, Name: "foobar"}, "https://hostname.foo/data/foobar", "https://hostname.foo/data/", }, { - &S3Layout{URL: "https://hostname.foo:1234/prefix/repo", Path: "", Join: path.Join}, + &S3LegacyLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "", Join: path.Join}, restic.Handle{Type: restic.LockFile, Name: "foobar"}, "https://hostname.foo:1234/prefix/repo/lock/foobar", "https://hostname.foo:1234/prefix/repo/lock/", }, { - &S3Layout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, + &S3LegacyLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, restic.Handle{Type: restic.ConfigFile, Name: "foobar"}, "https://hostname.foo:1234/prefix/repo/config", "https://hostname.foo:1234/prefix/repo/", }, { - &S3Layout{URL: "", Path: "", Join: path.Join}, + &S3LegacyLayout{URL: "", Path: "", Join: path.Join}, restic.Handle{Type: restic.DataFile, Name: "foobar"}, "data/foobar", "data/", }, { - &S3Layout{URL: "", Path: "", Join: path.Join}, + &S3LegacyLayout{URL: "", Path: "", Join: path.Join}, restic.Handle{Type: restic.LockFile, Name: "foobar"}, "lock/foobar", "lock/", }, { - &S3Layout{URL: "", Path: "/", Join: path.Join}, + &S3LegacyLayout{URL: "", Path: "/", Join: path.Join}, restic.Handle{Type: restic.ConfigFile, Name: "foobar"}, "/config", "/", @@ -225,7 +225,7 @@ func TestCloudLayoutURLs(t *testing.T) { } } -func TestS3Layout(t *testing.T) { +func TestS3LegacyLayout(t *testing.T) { path, cleanup := TempDir(t) defer cleanup() @@ -259,7 +259,7 @@ func TestS3Layout(t *testing.T) { }, } - l := &S3Layout{ + l := &S3LegacyLayout{ Path: path, Join: filepath.Join, } @@ -303,7 +303,7 @@ func TestDetectLayout(t *testing.T) { }{ {"repo-layout-local.tar.gz", "*backend.DefaultLayout"}, {"repo-layout-cloud.tar.gz", "*backend.CloudLayout"}, - {"repo-layout-s3-old.tar.gz", "*backend.S3Layout"}, + {"repo-layout-s3-old.tar.gz", "*backend.S3LegacyLayout"}, } var fs = &LocalFilesystem{} @@ -343,7 +343,7 @@ func TestParseLayout(t *testing.T) { }{ {"default", "", "*backend.DefaultLayout"}, {"cloud", "", "*backend.CloudLayout"}, - {"s3", "", "*backend.S3Layout"}, + {"s3legacy", "", "*backend.S3LegacyLayout"}, {"", "", "*backend.CloudLayout"}, } diff --git a/src/restic/backend/s3/s3.go b/src/restic/backend/s3/s3.go index 9afd13df5..241bfe323 100644 --- a/src/restic/backend/s3/s3.go +++ b/src/restic/backend/s3/s3.go @@ -45,7 +45,7 @@ func Open(cfg Config) (restic.Backend, error) { bucketname: cfg.Bucket, prefix: cfg.Prefix, cacheObjSize: make(map[string]int64), - Layout: &backend.S3Layout{Path: cfg.Prefix, Join: path.Join}, + Layout: &backend.S3LegacyLayout{Path: cfg.Prefix, Join: path.Join}, } client.SetCustomTransport(backend.Transport()) From f19852a738648e4e71023e5c6206c26f93229637 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 22:01:49 +0200 Subject: [PATCH 03/10] Rename CloudLayout -> RESTLayout The REST backend uses a special layout without subdirs below data/. This layout is just used there and nowhere else, and our REST server implementation uses the default layout for the on disk storage. So we remove the REST layout from the auto detection code. --- src/restic/backend/layout.go | 21 ++------ src/restic/backend/layout_cloud.go | 46 ------------------ src/restic/backend/layout_rest.go | 45 +++++++++++++++++ src/restic/backend/layout_test.go | 18 +++---- src/restic/backend/local/layout_test.go | 5 -- src/restic/backend/rest/rest.go | 2 +- src/restic/backend/sftp/layout_test.go | 5 -- .../backend/testdata/repo-layout-cloud.tar.gz | Bin 38095 -> 0 bytes 8 files changed, 58 insertions(+), 84 deletions(-) delete mode 100644 src/restic/backend/layout_cloud.go create mode 100644 src/restic/backend/layout_rest.go delete mode 100644 src/restic/backend/testdata/repo-layout-cloud.tar.gz diff --git a/src/restic/backend/layout.go b/src/restic/backend/layout.go index 1fa110760..38990e044 100644 --- a/src/restic/backend/layout.go +++ b/src/restic/backend/layout.go @@ -128,19 +128,19 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { repo = &LocalFilesystem{} } - // key file in the "keys" dir (DefaultLayout or CloudLayout) + // key file in the "keys" dir (DefaultLayout) foundKeysFile, err := hasBackendFile(repo, repo.Join(dir, defaultLayoutPaths[restic.KeyFile])) if err != nil { return nil, err } - // key file in the "key" dir (S3Layout) + // key file in the "key" dir (S3LegacyLayout) foundKeyFile, err := hasBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.KeyFile])) if err != nil { return nil, err } - // data file in "data" directory (S3Layout or CloudLayout) + // data file in "data" directory (S3LegacyLayout) foundDataFile, err := hasBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.DataFile])) if err != nil { return nil, err @@ -152,14 +152,6 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { return nil, err } - if foundKeysFile && foundDataFile && !foundKeyFile && !foundDataSubdirFile { - debug.Log("found cloud layout at %v", dir) - return &CloudLayout{ - Path: dir, - Join: repo.Join, - }, nil - } - if foundKeysFile && foundDataSubdirFile && !foundKeyFile && !foundDataFile { debug.Log("found default layout at %v", dir) return &DefaultLayout{ @@ -190,11 +182,6 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, Path: path, Join: repo.Join, } - case "cloud": - l = &CloudLayout{ - Path: path, - Join: repo.Join, - } case "s3legacy": l = &S3LegacyLayout{ Path: path, @@ -214,7 +201,7 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, } debug.Log("layout detected: %v", l) default: - return nil, errors.Errorf("unknown backend layout string %q, may be one of: default, cloud, s3legacy", layout) + return nil, errors.Errorf("unknown backend layout string %q, may be one of: default, s3legacy", layout) } return l, nil diff --git a/src/restic/backend/layout_cloud.go b/src/restic/backend/layout_cloud.go deleted file mode 100644 index 6331d4709..000000000 --- a/src/restic/backend/layout_cloud.go +++ /dev/null @@ -1,46 +0,0 @@ -package backend - -import "restic" - -// CloudLayout implements the default layout for cloud storage backends, as -// described in the Design document. -type CloudLayout struct { - URL string - Path string - Join func(...string) string -} - -var cloudLayoutPaths = defaultLayoutPaths - -// Dirname returns the directory path for a given file type and name. -func (l *CloudLayout) Dirname(h restic.Handle) string { - if h.Type == restic.ConfigFile { - return l.URL + l.Join(l.Path, "/") - } - - return l.URL + l.Join(l.Path, "/", cloudLayoutPaths[h.Type]) + "/" -} - -// Filename returns a path to a file, including its name. -func (l *CloudLayout) Filename(h restic.Handle) string { - name := h.Name - - if h.Type == restic.ConfigFile { - name = "config" - } - - return l.URL + l.Join(l.Path, "/", cloudLayoutPaths[h.Type], name) -} - -// Paths returns all directory names -func (l *CloudLayout) Paths() (dirs []string) { - for _, p := range cloudLayoutPaths { - dirs = append(dirs, l.URL+l.Join(l.Path, p)) - } - return dirs -} - -// Basedir returns the base dir name for files of type t. -func (l *CloudLayout) Basedir(t restic.FileType) string { - return l.URL + l.Join(l.Path, cloudLayoutPaths[t]) -} diff --git a/src/restic/backend/layout_rest.go b/src/restic/backend/layout_rest.go new file mode 100644 index 000000000..2d01ece79 --- /dev/null +++ b/src/restic/backend/layout_rest.go @@ -0,0 +1,45 @@ +package backend + +import "restic" + +// RESTLayout implements the default layout for the REST protocol. +type RESTLayout struct { + URL string + Path string + Join func(...string) string +} + +var restLayoutPaths = defaultLayoutPaths + +// Dirname returns the directory path for a given file type and name. +func (l *RESTLayout) Dirname(h restic.Handle) string { + if h.Type == restic.ConfigFile { + return l.URL + l.Join(l.Path, "/") + } + + return l.URL + l.Join(l.Path, "/", restLayoutPaths[h.Type]) + "/" +} + +// Filename returns a path to a file, including its name. +func (l *RESTLayout) Filename(h restic.Handle) string { + name := h.Name + + if h.Type == restic.ConfigFile { + name = "config" + } + + return l.URL + l.Join(l.Path, "/", restLayoutPaths[h.Type], name) +} + +// Paths returns all directory names +func (l *RESTLayout) Paths() (dirs []string) { + for _, p := range restLayoutPaths { + dirs = append(dirs, l.URL+l.Join(l.Path, p)) + } + return dirs +} + +// Basedir returns the base dir name for files of type t. +func (l *RESTLayout) Basedir(t restic.FileType) string { + return l.URL + l.Join(l.Path, restLayoutPaths[t]) +} diff --git a/src/restic/backend/layout_test.go b/src/restic/backend/layout_test.go index 4c296f29e..11e33ca98 100644 --- a/src/restic/backend/layout_test.go +++ b/src/restic/backend/layout_test.go @@ -79,7 +79,7 @@ func TestDefaultLayout(t *testing.T) { } } -func TestCloudLayout(t *testing.T) { +func TestRESTLayout(t *testing.T) { path, cleanup := TempDir(t) defer cleanup() @@ -113,7 +113,7 @@ func TestCloudLayout(t *testing.T) { }, } - l := &CloudLayout{ + l := &RESTLayout{ Path: path, Join: filepath.Join, } @@ -147,7 +147,7 @@ func TestCloudLayout(t *testing.T) { } } -func TestCloudLayoutURLs(t *testing.T) { +func TestRESTLayoutURLs(t *testing.T) { var tests = []struct { l Layout h restic.Handle @@ -155,19 +155,19 @@ func TestCloudLayoutURLs(t *testing.T) { dir string }{ { - &CloudLayout{URL: "https://hostname.foo", Path: "", Join: path.Join}, + &RESTLayout{URL: "https://hostname.foo", Path: "", Join: path.Join}, restic.Handle{Type: restic.DataFile, Name: "foobar"}, "https://hostname.foo/data/foobar", "https://hostname.foo/data/", }, { - &CloudLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, + &RESTLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, restic.Handle{Type: restic.LockFile, Name: "foobar"}, "https://hostname.foo:1234/prefix/repo/locks/foobar", "https://hostname.foo:1234/prefix/repo/locks/", }, { - &CloudLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, + &RESTLayout{URL: "https://hostname.foo:1234/prefix/repo", Path: "/", Join: path.Join}, restic.Handle{Type: restic.ConfigFile, Name: "foobar"}, "https://hostname.foo:1234/prefix/repo/config", "https://hostname.foo:1234/prefix/repo/", @@ -302,7 +302,6 @@ func TestDetectLayout(t *testing.T) { want string }{ {"repo-layout-local.tar.gz", "*backend.DefaultLayout"}, - {"repo-layout-cloud.tar.gz", "*backend.CloudLayout"}, {"repo-layout-s3-old.tar.gz", "*backend.S3LegacyLayout"}, } @@ -342,12 +341,11 @@ func TestParseLayout(t *testing.T) { want string }{ {"default", "", "*backend.DefaultLayout"}, - {"cloud", "", "*backend.CloudLayout"}, {"s3legacy", "", "*backend.S3LegacyLayout"}, - {"", "", "*backend.CloudLayout"}, + {"", "", "*backend.DefaultLayout"}, } - SetupTarTestFixture(t, path, filepath.Join("testdata", "repo-layout-cloud.tar.gz")) + SetupTarTestFixture(t, path, filepath.Join("testdata", "repo-layout-local.tar.gz")) for _, test := range tests { t.Run(test.layoutName, func(t *testing.T) { diff --git a/src/restic/backend/local/layout_test.go b/src/restic/backend/local/layout_test.go index da0b0bfc8..c5b9427a8 100644 --- a/src/restic/backend/local/layout_test.go +++ b/src/restic/backend/local/layout_test.go @@ -22,11 +22,6 @@ func TestLayout(t *testing.T) { "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, }}, - {"repo-layout-cloud.tar.gz", "", false, map[string]bool{ - "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, - "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, - "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, - }}, {"repo-layout-s3-old.tar.gz", "", false, map[string]bool{ "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, diff --git a/src/restic/backend/rest/rest.go b/src/restic/backend/rest/rest.go index 5a0e6226b..db1118077 100644 --- a/src/restic/backend/rest/rest.go +++ b/src/restic/backend/rest/rest.go @@ -48,7 +48,7 @@ func Open(cfg Config) (restic.Backend, error) { url: cfg.URL, connChan: connChan, client: client, - Layout: &backend.CloudLayout{URL: url, Join: path.Join}, + Layout: &backend.RESTLayout{URL: url, Join: path.Join}, } return be, nil diff --git a/src/restic/backend/sftp/layout_test.go b/src/restic/backend/sftp/layout_test.go index 856bd5ce4..196dcdeba 100644 --- a/src/restic/backend/sftp/layout_test.go +++ b/src/restic/backend/sftp/layout_test.go @@ -28,11 +28,6 @@ func TestLayout(t *testing.T) { "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, }}, - {"repo-layout-cloud.tar.gz", "", false, map[string]bool{ - "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, - "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, - "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, - }}, {"repo-layout-s3-old.tar.gz", "", false, map[string]bool{ "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, diff --git a/src/restic/backend/testdata/repo-layout-cloud.tar.gz b/src/restic/backend/testdata/repo-layout-cloud.tar.gz deleted file mode 100644 index 189832589efda01218982691fed121bf50d3d296..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38095 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 From 0c537837d9b6baff29e6d8c7dceabce007f5c573 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 22:11:09 +0200 Subject: [PATCH 04/10] Rename testdata files --- src/restic/backend/layout_test.go | 6 +++--- src/restic/backend/local/layout_test.go | 4 ++-- src/restic/backend/sftp/layout_test.go | 4 ++-- ...yout-local.tar.gz => repo-layout-default.tar.gz} | Bin ...ut-s3-old.tar.gz => repo-layout-s3legacy.tar.gz} | Bin 5 files changed, 7 insertions(+), 7 deletions(-) rename src/restic/backend/testdata/{repo-layout-local.tar.gz => repo-layout-default.tar.gz} (100%) rename src/restic/backend/testdata/{repo-layout-s3-old.tar.gz => repo-layout-s3legacy.tar.gz} (100%) diff --git a/src/restic/backend/layout_test.go b/src/restic/backend/layout_test.go index 11e33ca98..cd06d4e63 100644 --- a/src/restic/backend/layout_test.go +++ b/src/restic/backend/layout_test.go @@ -301,8 +301,8 @@ func TestDetectLayout(t *testing.T) { filename string want string }{ - {"repo-layout-local.tar.gz", "*backend.DefaultLayout"}, - {"repo-layout-s3-old.tar.gz", "*backend.S3LegacyLayout"}, + {"repo-layout-default.tar.gz", "*backend.DefaultLayout"}, + {"repo-layout-s3legacy.tar.gz", "*backend.S3LegacyLayout"}, } var fs = &LocalFilesystem{} @@ -345,7 +345,7 @@ func TestParseLayout(t *testing.T) { {"", "", "*backend.DefaultLayout"}, } - SetupTarTestFixture(t, path, filepath.Join("testdata", "repo-layout-local.tar.gz")) + SetupTarTestFixture(t, path, filepath.Join("testdata", "repo-layout-default.tar.gz")) for _, test := range tests { t.Run(test.layoutName, func(t *testing.T) { diff --git a/src/restic/backend/local/layout_test.go b/src/restic/backend/local/layout_test.go index c5b9427a8..16b6b16e3 100644 --- a/src/restic/backend/local/layout_test.go +++ b/src/restic/backend/local/layout_test.go @@ -17,12 +17,12 @@ func TestLayout(t *testing.T) { failureExpected bool datafiles map[string]bool }{ - {"repo-layout-local.tar.gz", "", false, map[string]bool{ + {"repo-layout-default.tar.gz", "", false, map[string]bool{ "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, }}, - {"repo-layout-s3-old.tar.gz", "", false, map[string]bool{ + {"repo-layout-s3legacy.tar.gz", "", false, map[string]bool{ "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, diff --git a/src/restic/backend/sftp/layout_test.go b/src/restic/backend/sftp/layout_test.go index 196dcdeba..166fa97e3 100644 --- a/src/restic/backend/sftp/layout_test.go +++ b/src/restic/backend/sftp/layout_test.go @@ -23,12 +23,12 @@ func TestLayout(t *testing.T) { failureExpected bool datafiles map[string]bool }{ - {"repo-layout-local.tar.gz", "", false, map[string]bool{ + {"repo-layout-default.tar.gz", "", false, map[string]bool{ "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, }}, - {"repo-layout-s3-old.tar.gz", "", false, map[string]bool{ + {"repo-layout-s3legacy.tar.gz", "", false, map[string]bool{ "fc919a3b421850f6fa66ad22ebcf91e433e79ffef25becf8aef7c7b1eca91683": false, "c089d62788da14f8b7cbf77188305c0874906f0b73d3fce5a8869050e8d0c0e1": false, "aa464e9fd598fe4202492ee317ffa728e82fa83a1de1a61996e5bd2d6651646c": false, diff --git a/src/restic/backend/testdata/repo-layout-local.tar.gz b/src/restic/backend/testdata/repo-layout-default.tar.gz similarity index 100% rename from src/restic/backend/testdata/repo-layout-local.tar.gz rename to src/restic/backend/testdata/repo-layout-default.tar.gz diff --git a/src/restic/backend/testdata/repo-layout-s3-old.tar.gz b/src/restic/backend/testdata/repo-layout-s3legacy.tar.gz similarity index 100% rename from src/restic/backend/testdata/repo-layout-s3-old.tar.gz rename to src/restic/backend/testdata/repo-layout-s3legacy.tar.gz From 069752cb42948f5f19a357c7f11e384cb83d098e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 23:35:52 +0200 Subject: [PATCH 05/10] Make layout default Dirname() consistent Always return a trailing slash now. --- src/restic/backend/layout_default.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/restic/backend/layout_default.go b/src/restic/backend/layout_default.go index 77cb27508..836700b33 100644 --- a/src/restic/backend/layout_default.go +++ b/src/restic/backend/layout_default.go @@ -24,10 +24,10 @@ func (l *DefaultLayout) Dirname(h restic.Handle) string { p := defaultLayoutPaths[h.Type] if h.Type == restic.DataFile && len(h.Name) > 2 { - p = l.Join(p, h.Name[:2]) + p = l.Join(p, h.Name[:2]) + "/" } - return l.Join(l.Path, p) + return l.Join(l.Path, p) + "/" } // Filename returns a path to a file, including its name. From 959aa0f5959d34e9bc74f049def216e1a364cb45 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 23:36:23 +0200 Subject: [PATCH 06/10] Simplify layout detection --- src/restic/backend/layout.go | 48 +++--------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/src/restic/backend/layout.go b/src/restic/backend/layout.go index 38990e044..3d0953de8 100644 --- a/src/restic/backend/layout.go +++ b/src/restic/backend/layout.go @@ -85,36 +85,6 @@ func hasBackendFile(fs Filesystem, dir string) (bool, error) { 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 && fs.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 -} - // ErrLayoutDetectionFailed is returned by DetectLayout() when the layout // cannot be detected automatically. var ErrLayoutDetectionFailed = errors.New("auto-detecting the filesystem layout failed") @@ -140,19 +110,7 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { return nil, err } - // data file in "data" directory (S3LegacyLayout) - 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 && foundDataSubdirFile && !foundKeyFile && !foundDataFile { + if foundKeysFile && !foundKeyFile { debug.Log("found default layout at %v", dir) return &DefaultLayout{ Path: dir, @@ -160,7 +118,7 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) { }, nil } - if foundKeyFile && foundDataFile && !foundKeysFile && !foundDataSubdirFile { + if foundKeyFile && !foundKeysFile { debug.Log("found s3 layout at %v", dir) return &S3LegacyLayout{ Path: dir, @@ -192,7 +150,7 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, // use the default layout if auto detection failed if errors.Cause(err) == ErrLayoutDetectionFailed && defaultLayout != "" { - debug.Log("error: %v, use default layout %v", defaultLayout) + debug.Log("error: %v, use default layout %v", err, defaultLayout) return ParseLayout(repo, defaultLayout, "", path) } From fa41183a53fc185a8a78df9b0b4ce5109e8627c1 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 23:37:02 +0200 Subject: [PATCH 07/10] s3: Add s3.layout option and layout auto detection --- src/restic/backend/s3/config.go | 6 +++ src/restic/backend/s3/s3.go | 87 ++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/restic/backend/s3/config.go b/src/restic/backend/s3/config.go index 2df02b58c..6cf7db9c1 100644 --- a/src/restic/backend/s3/config.go +++ b/src/restic/backend/s3/config.go @@ -6,6 +6,7 @@ import ( "strings" "restic/errors" + "restic/options" ) // Config contains all configuration necessary to connect to an s3 compatible @@ -16,6 +17,11 @@ type Config struct { KeyID, Secret string Bucket string Prefix string + Layout string `option:"layout" help:"use this backend layout (default: auto-detect)"` +} + +func init() { + options.Register("s3", Config{}) } const defaultPrefix = "restic" diff --git a/src/restic/backend/s3/s3.go b/src/restic/backend/s3/s3.go index 241bfe323..3b6a60b54 100644 --- a/src/restic/backend/s3/s3.go +++ b/src/restic/backend/s3/s3.go @@ -8,6 +8,7 @@ import ( "restic" "strings" "sync" + "time" "restic/backend" "restic/errors" @@ -30,6 +31,8 @@ type s3 struct { backend.Layout } +const defaultLayout = "s3legacy" + // Open opens the S3 backend at bucket and region. The bucket is created if it // does not exist yet. func Open(cfg Config) (restic.Backend, error) { @@ -45,11 +48,17 @@ func Open(cfg Config) (restic.Backend, error) { bucketname: cfg.Bucket, prefix: cfg.Prefix, cacheObjSize: make(map[string]int64), - Layout: &backend.S3LegacyLayout{Path: cfg.Prefix, Join: path.Join}, } client.SetCustomTransport(backend.Transport()) + l, err := backend.ParseLayout(be, cfg.Layout, defaultLayout, cfg.Prefix) + if err != nil { + return nil, err + } + + be.Layout = l + be.createConnections() found, err := client.BucketExists(cfg.Bucket) @@ -76,6 +85,77 @@ func (be *s3) createConnections() { } } +// IsNotExist returns true if the error is caused by a not existing file. +func (be *s3) IsNotExist(err error) bool { + debug.Log("IsNotExist(%T, %#v)", err, err) + if os.IsNotExist(err) { + return true + } + + return false +} + +// Join combines path components with slashes. +func (be *s3) Join(p ...string) string { + return path.Join(p...) +} + +type fileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time + isDir bool +} + +func (fi fileInfo) Name() string { return fi.name } // base name of the file +func (fi fileInfo) Size() int64 { return fi.size } // length in bytes for regular files; system-dependent for others +func (fi fileInfo) Mode() os.FileMode { return fi.mode } // file mode bits +func (fi fileInfo) ModTime() time.Time { return fi.modTime } // modification time +func (fi fileInfo) IsDir() bool { return fi.isDir } // abbreviation for Mode().IsDir() +func (fi fileInfo) Sys() interface{} { return nil } // underlying data source (can return nil) + +// ReadDir returns the entries for a directory. +func (be *s3) ReadDir(dir string) (list []os.FileInfo, err error) { + debug.Log("ReadDir(%v)", dir) + + // make sure dir ends with a slash + if dir[len(dir)-1] != '/' { + dir += "/" + } + + done := make(chan struct{}) + defer close(done) + + for obj := range be.client.ListObjects(be.bucketname, dir, false, done) { + if obj.Key == "" { + continue + } + + name := strings.TrimPrefix(obj.Key, dir) + if name == "" { + return nil, errors.Errorf("invalid key name %v, removing prefix %v yielded empty string", obj.Key, dir) + } + entry := fileInfo{ + name: name, + size: obj.Size, + modTime: obj.LastModified, + } + + if name[len(name)-1] == '/' { + entry.isDir = true + entry.mode = os.ModeDir | 0755 + entry.name = name[:len(name)-1] + } else { + entry.mode = 0644 + } + + list = append(list, entry) + } + + return list, nil +} + // Location returns this backend's location (the bucket name). func (be *s3) Location() string { return be.bucketname @@ -290,6 +370,11 @@ func (be *s3) List(t restic.FileType, done <-chan struct{}) <-chan string { prefix := be.Dirname(restic.Handle{Type: t}) + // make sure prefix ends with a slash + if prefix[len(prefix)-1] != '/' { + prefix += "/" + } + listresp := be.client.ListObjects(be.bucketname, prefix, true, done) go func() { From 55ae5dab2b2fbd56b78955b278d324d066b288d1 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 15 May 2017 23:37:16 +0200 Subject: [PATCH 08/10] design: Add s3.layout option --- doc/design.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/design.rst b/doc/design.rst index 02b0992fb..16fa8e7ae 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -121,7 +121,8 @@ The local and sftp backends will auto-detect and accept all layouts described in the following sections, so that remote repositories mounted locally e.g. via fuse can be accessed. The layout auto-detection can be overridden by specifying the option ``-o local.layout=default``, valid values are ``default`` and -``s3legacy``. The option for the sftp backend is named ``sftp.layout``. +``s3legacy``. The option for the sftp backend is named ``sftp.layout``, for the +s3 backend ``s3.layout``. S3 Legacy Layout ~~~~~~~~~~~~~~~~ From 2fa1238b8a17b64a068521829d7468b4fe203f75 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Tue, 16 May 2017 20:45:17 +0200 Subject: [PATCH 09/10] Fix filenames for layout tests --- src/cmds/restic/local_layout_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/cmds/restic/local_layout_test.go b/src/cmds/restic/local_layout_test.go index eb6268e72..fbb1f13af 100644 --- a/src/cmds/restic/local_layout_test.go +++ b/src/cmds/restic/local_layout_test.go @@ -12,12 +12,10 @@ func TestRestoreLocalLayout(t *testing.T) { filename string layout string }{ - {"repo-layout-cloud.tar.gz", ""}, - {"repo-layout-local.tar.gz", ""}, - {"repo-layout-s3-old.tar.gz", ""}, - {"repo-layout-cloud.tar.gz", "cloud"}, - {"repo-layout-local.tar.gz", "default"}, - {"repo-layout-s3-old.tar.gz", "s3"}, + {"repo-layout-default.tar.gz", ""}, + {"repo-layout-s3legacy.tar.gz", ""}, + {"repo-layout-default.tar.gz", "default"}, + {"repo-layout-s3legacy.tar.gz", "s3legacy"}, } for _, test := range tests { From 8b461a7456c905802e3a4c97b82c512f6c4523a7 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Tue, 16 May 2017 20:49:18 +0200 Subject: [PATCH 10/10] Make TestFlags less verbose --- src/cmds/restic/flags_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cmds/restic/flags_test.go b/src/cmds/restic/flags_test.go index 3b313b68c..7cd758592 100644 --- a/src/cmds/restic/flags_test.go +++ b/src/cmds/restic/flags_test.go @@ -1,6 +1,7 @@ package main import ( + "io/ioutil" "testing" ) @@ -13,6 +14,7 @@ func TestFlags(t *testing.T) { for _, cmd := range cmdRoot.Commands() { t.Run(cmd.Name(), func(t *testing.T) { + cmd.Flags().SetOutput(ioutil.Discard) err := cmd.ParseFlags([]string{"--help"}) if err.Error() == "pflag: help requested" { err = nil