backend: Unify backend construction using factory and registry

This unified construction removes most backend-specific code from
global.go. The backend registry will also enable integration tests to
use custom backends if necessary.
This commit is contained in:
Michael Eischer
2023-06-08 13:04:34 +02:00
parent 56836364a4
commit 7d12c29286
16 changed files with 235 additions and 142 deletions
+5 -5
View File
@@ -87,9 +87,9 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
return err
}
be, err := create(ctx, repo, gopts.extended)
be, err := create(ctx, repo, gopts, gopts.extended)
if err != nil {
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err)
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.backends, gopts.Repo), err)
}
s, err := repository.New(be, repository.Options{
@@ -102,11 +102,11 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
err = s.Init(ctx, version, gopts.password, chunkerPolynomial)
if err != nil {
return errors.Fatalf("create key in repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err)
return errors.Fatalf("create key in repository at %s failed: %v\n", location.StripPassword(gopts.backends, gopts.Repo), err)
}
if !gopts.JSON {
Verbosef("created restic repository %v at %s", s.Config().ID[:10], location.StripPassword(gopts.Repo))
Verbosef("created restic repository %v at %s", s.Config().ID[:10], location.StripPassword(gopts.backends, gopts.Repo))
if opts.CopyChunkerParameters && chunkerPolynomial != nil {
Verbosef(" with chunker parameters copied from secondary repository\n")
} else {
@@ -121,7 +121,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
status := initSuccess{
MessageType: "initialized",
ID: s.Config().ID,
Repository: location.StripPassword(gopts.Repo),
Repository: location.StripPassword(gopts.backends, gopts.Repo),
}
return json.NewEncoder(globalOptions.stdout).Encode(status)
}
+26 -50
View File
@@ -75,6 +75,7 @@ type GlobalOptions struct {
stdout io.Writer
stderr io.Writer
backends *location.Registry
backendTestHook, backendInnerTestHook backendWrapper
// verbosity is set as follows:
@@ -98,6 +99,18 @@ var isReadingPassword bool
var internalGlobalCtx context.Context
func init() {
backends := location.NewRegistry()
backends.Register("b2", b2.NewFactory())
backends.Register("local", local.NewFactory())
backends.Register("sftp", sftp.NewFactory())
backends.Register("s3", s3.NewFactory())
backends.Register("gs", gs.NewFactory())
backends.Register("azure", azure.NewFactory())
backends.Register("swift", swift.NewFactory())
backends.Register("rest", rest.NewFactory())
backends.Register("rclone", rclone.NewFactory())
globalOptions.backends = backends
var cancel context.CancelFunc
internalGlobalCtx, cancel = context.WithCancel(context.Background())
AddCleanupHandler(func(code int) (int, error) {
@@ -554,8 +567,8 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
// Open the backend specified by a location config.
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (restic.Backend, error) {
debug.Log("parsing location %v", location.StripPassword(s))
loc, err := location.Parse(s)
debug.Log("parsing location %v", location.StripPassword(gopts.backends, s))
loc, err := location.Parse(gopts.backends, s)
if err != nil {
return nil, errors.Fatalf("parsing repository location failed: %v", err)
}
@@ -576,32 +589,14 @@ func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Optio
lim := limiter.NewStaticLimiter(gopts.Limits)
rt = lim.Transport(rt)
switch loc.Scheme {
case "local":
be, err = local.Open(ctx, *cfg.(*local.Config))
case "sftp":
be, err = sftp.Open(ctx, *cfg.(*sftp.Config))
case "s3":
be, err = s3.Open(ctx, *cfg.(*s3.Config), rt)
case "gs":
be, err = gs.Open(ctx, *cfg.(*gs.Config), rt)
case "azure":
be, err = azure.Open(ctx, *cfg.(*azure.Config), rt)
case "swift":
be, err = swift.Open(ctx, *cfg.(*swift.Config), rt)
case "b2":
be, err = b2.Open(ctx, *cfg.(*b2.Config), rt)
case "rest":
be, err = rest.Open(ctx, *cfg.(*rest.Config), rt)
case "rclone":
be, err = rclone.Open(ctx, *cfg.(*rclone.Config), lim)
default:
factory := gopts.backends.Lookup(loc.Scheme)
if factory == nil {
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
}
be, err = factory.Open(ctx, cfg, rt, lim)
if err != nil {
return nil, errors.Fatalf("unable to open repository at %v: %v", location.StripPassword(s), err)
return nil, errors.Fatalf("unable to open repository at %v: %v", location.StripPassword(gopts.backends, s), err)
}
// wrap with debug logging and connection limiting
@@ -623,7 +618,7 @@ func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Optio
// check if config is there
fi, err := be.Stat(ctx, restic.Handle{Type: restic.ConfigFile})
if err != nil {
return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, location.StripPassword(s))
return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, location.StripPassword(gopts.backends, s))
}
if fi.Size == 0 {
@@ -634,9 +629,9 @@ func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Optio
}
// Create the backend specified by URI.
func create(ctx context.Context, s string, opts options.Options) (restic.Backend, error) {
func create(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (restic.Backend, error) {
debug.Log("parsing location %v", s)
loc, err := location.Parse(s)
loc, err := location.Parse(gopts.backends, s)
if err != nil {
return nil, err
}
@@ -651,31 +646,12 @@ func create(ctx context.Context, s string, opts options.Options) (restic.Backend
return nil, err
}
var be restic.Backend
switch loc.Scheme {
case "local":
be, err = local.Create(ctx, *cfg.(*local.Config))
case "sftp":
be, err = sftp.Create(ctx, *cfg.(*sftp.Config))
case "s3":
be, err = s3.Create(ctx, *cfg.(*s3.Config), rt)
case "gs":
be, err = gs.Create(ctx, *cfg.(*gs.Config), rt)
case "azure":
be, err = azure.Create(ctx, *cfg.(*azure.Config), rt)
case "swift":
be, err = swift.Open(ctx, *cfg.(*swift.Config), rt)
case "b2":
be, err = b2.Create(ctx, *cfg.(*b2.Config), rt)
case "rest":
be, err = rest.Create(ctx, *cfg.(*rest.Config), rt)
case "rclone":
be, err = rclone.Create(ctx, *cfg.(*rclone.Config))
default:
debug.Log("invalid repository scheme: %v", s)
return nil, errors.Fatalf("invalid scheme %q", loc.Scheme)
factory := gopts.backends.Lookup(loc.Scheme)
if factory == nil {
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
}
be, err := factory.Create(ctx, cfg, rt, nil)
if err != nil {
return nil, err
}
+2
View File
@@ -206,6 +206,8 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
// replace this hook with "nil" if listing a filetype more than once is necessary
backendTestHook: func(r restic.Backend) (restic.Backend, error) { return newOrderedListOnceBackend(r), nil },
// start with default set of backends
backends: globalOptions.backends,
}
// always overwrite global options