From cc147c002e64e599d41c266667920a216d2d1ae6 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Dec 2014 17:02:49 +0100 Subject: [PATCH] Introduce type Server --- archiver.go | 8 +-- backend/generic.go | 12 ++--- backend/interface.go | 30 +++++------ cmd/restic/cmd_find.go | 2 +- cmd/restic/cmd_fsck.go | 2 +- cmd/restic/cmd_key.go | 8 +-- cmd/restic/cmd_list.go | 11 ++-- cmd/restic/cmd_ls.go | 2 +- cmd/restic/main.go | 22 ++++---- contenthandler.go | 16 +++--- key.go | 37 ++++---------- key_test.go | 39 +++++++------- restorer.go | 8 +-- server.go | 113 +++++++++++++++++++++++++++++++++++++++++ snapshot_test.go | 2 +- 15 files changed, 206 insertions(+), 106 deletions(-) create mode 100644 server.go diff --git a/archiver.go b/archiver.go index caa66e5a8..aba53cbcb 100644 --- a/archiver.go +++ b/archiver.go @@ -22,7 +22,7 @@ const ( ) type Archiver struct { - be backend.Server + s Server key *Key ch *ContentHandler @@ -58,10 +58,10 @@ func (s *Stats) Add(other Stats) { s.Other += other.Other } -func NewArchiver(be backend.Server, key *Key) (*Archiver, error) { +func NewArchiver(s Server, key *Key) (*Archiver, error) { var err error arch := &Archiver{ - be: be, + s: s, key: key, fileToken: make(chan struct{}, maxConcurrentFiles), blobToken: make(chan struct{}, maxConcurrentBlobs), @@ -82,7 +82,7 @@ func NewArchiver(be backend.Server, key *Key) (*Archiver, error) { arch.Filter = func(string, os.FileInfo) bool { return true } arch.bl = NewBlobList() - arch.ch, err = NewContentHandler(be, key) + arch.ch, err = NewContentHandler(s, key) if err != nil { return nil, err } diff --git a/backend/generic.go b/backend/generic.go index e5509f334..dd5e9ac78 100644 --- a/backend/generic.go +++ b/backend/generic.go @@ -22,8 +22,8 @@ var ( // Each lists all entries of type t in the backend and calls function f() with // the id and data. func Each(be interface { - lister - getter + Lister + Getter }, t Type, f func(id ID, data []byte, err error)) error { ids, err := be.List(t) if err != nil { @@ -45,7 +45,7 @@ func Each(be interface { // Each lists all entries of type t in the backend and calls function f() with // the id. -func EachID(be lister, t Type, f func(ID)) error { +func EachID(be Lister, t Type, f func(ID)) error { ids, err := be.List(t) if err != nil { return err @@ -101,7 +101,7 @@ func Hash(data []byte) ID { // Find loads the list of all blobs of type t and searches for IDs which start // with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. If // more than one is found, nil and ErrMultipleIDMatches is returned. -func Find(be lister, t Type, prefix string) (ID, error) { +func Find(be Lister, t Type, prefix string) (ID, error) { p, err := hex.DecodeString(prefix) if err != nil { return nil, err @@ -134,7 +134,7 @@ func Find(be lister, t Type, prefix string) (ID, error) { // FindSnapshot takes a string and tries to find a snapshot whose ID matches // the string as closely as possible. -func FindSnapshot(be lister, s string) (ID, error) { +func FindSnapshot(be Lister, s string) (ID, error) { // parse ID directly if id, err := ParseID(s); err == nil { return id, nil @@ -151,7 +151,7 @@ func FindSnapshot(be lister, s string) (ID, error) { // PrefixLength returns the number of bytes required so that all prefixes of // all IDs of type t are unique. -func PrefixLength(be lister, t Type) (int, error) { +func PrefixLength(be Lister, t Type) (int, error) { // load all IDs of the given type list, err := be.List(t) if err != nil { diff --git a/backend/interface.go b/backend/interface.go index 644ab9653..b0ada8602 100644 --- a/backend/interface.go +++ b/backend/interface.go @@ -21,43 +21,43 @@ var ( ErrAlreadyPresent = errors.New("blob is already present in backend") ) -type lister interface { +type Lister interface { List(Type) (IDs, error) } -type getter interface { +type Getter interface { Get(Type, ID) ([]byte, error) } -type creater interface { +type Creater interface { Create(Type, []byte) (ID, error) } -type tester interface { +type Tester interface { Test(Type, ID) (bool, error) } -type remover interface { +type Remover interface { Remove(Type, ID) error } -type closer interface { +type Closer interface { Close() error } -type deleter interface { +type Deleter interface { Delete() error } -type locationer interface { +type Locationer interface { Location() string } -type backend interface { - lister - getter - creater - tester - remover - closer +type Backend interface { + Lister + Getter + Creater + Tester + Remover + Closer } diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index aa8157259..585e60339 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -110,7 +110,7 @@ func (c CmdFind) findInTree(ch *restic.ContentHandler, id backend.ID, path strin return results, nil } -func (c CmdFind) findInSnapshot(be backend.Server, key *restic.Key, id backend.ID) error { +func (c CmdFind) findInSnapshot(be restic.Server, key *restic.Key, id backend.ID) error { debug("searching in snapshot %s\n for entries within [%s %s]", id, c.oldest, c.newest) ch, err := restic.NewContentHandler(be, key) diff --git a/cmd/restic/cmd_fsck.go b/cmd/restic/cmd_fsck.go index 4db9a6ff2..fcb1c6329 100644 --- a/cmd/restic/cmd_fsck.go +++ b/cmd/restic/cmd_fsck.go @@ -75,7 +75,7 @@ func fsckTree(ch *restic.ContentHandler, id backend.ID) error { return nil } -func fsck_snapshot(be backend.Server, key *restic.Key, id backend.ID) error { +func fsck_snapshot(be restic.Server, key *restic.Key, id backend.ID) error { debug("checking snapshot %v\n", id) ch, err := restic.NewContentHandler(be, key) diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index c111696d1..9332ebb8c 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -22,7 +22,7 @@ func init() { } } -func list_keys(be backend.Server, key *restic.Key) error { +func list_keys(be restic.Server, key *restic.Key) error { tab := NewTable() tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created") tab.RowFormat = "%s%-10s %-10s %-10s %s" @@ -54,7 +54,7 @@ func list_keys(be backend.Server, key *restic.Key) error { return nil } -func add_key(be backend.Server, key *restic.Key) error { +func add_key(be restic.Server, key *restic.Key) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") @@ -72,7 +72,7 @@ func add_key(be backend.Server, key *restic.Key) error { return nil } -func delete_key(be backend.Server, key *restic.Key, id backend.ID) error { +func delete_key(be restic.Server, key *restic.Key, id backend.ID) error { if id.Equal(key.ID()) { return errors.New("refusing to remove key currently used to access repository") } @@ -86,7 +86,7 @@ func delete_key(be backend.Server, key *restic.Key, id backend.ID) error { return nil } -func change_password(be backend.Server, key *restic.Key) error { +func change_password(be restic.Server, key *restic.Key) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index caca10556..b85b9f010 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "github.com/restic/restic/backend" ) @@ -27,22 +28,22 @@ func (cmd CmdList) Execute(args []string) error { return fmt.Errorf("type not specified, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + be, _, err := OpenRepo() if err != nil { return err } var ( t backend.Type - each func(backend.Server, backend.Type, func(backend.ID, []byte, error)) error = backend.Each + each func(backend.Type, func(backend.ID, []byte, error)) error = be.Each ) switch args[0] { case "data": t = backend.Data - each = key.Each + each = be.EachDecrypted case "trees": t = backend.Tree - each = key.Each + each = be.EachDecrypted case "snapshots": t = backend.Snapshot case "maps": @@ -55,7 +56,7 @@ func (cmd CmdList) Execute(args []string) error { return errors.New("invalid type") } - return each(be, t, func(id backend.ID, data []byte, err error) { + return each(t, func(id backend.ID, data []byte, err error) { if t == backend.Data || t == backend.Tree { fmt.Printf("%s %s\n", id, backend.Hash(data)) } else { diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index a586a3835..b64454a43 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -63,7 +63,7 @@ func (cmd CmdLs) Usage() string { return "ls snapshot-ID [DIR]" } -func (cmd CmdLs) Execute(be backend.Server, key *restic.Key, args []string) error { +func (cmd CmdLs) Execute(be restic.Server, key *restic.Key, args []string) error { if len(args) < 1 || len(args) > 2 { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 469034af7..ee2f835bf 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -70,13 +70,15 @@ func (cmd CmdInit) Execute(args []string) error { os.Exit(1) } - _, err = restic.CreateKey(be, pw) + s := restic.NewServer(be) + + _, err = restic.CreateKey(s, pw) if err != nil { fmt.Fprintf(os.Stderr, "creating key in backend at %s failed: %v\n", opts.Repo, err) os.Exit(1) } - fmt.Printf("created restic backend at %s\n", be.Location()) + fmt.Printf("created restic backend at %s\n", opts.Repo) return nil } @@ -86,7 +88,7 @@ func (cmd CmdInit) Execute(args []string) error { // * /foo/bar -> local repository at /foo/bar // * sftp://user@host/foo/bar -> remote sftp repository on host for user at path foo/bar // * sftp://host//tmp/backup -> remote sftp repository on host at path /tmp/backup -func open(u string) (backend.Server, error) { +func open(u string) (backend.Backend, error) { url, err := url.Parse(u) if err != nil { return nil, err @@ -107,7 +109,7 @@ func open(u string) (backend.Server, error) { } // Create the backend specified by URI. -func create(u string) (backend.Server, error) { +func create(u string) (backend.Backend, error) { url, err := url.Parse(u) if err != nil { return nil, err @@ -127,18 +129,20 @@ func create(u string) (backend.Server, error) { return backend.CreateSFTP(url.Path[1:], "ssh", args...) } -func OpenRepo() (backend.Server, *restic.Key, error) { +func OpenRepo() (restic.Server, *restic.Key, error) { be, err := open(opts.Repo) if err != nil { - return nil, nil, err + return restic.Server{}, nil, err } - key, err := restic.SearchKey(be, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) + s := restic.NewServer(be) + + key, err := restic.SearchKey(s, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) if err != nil { - return nil, nil, fmt.Errorf("unable to open repo: %v", err) + return restic.Server{}, nil, fmt.Errorf("unable to open repo: %v", err) } - return be, key, nil + return s, key, nil } func init() { diff --git a/contenthandler.go b/contenthandler.go index bf43255d1..3beed5f09 100644 --- a/contenthandler.go +++ b/contenthandler.go @@ -11,16 +11,16 @@ import ( var ErrWrongData = errors.New("wrong data decrypt, checksum does not match") type ContentHandler struct { - be backend.Server + s Server key *Key bl *BlobList } // NewContentHandler creates a new content handler. -func NewContentHandler(be backend.Server, key *Key) (*ContentHandler, error) { +func NewContentHandler(s Server, key *Key) (*ContentHandler, error) { ch := &ContentHandler{ - be: be, + s: s, key: key, bl: NewBlobList(), } @@ -49,7 +49,7 @@ func (ch *ContentHandler) LoadSnapshot(id backend.ID) (*Snapshot, error) { // into the content handler. func (ch *ContentHandler) LoadAllMaps() error { // add all maps from all snapshots that can be decrypted to the storage map - err := backend.EachID(ch.be, backend.Map, func(id backend.ID) { + err := backend.EachID(ch.s, backend.Map, func(id backend.ID) { bl, err := LoadBlobList(ch, id) if err != nil { return @@ -103,7 +103,7 @@ func (ch *ContentHandler) Save(t backend.Type, data []byte) (Blob, error) { ciphertext = ciphertext[:n] // save blob - sid, err := ch.be.Create(t, ciphertext) + sid, err := ch.s.Create(t, ciphertext) if err != nil { return Blob{}, err } @@ -133,7 +133,7 @@ func (ch *ContentHandler) SaveJSON(t backend.Type, item interface{}) (Blob, erro func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { if t == backend.Snapshot { // load data - buf, err := ch.be.Get(t, id) + buf, err := ch.s.Get(t, id) if err != nil { return nil, err } @@ -154,7 +154,7 @@ func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { } // load data - buf, err := ch.be.Get(t, blob.Storage) + buf, err := ch.s.Get(t, blob.Storage) if err != nil { return nil, err } @@ -201,7 +201,7 @@ func (ch *ContentHandler) LoadJSON(t backend.Type, id backend.ID, item interface // decrypts it and calls json.Unmarshal on the item. func (ch *ContentHandler) LoadJSONRaw(t backend.Type, id backend.ID, item interface{}) error { // load data - buf, err := ch.be.Get(t, id) + buf, err := ch.s.Get(t, id) if err != nil { return err } diff --git a/key.go b/key.go index 4889047de..4bfad1380 100644 --- a/key.go +++ b/key.go @@ -75,7 +75,7 @@ type keys struct { // CreateKey initializes a master key in the given backend and encrypts it with // the password. -func CreateKey(be backend.Server, password string) (*Key, error) { +func CreateKey(s Server, password string) (*Key, error) { // fill meta data about key k := &Key{ Created: time.Now(), @@ -131,7 +131,7 @@ func CreateKey(be backend.Server, password string) (*Key, error) { } // store in repository and return - id, err := be.Create(backend.Key, buf) + id, err := s.Create(backend.Key, buf) if err != nil { return nil, err } @@ -143,9 +143,9 @@ func CreateKey(be backend.Server, password string) (*Key, error) { } // OpenKey tries do decrypt the key specified by id with the given password. -func OpenKey(be backend.Server, id backend.ID, password string) (*Key, error) { +func OpenKey(s Server, id backend.ID, password string) (*Key, error) { // extract data from repo - data, err := be.Get(backend.Key, id) + data, err := s.Get(backend.Key, id) if err != nil { return nil, err } @@ -187,9 +187,9 @@ func OpenKey(be backend.Server, id backend.ID, password string) (*Key, error) { // SearchKey tries to decrypt all keys in the backend with the given password. // If none could be found, ErrNoKeyFound is returned. -func SearchKey(be backend.Server, password string) (*Key, error) { +func SearchKey(s Server, password string) (*Key, error) { // list all keys - ids, err := be.List(backend.Key) + ids, err := s.List(backend.Key) if err != nil { panic(err) } @@ -197,7 +197,7 @@ func SearchKey(be backend.Server, password string) (*Key, error) { // try all keys in repo var key *Key for _, id := range ids { - key, err = OpenKey(be, id, password) + key, err = OpenKey(s, id, password) if err != nil { continue } @@ -209,7 +209,7 @@ func SearchKey(be backend.Server, password string) (*Key, error) { } // AddKey adds a new key to an already existing repository. -func (oldkey *Key) AddKey(be backend.Server, password string) (backend.ID, error) { +func (oldkey *Key) AddKey(s Server, password string) (backend.ID, error) { // fill meta data about key newkey := &Key{ Created: time.Now(), @@ -262,7 +262,7 @@ func (oldkey *Key) AddKey(be backend.Server, password string) (backend.ID, error } // store in repository and return - id, err := be.Create(backend.Key, buf) + id, err := s.Create(backend.Key, buf) if err != nil { return nil, err } @@ -426,25 +426,6 @@ func (k *Key) DecryptUser(ciphertext []byte) ([]byte, error) { return k.decrypt(k.user, ciphertext) } -// Each calls backend.Each() with the given parameters, Decrypt() on the -// ciphertext and, on successful decryption, f with the plaintext. -func (k *Key) Each(be backend.Server, t backend.Type, f func(backend.ID, []byte, error)) error { - return backend.Each(be, t, func(id backend.ID, data []byte, e error) { - if e != nil { - f(id, nil, e) - return - } - - buf, err := k.Decrypt(data) - if err != nil { - f(id, nil, err) - return - } - - f(id, buf, nil) - }) -} - func (k *Key) String() string { if k == nil { return "" diff --git a/key_test.go b/key_test.go index 49e47ed5c..6fdcca836 100644 --- a/key_test.go +++ b/key_test.go @@ -15,42 +15,43 @@ import ( var testPassword = "foobar" var testCleanup = flag.Bool("test.cleanup", true, "clean up after running tests (remove local backend directory with all content)") -func setupBackend(t testing.TB) *backend.Local { +func setupBackend(t testing.TB) restic.Server { tempdir, err := ioutil.TempDir("", "restic-test-") ok(t, err) b, err := backend.CreateLocal(tempdir) ok(t, err) - return b + return restic.NewServer(b) } -func teardownBackend(t testing.TB, b *backend.Local) { +func teardownBackend(t testing.TB, s restic.Server) { if !*testCleanup { - t.Logf("leaving local backend at %s\n", b.Location()) + l := s.Backend().(*backend.Local) + t.Logf("leaving local backend at %s\n", l.Location()) return } - ok(t, os.RemoveAll(b.Location())) + ok(t, s.Delete()) } -func setupKey(t testing.TB, be backend.Server, password string) *restic.Key { - k, err := restic.CreateKey(be, password) +func setupKey(t testing.TB, s restic.Server, password string) *restic.Key { + k, err := restic.CreateKey(s, password) ok(t, err) return k } func TestRepo(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - _ = setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + _ = setupKey(t, s, testPassword) } func TestEncryptDecrypt(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - k := setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + k := setupKey(t, s, testPassword) for _, size := range []int{5, 23, 1 << 20, 7<<20 + 123} { data := make([]byte, size) @@ -74,9 +75,9 @@ func TestEncryptDecrypt(t *testing.T) { } func TestLargeEncrypt(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - k := setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + k := setupKey(t, s, testPassword) for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1} { data := make([]byte, size) @@ -120,9 +121,9 @@ func BenchmarkDecrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) - be := setupBackend(b) - defer teardownBackend(b, be) - k := setupKey(b, be, testPassword) + s := setupBackend(b) + defer teardownBackend(b, s) + k := setupKey(b, s, testPassword) ciphertext := restic.GetChunkBuf("BenchmarkDecrypt") n, err := k.Encrypt(ciphertext, data) diff --git a/restorer.go b/restorer.go index 83dd18da3..7650e60b8 100644 --- a/restorer.go +++ b/restorer.go @@ -11,7 +11,7 @@ import ( ) type Restorer struct { - be backend.Server + s Server key *Key ch *ContentHandler sn *Snapshot @@ -21,14 +21,14 @@ type Restorer struct { } // NewRestorer creates a restorer preloaded with the content from the snapshot snid. -func NewRestorer(be backend.Server, key *Key, snid backend.ID) (*Restorer, error) { +func NewRestorer(s Server, key *Key, snid backend.ID) (*Restorer, error) { r := &Restorer{ - be: be, + s: s, key: key, } var err error - r.ch, err = NewContentHandler(be, key) + r.ch, err = NewContentHandler(s, key) if err != nil { return nil, arrar.Annotate(err, "create contenthandler for restorer") } diff --git a/server.go b/server.go new file mode 100644 index 000000000..dec529b31 --- /dev/null +++ b/server.go @@ -0,0 +1,113 @@ +package restic + +import ( + "errors" + + "github.com/restic/restic/backend" +) + +type Server struct { + be backend.Backend + key *Key +} + +func NewServer(be backend.Backend) Server { + return Server{be: be} +} + +func NewServerWithKey(be backend.Backend, key *Key) Server { + return Server{be: be, key: key} +} + +// Each lists all entries of type t in the backend and calls function f() with +// the id and data. +func (s Server) Each(t backend.Type, f func(id backend.ID, data []byte, err error)) error { + return backend.Each(s.be, t, f) +} + +// Each lists all entries of type t in the backend and calls function f() with +// the id. +func (s Server) EachID(t backend.Type, f func(backend.ID)) error { + return backend.EachID(s.be, t, f) +} + +// Find loads the list of all blobs of type t and searches for IDs which start +// with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. If +// more than one is found, nil and ErrMultipleIDMatches is returned. +func (s Server) Find(t backend.Type, prefix string) (backend.ID, error) { + return backend.Find(s.be, t, prefix) +} + +// FindSnapshot takes a string and tries to find a snapshot whose ID matches +// the string as closely as possible. +func (s Server) FindSnapshot(id string) (backend.ID, error) { + return backend.FindSnapshot(s.be, id) +} + +// PrefixLength returns the number of bytes required so that all prefixes of +// all IDs of type t are unique. +func (s Server) PrefixLength(t backend.Type) (int, error) { + return backend.PrefixLength(s.be, t) +} + +// Returns the backend used for this server. +func (s Server) Backend() backend.Backend { + return s.be +} + +// Each calls Each() with the given parameters, Decrypt() on the ciphertext +// and, on successful decryption, f with the plaintext. +func (s Server) EachDecrypted(t backend.Type, f func(backend.ID, []byte, error)) error { + if s.key == nil { + return errors.New("key for server not set") + } + + return s.Each(t, func(id backend.ID, data []byte, e error) { + if e != nil { + f(id, nil, e) + return + } + + buf, err := s.key.Decrypt(data) + if err != nil { + f(id, nil, err) + return + } + + f(id, buf, nil) + }) +} + +// Proxy methods to backend + +func (s Server) List(t backend.Type) (backend.IDs, error) { + return s.be.List(t) +} + +func (s Server) Get(t backend.Type, id backend.ID) ([]byte, error) { + return s.be.Get(t, id) +} + +func (s Server) Create(t backend.Type, data []byte) (backend.ID, error) { + return s.be.Create(t, data) +} + +func (s Server) Test(t backend.Type, id backend.ID) (bool, error) { + return s.be.Test(t, id) +} + +func (s Server) Remove(t backend.Type, id backend.ID) error { + return s.be.Remove(t, id) +} + +func (s Server) Close() error { + return s.be.Close() +} + +func (s Server) Delete() error { + if b, ok := s.be.(backend.Deleter); ok { + return b.Delete() + } + + return errors.New("Delete() called for backend that does not implement this method") +} diff --git a/snapshot_test.go b/snapshot_test.go index fd441d0a7..39213d20f 100644 --- a/snapshot_test.go +++ b/snapshot_test.go @@ -8,7 +8,7 @@ import ( "github.com/restic/restic/backend" ) -func testSnapshot(t *testing.T, be backend.Server) { +func testSnapshot(t *testing.T, s restic.Server) { var err error sn, err := restic.NewSnapshot("/home/foobar") ok(t, err)