Merge pull request #2935 from MichaelEischer/upgrade-minio

Upgrade minio SDK to version 7
This commit is contained in:
Alexander Neumann
2020-11-02 09:09:10 +01:00
committed by GitHub
15 changed files with 112 additions and 102 deletions

View File

@@ -1,6 +1,7 @@
package backend
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -24,7 +25,7 @@ type Layout interface {
// Filesystem is the abstraction of a file system used for a backend.
type Filesystem interface {
Join(...string) string
ReadDir(string) ([]os.FileInfo, error)
ReadDir(context.Context, string) ([]os.FileInfo, error)
IsNotExist(error) bool
}
@@ -36,7 +37,7 @@ type LocalFilesystem struct {
}
// ReadDir returns all entries of a directory.
func (l *LocalFilesystem) ReadDir(dir string) ([]os.FileInfo, error) {
func (l *LocalFilesystem) ReadDir(ctx context.Context, dir string) ([]os.FileInfo, error) {
f, err := fs.Open(dir)
if err != nil {
return nil, err
@@ -68,8 +69,8 @@ func (l *LocalFilesystem) IsNotExist(err error) bool {
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)
func hasBackendFile(ctx context.Context, fs Filesystem, dir string) (bool, error) {
entries, err := fs.ReadDir(ctx, dir)
if err != nil && fs.IsNotExist(errors.Cause(err)) {
return false, nil
}
@@ -94,20 +95,20 @@ var ErrLayoutDetectionFailed = errors.New("auto-detecting the filesystem layout
// DetectLayout tries to find out which layout is used in a local (or sftp)
// filesystem at the given path. If repo is nil, an instance of LocalFilesystem
// is used.
func DetectLayout(repo Filesystem, dir string) (Layout, error) {
func DetectLayout(ctx context.Context, repo Filesystem, dir string) (Layout, error) {
debug.Log("detect layout at %v", dir)
if repo == nil {
repo = &LocalFilesystem{}
}
// key file in the "keys" dir (DefaultLayout)
foundKeysFile, err := hasBackendFile(repo, repo.Join(dir, defaultLayoutPaths[restic.KeyFile]))
foundKeysFile, err := hasBackendFile(ctx, repo, repo.Join(dir, defaultLayoutPaths[restic.KeyFile]))
if err != nil {
return nil, err
}
// key file in the "key" dir (S3LegacyLayout)
foundKeyFile, err := hasBackendFile(repo, repo.Join(dir, s3LayoutPaths[restic.KeyFile]))
foundKeyFile, err := hasBackendFile(ctx, repo, repo.Join(dir, s3LayoutPaths[restic.KeyFile]))
if err != nil {
return nil, err
}
@@ -134,7 +135,7 @@ func DetectLayout(repo Filesystem, dir string) (Layout, error) {
// ParseLayout parses the config string and returns a Layout. When layout is
// the empty string, DetectLayout is used. If that fails, defaultLayout is used.
func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout, err error) {
func ParseLayout(ctx context.Context, repo Filesystem, layout, defaultLayout, path string) (l Layout, err error) {
debug.Log("parse layout string %q for backend at %v", layout, path)
switch layout {
case "default":
@@ -148,12 +149,12 @@ func ParseLayout(repo Filesystem, layout, defaultLayout, path string) (l Layout,
Join: repo.Join,
}
case "":
l, err = DetectLayout(repo, path)
l, err = DetectLayout(ctx, repo, path)
// use the default layout if auto detection failed
if errors.Cause(err) == ErrLayoutDetectionFailed && defaultLayout != "" {
debug.Log("error: %v, use default layout %v", err, defaultLayout)
return ParseLayout(repo, defaultLayout, "", path)
return ParseLayout(ctx, repo, defaultLayout, "", path)
}
if err != nil {

View File

@@ -1,6 +1,7 @@
package backend
import (
"context"
"fmt"
"path"
"path/filepath"
@@ -371,7 +372,7 @@ func TestDetectLayout(t *testing.T) {
t.Run(fmt.Sprintf("%v/fs-%T", test.filename, fs), func(t *testing.T) {
rtest.SetupTarTestFixture(t, path, filepath.Join("testdata", test.filename))
layout, err := DetectLayout(fs, filepath.Join(path, "repo"))
layout, err := DetectLayout(context.TODO(), fs, filepath.Join(path, "repo"))
if err != nil {
t.Fatal(err)
}
@@ -409,7 +410,7 @@ func TestParseLayout(t *testing.T) {
for _, test := range tests {
t.Run(test.layoutName, func(t *testing.T) {
layout, err := ParseLayout(&LocalFilesystem{}, test.layoutName, test.defaultLayoutName, filepath.Join(path, "repo"))
layout, err := ParseLayout(context.TODO(), &LocalFilesystem{}, test.layoutName, test.defaultLayoutName, filepath.Join(path, "repo"))
if err != nil {
t.Fatal(err)
}
@@ -441,7 +442,7 @@ func TestParseLayoutInvalid(t *testing.T) {
for _, name := range invalidNames {
t.Run(name, func(t *testing.T) {
layout, err := ParseLayout(nil, name, "", path)
layout, err := ParseLayout(context.TODO(), nil, name, "", path)
if err == nil {
t.Fatalf("expected error not found for layout name %v, layout is %v", name, layout)
}

View File

@@ -36,7 +36,7 @@ func TestLayout(t *testing.T) {
rtest.SetupTarTestFixture(t, path, filepath.Join("..", "testdata", test.filename))
repo := filepath.Join(path, "repo")
be, err := Open(Config{
be, err := Open(context.TODO(), Config{
Path: repo,
Layout: test.layout,
})

View File

@@ -27,9 +27,9 @@ var _ restic.Backend = &Local{}
const defaultLayout = "default"
// Open opens the local backend as specified by config.
func Open(cfg Config) (*Local, error) {
func Open(ctx context.Context, cfg Config) (*Local, error) {
debug.Log("open local backend at %v (layout %q)", cfg.Path, cfg.Layout)
l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
l, err := backend.ParseLayout(ctx, &backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
if err != nil {
return nil, err
}
@@ -39,10 +39,10 @@ func Open(cfg Config) (*Local, error) {
// Create creates all the necessary files and directories for a new local
// backend at dir. Afterwards a new config blob should be created.
func Create(cfg Config) (*Local, error) {
func Create(ctx context.Context, cfg Config) (*Local, error) {
debug.Log("create local backend at %v (layout %q)", cfg.Path, cfg.Layout)
l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
l, err := backend.ParseLayout(ctx, &backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package local_test
import (
"context"
"io/ioutil"
"os"
"path/filepath"
@@ -32,13 +33,13 @@ func newTestSuite(t testing.TB) *test.Suite {
// CreateFn is a function that creates a temporary repository for the tests.
Create: func(config interface{}) (restic.Backend, error) {
cfg := config.(local.Config)
return local.Create(cfg)
return local.Create(context.TODO(), cfg)
},
// OpenFn is a function that opens a previously created temporary repository.
Open: func(config interface{}) (restic.Backend, error) {
cfg := config.(local.Config)
return local.Open(cfg)
return local.Open(context.TODO(), cfg)
},
// CleanupFn removes data created during the tests.
@@ -91,7 +92,7 @@ func empty(t testing.TB, dir string) {
func openclose(t testing.TB, dir string) {
cfg := local.Config{Path: dir}
be, err := local.Open(cfg)
be, err := local.Open(context.TODO(), cfg)
if err != nil {
t.Logf("Open returned error %v", err)
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/credentials"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/restic/restic/internal/debug"
)
@@ -33,7 +33,7 @@ var _ restic.Backend = &Backend{}
const defaultLayout = "default"
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
debug.Log("open, config %#v", cfg)
if cfg.MaxRetries > 0 {
@@ -66,9 +66,14 @@ func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
},
},
})
client, err := minio.NewWithCredentials(cfg.Endpoint, creds, !cfg.UseHTTP, cfg.Region)
client, err := minio.New(cfg.Endpoint, &minio.Options{
Creds: creds,
Secure: !cfg.UseHTTP,
Region: cfg.Region,
Transport: rt,
})
if err != nil {
return nil, errors.Wrap(err, "minio.NewWithCredentials")
return nil, errors.Wrap(err, "minio.New")
}
sem, err := backend.NewSemaphore(cfg.Connections)
@@ -82,9 +87,7 @@ func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
cfg: cfg,
}
client.SetCustomTransport(rt)
l, err := backend.ParseLayout(be, cfg.Layout, defaultLayout, cfg.Prefix)
l, err := backend.ParseLayout(ctx, be, cfg.Layout, defaultLayout, cfg.Prefix)
if err != nil {
return nil, err
}
@@ -96,18 +99,18 @@ func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
// Open opens the S3 backend at bucket and region. The bucket is created if it
// does not exist yet.
func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
return open(cfg, rt)
func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (restic.Backend, error) {
return open(ctx, cfg, rt)
}
// Create opens the S3 backend at bucket and region and creates the bucket if
// it does not exist yet.
func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
be, err := open(cfg, rt)
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (restic.Backend, error) {
be, err := open(ctx, cfg, rt)
if err != nil {
return nil, errors.Wrap(err, "open")
}
found, err := be.client.BucketExists(cfg.Bucket)
found, err := be.client.BucketExists(ctx, cfg.Bucket)
if err != nil && be.IsAccessDenied(err) {
err = nil
@@ -121,7 +124,7 @@ func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
if !found {
// create new bucket with default ACL in default region
err = be.client.MakeBucket(cfg.Bucket, "")
err = be.client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{})
if err != nil {
return nil, errors.Wrap(err, "client.MakeBucket")
}
@@ -176,7 +179,7 @@ func (fi fileInfo) IsDir() bool { return fi.isDir } // abbreviation for
func (fi fileInfo) Sys() interface{} { return nil } // underlying data source (can return nil)
// ReadDir returns the entries for a directory.
func (be *Backend) ReadDir(dir string) (list []os.FileInfo, err error) {
func (be *Backend) ReadDir(ctx context.Context, dir string) (list []os.FileInfo, err error) {
debug.Log("ReadDir(%v)", dir)
// make sure dir ends with a slash
@@ -184,10 +187,13 @@ func (be *Backend) ReadDir(dir string) (list []os.FileInfo, err error) {
dir += "/"
}
done := make(chan struct{})
defer close(done)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for obj := range be.client.ListObjects(be.cfg.Bucket, dir, false, done) {
for obj := range be.client.ListObjects(ctx, be.cfg.Bucket, minio.ListObjectsOptions{
Prefix: dir,
Recursive: false,
}) {
if obj.Err != nil {
return nil, err
}
@@ -248,7 +254,7 @@ func (be *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindRe
opts.ContentType = "application/octet-stream"
debug.Log("PutObject(%v, %v, %v)", be.cfg.Bucket, objName, rd.Length())
n, err := be.client.PutObjectWithContext(ctx, be.cfg.Bucket, objName, ioutil.NopCloser(rd), int64(rd.Length()), opts)
n, err := be.client.PutObject(ctx, be.cfg.Bucket, objName, ioutil.NopCloser(rd), int64(rd.Length()), opts)
debug.Log("%v -> %v bytes, err %#v: %v", objName, n, err, err)
@@ -305,7 +311,7 @@ func (be *Backend) openReader(ctx context.Context, h restic.Handle, length int,
be.sem.GetToken()
coreClient := minio.Core{Client: be.client}
rd, _, _, err := coreClient.GetObjectWithContext(ctx, be.cfg.Bucket, objName, opts)
rd, _, _, err := coreClient.GetObject(ctx, be.cfg.Bucket, objName, opts)
if err != nil {
be.sem.ReleaseToken()
return nil, err
@@ -332,7 +338,7 @@ func (be *Backend) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInf
opts := minio.GetObjectOptions{}
be.sem.GetToken()
obj, err = be.client.GetObjectWithContext(ctx, be.cfg.Bucket, objName, opts)
obj, err = be.client.GetObject(ctx, be.cfg.Bucket, objName, opts)
if err != nil {
debug.Log("GetObject() err %v", err)
be.sem.ReleaseToken()
@@ -363,7 +369,7 @@ func (be *Backend) Test(ctx context.Context, h restic.Handle) (bool, error) {
objName := be.Filename(h)
be.sem.GetToken()
_, err := be.client.StatObject(be.cfg.Bucket, objName, minio.StatObjectOptions{})
_, err := be.client.StatObject(ctx, be.cfg.Bucket, objName, minio.StatObjectOptions{})
be.sem.ReleaseToken()
if err == nil {
@@ -379,7 +385,7 @@ func (be *Backend) Remove(ctx context.Context, h restic.Handle) error {
objName := be.Filename(h)
be.sem.GetToken()
err := be.client.RemoveObject(be.cfg.Bucket, objName)
err := be.client.RemoveObject(ctx, be.cfg.Bucket, objName, minio.RemoveObjectOptions{})
be.sem.ReleaseToken()
debug.Log("Remove(%v) at %v -> err %v", h, objName, err)
@@ -409,7 +415,10 @@ func (be *Backend) List(ctx context.Context, t restic.FileType, fn func(restic.F
// NB: unfortunately we can't protect this with be.sem.GetToken() here.
// Doing so would enable a deadlock situation (gh-1399), as ListObjects()
// starts its own goroutine and returns results via a channel.
listresp := be.client.ListObjects(be.cfg.Bucket, prefix, recursive, ctx.Done())
listresp := be.client.ListObjects(ctx, be.cfg.Bucket, minio.ListObjectsOptions{
Prefix: prefix,
Recursive: recursive,
})
for obj := range listresp {
if obj.Err != nil {
@@ -473,7 +482,7 @@ func (be *Backend) Delete(ctx context.Context) error {
func (be *Backend) Close() error { return nil }
// Rename moves a file based on the new layout l.
func (be *Backend) Rename(h restic.Handle, l backend.Layout) error {
func (be *Backend) Rename(ctx context.Context, h restic.Handle, l backend.Layout) error {
debug.Log("Rename %v to %v", h, l)
oldname := be.Filename(h)
newname := l.Filename(h)
@@ -485,14 +494,17 @@ func (be *Backend) Rename(h restic.Handle, l backend.Layout) error {
debug.Log(" %v -> %v", oldname, newname)
src := minio.NewSourceInfo(be.cfg.Bucket, oldname, nil)
dst, err := minio.NewDestinationInfo(be.cfg.Bucket, newname, nil, nil)
if err != nil {
return errors.Wrap(err, "NewDestinationInfo")
src := minio.CopySrcOptions{
Bucket: be.cfg.Bucket,
Object: oldname,
}
err = be.client.CopyObject(dst, src)
dst := minio.CopyDestOptions{
Bucket: be.cfg.Bucket,
Object: newname,
}
_, err := be.client.CopyObject(ctx, dst, src)
if err != nil && be.IsNotExist(err) {
debug.Log("copy failed: %v, seems to already have been renamed", err)
return nil
@@ -503,5 +515,5 @@ func (be *Backend) Rename(h restic.Handle, l backend.Layout) error {
return err
}
return be.client.RemoveObject(be.cfg.Bucket, oldname)
return be.client.RemoveObject(ctx, be.cfg.Bucket, oldname, minio.RemoveObjectOptions{})
}

View File

@@ -107,7 +107,7 @@ type MinioTestConfig struct {
func createS3(t testing.TB, cfg MinioTestConfig, tr http.RoundTripper) (be restic.Backend, err error) {
for i := 0; i < 10; i++ {
be, err = s3.Create(cfg.Config, tr)
be, err = s3.Create(context.TODO(), cfg.Config, tr)
if err != nil {
t.Logf("s3 open: try %d: error %v", i, err)
time.Sleep(500 * time.Millisecond)
@@ -154,7 +154,7 @@ func newMinioTestSuite(ctx context.Context, t testing.TB) *test.Suite {
return nil, err
}
exists, err := be.Test(context.TODO(), restic.Handle{Type: restic.ConfigFile})
exists, err := be.Test(ctx, restic.Handle{Type: restic.ConfigFile})
if err != nil {
return nil, err
}
@@ -169,7 +169,7 @@ func newMinioTestSuite(ctx context.Context, t testing.TB) *test.Suite {
// OpenFn is a function that opens a previously created temporary repository.
Open: func(config interface{}) (restic.Backend, error) {
cfg := config.(MinioTestConfig)
return s3.Open(cfg.Config, tr)
return s3.Open(ctx, cfg.Config, tr)
},
// CleanupFn removes data created during the tests.
@@ -248,7 +248,7 @@ func newS3TestSuite(t testing.TB) *test.Suite {
Create: func(config interface{}) (restic.Backend, error) {
cfg := config.(s3.Config)
be, err := s3.Create(cfg, tr)
be, err := s3.Create(context.TODO(), cfg, tr)
if err != nil {
return nil, err
}
@@ -268,14 +268,14 @@ func newS3TestSuite(t testing.TB) *test.Suite {
// OpenFn is a function that opens a previously created temporary repository.
Open: func(config interface{}) (restic.Backend, error) {
cfg := config.(s3.Config)
return s3.Open(cfg, tr)
return s3.Open(context.TODO(), cfg, tr)
},
// CleanupFn removes data created during the tests.
Cleanup: func(config interface{}) error {
cfg := config.(s3.Config)
be, err := s3.Open(cfg, tr)
be, err := s3.Open(context.TODO(), cfg, tr)
if err != nil {
return err
}

View File

@@ -42,7 +42,7 @@ func TestLayout(t *testing.T) {
rtest.SetupTarTestFixture(t, path, filepath.Join("..", "testdata", test.filename))
repo := filepath.Join(path, "repo")
be, err := sftp.Open(sftp.Config{
be, err := sftp.Open(context.TODO(), sftp.Config{
Command: fmt.Sprintf("%q -e", sftpServer),
Path: repo,
Layout: test.layout,

View File

@@ -109,7 +109,7 @@ func (r *SFTP) clientError() error {
// Open opens an sftp backend as described by the config by running
// "ssh" with the appropriate arguments (or cfg.Command, if set). The function
// preExec is run just before, postExec just after starting a program.
func Open(cfg Config) (*SFTP, error) {
func Open(ctx context.Context, cfg Config) (*SFTP, error) {
debug.Log("open backend with config %#v", cfg)
cmd, args, err := buildSSHCommand(cfg)
@@ -123,7 +123,7 @@ func Open(cfg Config) (*SFTP, error) {
return nil, err
}
sftp.Layout, err = backend.ParseLayout(sftp, cfg.Layout, defaultLayout, cfg.Path)
sftp.Layout, err = backend.ParseLayout(ctx, sftp, cfg.Layout, defaultLayout, cfg.Path)
if err != nil {
return nil, err
}
@@ -152,7 +152,7 @@ func (r *SFTP) Join(p ...string) string {
}
// ReadDir returns the entries for a directory.
func (r *SFTP) ReadDir(dir string) ([]os.FileInfo, error) {
func (r *SFTP) ReadDir(ctx context.Context, dir string) ([]os.FileInfo, error) {
fi, err := r.c.ReadDir(dir)
// sftp client does not specify dir name on error, so add it here
@@ -207,7 +207,7 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
// Create creates an sftp backend as described by the config by running "ssh"
// with the appropriate arguments (or cfg.Command, if set). The function
// preExec is run just before, postExec just after starting a program.
func Create(cfg Config) (*SFTP, error) {
func Create(ctx context.Context, cfg Config) (*SFTP, error) {
cmd, args, err := buildSSHCommand(cfg)
if err != nil {
return nil, err
@@ -219,7 +219,7 @@ func Create(cfg Config) (*SFTP, error) {
return nil, err
}
sftp.Layout, err = backend.ParseLayout(sftp, cfg.Layout, defaultLayout, cfg.Path)
sftp.Layout, err = backend.ParseLayout(ctx, sftp, cfg.Layout, defaultLayout, cfg.Path)
if err != nil {
return nil, err
}
@@ -241,7 +241,7 @@ func Create(cfg Config) (*SFTP, error) {
}
// open backend
return Open(cfg)
return Open(ctx, cfg)
}
// Location returns this backend's location (the directory name).
@@ -467,8 +467,8 @@ func (r *SFTP) Close() error {
return nil
}
func (r *SFTP) deleteRecursive(name string) error {
entries, err := r.ReadDir(name)
func (r *SFTP) deleteRecursive(ctx context.Context, name string) error {
entries, err := r.ReadDir(ctx, name)
if err != nil {
return errors.Wrap(err, "ReadDir")
}
@@ -476,7 +476,7 @@ func (r *SFTP) deleteRecursive(name string) error {
for _, fi := range entries {
itemName := r.Join(name, fi.Name())
if fi.IsDir() {
err := r.deleteRecursive(itemName)
err := r.deleteRecursive(ctx, itemName)
if err != nil {
return errors.Wrap(err, "ReadDir")
}
@@ -499,6 +499,6 @@ func (r *SFTP) deleteRecursive(name string) error {
}
// Delete removes all data in the backend.
func (r *SFTP) Delete(context.Context) error {
return r.deleteRecursive(r.p)
func (r *SFTP) Delete(ctx context.Context) error {
return r.deleteRecursive(ctx, r.p)
}

View File

@@ -1,6 +1,7 @@
package sftp_test
import (
"context"
"fmt"
"io/ioutil"
"os"
@@ -50,13 +51,13 @@ func newTestSuite(t testing.TB) *test.Suite {
// CreateFn is a function that creates a temporary repository for the tests.
Create: func(config interface{}) (restic.Backend, error) {
cfg := config.(sftp.Config)
return sftp.Create(cfg)
return sftp.Create(context.TODO(), cfg)
},
// OpenFn is a function that opens a previously created temporary repository.
Open: func(config interface{}) (restic.Backend, error) {
cfg := config.(sftp.Config)
return sftp.Open(cfg)
return sftp.Open(context.TODO(), cfg)
},
// CleanupFn removes data created during the tests.

View File

@@ -64,7 +64,7 @@ func (m *S3Layout) moveFiles(ctx context.Context, be *s3.Backend, l backend.Layo
debug.Log("move %v", h)
return retry(maxErrors, printErr, func() error {
return be.Rename(h, l)
return be.Rename(ctx, h, l)
})
})
}

View File

@@ -76,7 +76,7 @@ func TestRepository(t testing.TB) (r restic.Repository, cleanup func()) {
if dir != "" {
_, err := os.Stat(dir)
if err != nil {
be, err := local.Create(local.Config{Path: dir})
be, err := local.Create(context.TODO(), local.Config{Path: dir})
if err != nil {
t.Fatalf("error creating local backend at %v: %v", dir, err)
}
@@ -93,7 +93,7 @@ func TestRepository(t testing.TB) (r restic.Repository, cleanup func()) {
// TestOpenLocal opens a local repository.
func TestOpenLocal(t testing.TB, dir string) (r restic.Repository) {
be, err := local.Open(local.Config{Path: dir})
be, err := local.Open(context.TODO(), local.Config{Path: dir})
if err != nil {
t.Fatal(err)
}