forked from TrueCloudLab/restic
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:
parent
56836364a4
commit
7d12c29286
16 changed files with 235 additions and 142 deletions
|
@ -87,9 +87,9 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
be, err := create(ctx, repo, gopts.extended)
|
be, err := create(ctx, repo, gopts, gopts.extended)
|
||||||
if err != nil {
|
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{
|
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)
|
err = s.Init(ctx, version, gopts.password, chunkerPolynomial)
|
||||||
if err != nil {
|
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 {
|
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 {
|
if opts.CopyChunkerParameters && chunkerPolynomial != nil {
|
||||||
Verbosef(" with chunker parameters copied from secondary repository\n")
|
Verbosef(" with chunker parameters copied from secondary repository\n")
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,7 +121,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
||||||
status := initSuccess{
|
status := initSuccess{
|
||||||
MessageType: "initialized",
|
MessageType: "initialized",
|
||||||
ID: s.Config().ID,
|
ID: s.Config().ID,
|
||||||
Repository: location.StripPassword(gopts.Repo),
|
Repository: location.StripPassword(gopts.backends, gopts.Repo),
|
||||||
}
|
}
|
||||||
return json.NewEncoder(globalOptions.stdout).Encode(status)
|
return json.NewEncoder(globalOptions.stdout).Encode(status)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ type GlobalOptions struct {
|
||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
stderr io.Writer
|
stderr io.Writer
|
||||||
|
|
||||||
|
backends *location.Registry
|
||||||
backendTestHook, backendInnerTestHook backendWrapper
|
backendTestHook, backendInnerTestHook backendWrapper
|
||||||
|
|
||||||
// verbosity is set as follows:
|
// verbosity is set as follows:
|
||||||
|
@ -98,6 +99,18 @@ var isReadingPassword bool
|
||||||
var internalGlobalCtx context.Context
|
var internalGlobalCtx context.Context
|
||||||
|
|
||||||
func init() {
|
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
|
var cancel context.CancelFunc
|
||||||
internalGlobalCtx, cancel = context.WithCancel(context.Background())
|
internalGlobalCtx, cancel = context.WithCancel(context.Background())
|
||||||
AddCleanupHandler(func(code int) (int, error) {
|
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.
|
// Open the backend specified by a location config.
|
||||||
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (restic.Backend, error) {
|
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options) (restic.Backend, error) {
|
||||||
debug.Log("parsing location %v", location.StripPassword(s))
|
debug.Log("parsing location %v", location.StripPassword(gopts.backends, s))
|
||||||
loc, err := location.Parse(s)
|
loc, err := location.Parse(gopts.backends, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Fatalf("parsing repository location failed: %v", err)
|
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)
|
lim := limiter.NewStaticLimiter(gopts.Limits)
|
||||||
rt = lim.Transport(rt)
|
rt = lim.Transport(rt)
|
||||||
|
|
||||||
switch loc.Scheme {
|
factory := gopts.backends.Lookup(loc.Scheme)
|
||||||
case "local":
|
if factory == nil {
|
||||||
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:
|
|
||||||
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
|
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
be, err = factory.Open(ctx, cfg, rt, lim)
|
||||||
if err != nil {
|
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
|
// 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
|
// check if config is there
|
||||||
fi, err := be.Stat(ctx, restic.Handle{Type: restic.ConfigFile})
|
fi, err := be.Stat(ctx, restic.Handle{Type: restic.ConfigFile})
|
||||||
if err != nil {
|
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 {
|
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.
|
// 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)
|
debug.Log("parsing location %v", s)
|
||||||
loc, err := location.Parse(s)
|
loc, err := location.Parse(gopts.backends, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -651,31 +646,12 @@ func create(ctx context.Context, s string, opts options.Options) (restic.Backend
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var be restic.Backend
|
factory := gopts.backends.Lookup(loc.Scheme)
|
||||||
switch loc.Scheme {
|
if factory == nil {
|
||||||
case "local":
|
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
be, err := factory.Create(ctx, cfg, rt, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// 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 },
|
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
|
// always overwrite global options
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -43,6 +44,10 @@ const defaultListMaxItems = 5000
|
||||||
// make sure that *Backend implements backend.Backend
|
// make sure that *Backend implements backend.Backend
|
||||||
var _ restic.Backend = &Backend{}
|
var _ restic.Backend = &Backend{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, location.NoPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||||
debug.Log("open, config %#v", cfg)
|
debug.Log("open, config %#v", cfg)
|
||||||
var client *azContainer.Client
|
var client *azContainer.Client
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -36,6 +37,10 @@ const defaultListMaxItems = 10 * 1000
|
||||||
// ensure statically that *b2Backend implements restic.Backend.
|
// ensure statically that *b2Backend implements restic.Backend.
|
||||||
var _ restic.Backend = &b2Backend{}
|
var _ restic.Backend = &b2Backend{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, location.NoPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
type sniffingRoundTripper struct {
|
type sniffingRoundTripper struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
lastErr error
|
lastErr error
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
|
||||||
|
@ -47,6 +48,10 @@ type Backend struct {
|
||||||
// Ensure that *Backend implements restic.Backend.
|
// Ensure that *Backend implements restic.Backend.
|
||||||
var _ restic.Backend = &Backend{}
|
var _ restic.Backend = &Backend{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, location.NoPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
func getStorageClient(rt http.RoundTripper) (*storage.Client, error) {
|
func getStorageClient(rt http.RoundTripper) (*storage.Client, error) {
|
||||||
// create a new HTTP client
|
// create a new HTTP client
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/limiter"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
@ -28,6 +30,14 @@ type Local struct {
|
||||||
// ensure statically that *Local implements restic.Backend.
|
// ensure statically that *Local implements restic.Backend.
|
||||||
var _ restic.Backend = &Local{}
|
var _ restic.Backend = &Local{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewLimitedBackendFactory(ParseConfig, location.NoPassword, func(ctx context.Context, cfg Config, _ limiter.Limiter) (*Local, error) {
|
||||||
|
return Create(ctx, cfg)
|
||||||
|
}, func(ctx context.Context, cfg Config, _ limiter.Limiter) (*Local, error) {
|
||||||
|
return Open(ctx, cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const defaultLayout = "default"
|
const defaultLayout = "default"
|
||||||
|
|
||||||
func open(ctx context.Context, cfg Config) (*Local, error) {
|
func open(ctx context.Context, cfg Config) (*Local, error) {
|
||||||
|
|
|
@ -4,15 +4,6 @@ package location
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend/azure"
|
|
||||||
"github.com/restic/restic/internal/backend/b2"
|
|
||||||
"github.com/restic/restic/internal/backend/gs"
|
|
||||||
"github.com/restic/restic/internal/backend/local"
|
|
||||||
"github.com/restic/restic/internal/backend/rclone"
|
|
||||||
"github.com/restic/restic/internal/backend/rest"
|
|
||||||
"github.com/restic/restic/internal/backend/s3"
|
|
||||||
"github.com/restic/restic/internal/backend/sftp"
|
|
||||||
"github.com/restic/restic/internal/backend/swift"
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,34 +14,8 @@ type Location struct {
|
||||||
Config interface{}
|
Config interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type parser struct {
|
// NoPassword returns the repository location unchanged (there's no sensitive information there)
|
||||||
scheme string
|
func NoPassword(s string) string {
|
||||||
parse func(string) (interface{}, error)
|
|
||||||
stripPassword func(string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func configToAny[C any](parser func(string) (*C, error)) func(string) (interface{}, error) {
|
|
||||||
return func(s string) (interface{}, error) {
|
|
||||||
return parser(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsers is a list of valid config parsers for the backends. The first parser
|
|
||||||
// is the fallback and should always be set to the local backend.
|
|
||||||
var parsers = []parser{
|
|
||||||
{"b2", configToAny(b2.ParseConfig), noPassword},
|
|
||||||
{"local", configToAny(local.ParseConfig), noPassword},
|
|
||||||
{"sftp", configToAny(sftp.ParseConfig), noPassword},
|
|
||||||
{"s3", configToAny(s3.ParseConfig), noPassword},
|
|
||||||
{"gs", configToAny(gs.ParseConfig), noPassword},
|
|
||||||
{"azure", configToAny(azure.ParseConfig), noPassword},
|
|
||||||
{"swift", configToAny(swift.ParseConfig), noPassword},
|
|
||||||
{"rest", configToAny(rest.ParseConfig), rest.StripPassword},
|
|
||||||
{"rclone", configToAny(rclone.ParseConfig), noPassword},
|
|
||||||
}
|
|
||||||
|
|
||||||
// noPassword returns the repository location unchanged (there's no sensitive information there)
|
|
||||||
func noPassword(s string) string {
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,16 +53,13 @@ func isPath(s string) bool {
|
||||||
// starts with a backend name followed by a colon, that backend's Parse()
|
// starts with a backend name followed by a colon, that backend's Parse()
|
||||||
// function is called. Otherwise, the local backend is used which interprets s
|
// function is called. Otherwise, the local backend is used which interprets s
|
||||||
// as the name of a directory.
|
// as the name of a directory.
|
||||||
func Parse(s string) (u Location, err error) {
|
func Parse(registry *Registry, s string) (u Location, err error) {
|
||||||
scheme := extractScheme(s)
|
scheme := extractScheme(s)
|
||||||
u.Scheme = scheme
|
u.Scheme = scheme
|
||||||
|
|
||||||
for _, parser := range parsers {
|
factory := registry.Lookup(scheme)
|
||||||
if parser.scheme != scheme {
|
if factory != nil {
|
||||||
continue
|
u.Config, err = factory.ParseConfig(s)
|
||||||
}
|
|
||||||
|
|
||||||
u.Config, err = parser.parse(s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Location{}, err
|
return Location{}, err
|
||||||
}
|
}
|
||||||
|
@ -111,7 +73,12 @@ func Parse(s string) (u Location, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Scheme = "local"
|
u.Scheme = "local"
|
||||||
u.Config, err = local.ParseConfig("local:" + s)
|
factory = registry.Lookup(u.Scheme)
|
||||||
|
if factory == nil {
|
||||||
|
return Location{}, errors.New("local backend not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Config, err = factory.ParseConfig("local:" + s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Location{}, err
|
return Location{}, err
|
||||||
}
|
}
|
||||||
|
@ -120,14 +87,12 @@ func Parse(s string) (u Location, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripPassword returns a displayable version of a repository location (with any sensitive information removed)
|
// StripPassword returns a displayable version of a repository location (with any sensitive information removed)
|
||||||
func StripPassword(s string) string {
|
func StripPassword(registry *Registry, s string) string {
|
||||||
scheme := extractScheme(s)
|
scheme := extractScheme(s)
|
||||||
|
|
||||||
for _, parser := range parsers {
|
factory := registry.Lookup(scheme)
|
||||||
if parser.scheme != scheme {
|
if factory != nil {
|
||||||
continue
|
return factory.StripPassword(s)
|
||||||
}
|
|
||||||
return parser.stripPassword(s)
|
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package location
|
package location_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend/b2"
|
"github.com/restic/restic/internal/backend/b2"
|
||||||
"github.com/restic/restic/internal/backend/local"
|
"github.com/restic/restic/internal/backend/local"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/backend/rest"
|
"github.com/restic/restic/internal/backend/rest"
|
||||||
"github.com/restic/restic/internal/backend/s3"
|
"github.com/restic/restic/internal/backend/s3"
|
||||||
"github.com/restic/restic/internal/backend/sftp"
|
"github.com/restic/restic/internal/backend/sftp"
|
||||||
|
@ -24,11 +25,11 @@ func parseURL(s string) *url.URL {
|
||||||
|
|
||||||
var parseTests = []struct {
|
var parseTests = []struct {
|
||||||
s string
|
s string
|
||||||
u Location
|
u location.Location
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"local:/srv/repo",
|
"local:/srv/repo",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "/srv/repo",
|
Path: "/srv/repo",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -37,7 +38,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"local:dir1/dir2",
|
"local:dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "dir1/dir2",
|
Path: "dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -46,7 +47,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"local:dir1/dir2",
|
"local:dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "dir1/dir2",
|
Path: "dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -55,7 +56,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dir1/dir2",
|
"dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "dir1/dir2",
|
Path: "dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -64,7 +65,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/dir1/dir2",
|
"/dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "/dir1/dir2",
|
Path: "/dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -73,7 +74,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"local:../dir1/dir2",
|
"local:../dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "../dir1/dir2",
|
Path: "../dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -82,7 +83,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/dir1/dir2",
|
"/dir1/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "/dir1/dir2",
|
Path: "/dir1/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -91,7 +92,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/dir1:foobar/dir2",
|
"/dir1:foobar/dir2",
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: "/dir1:foobar/dir2",
|
Path: "/dir1:foobar/dir2",
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -100,7 +101,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`\dir1\foobar\dir2`,
|
`\dir1\foobar\dir2`,
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: `\dir1\foobar\dir2`,
|
Path: `\dir1\foobar\dir2`,
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -109,7 +110,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`c:\dir1\foobar\dir2`,
|
`c:\dir1\foobar\dir2`,
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: `c:\dir1\foobar\dir2`,
|
Path: `c:\dir1\foobar\dir2`,
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -118,7 +119,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
`C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: `C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
Path: `C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -127,7 +128,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`c:/dir1/foobar/dir2`,
|
`c:/dir1/foobar/dir2`,
|
||||||
Location{Scheme: "local",
|
location.Location{Scheme: "local",
|
||||||
Config: &local.Config{
|
Config: &local.Config{
|
||||||
Path: `c:/dir1/foobar/dir2`,
|
Path: `c:/dir1/foobar/dir2`,
|
||||||
Connections: 2,
|
Connections: 2,
|
||||||
|
@ -136,7 +137,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sftp:user@host:/srv/repo",
|
"sftp:user@host:/srv/repo",
|
||||||
Location{Scheme: "sftp",
|
location.Location{Scheme: "sftp",
|
||||||
Config: &sftp.Config{
|
Config: &sftp.Config{
|
||||||
User: "user",
|
User: "user",
|
||||||
Host: "host",
|
Host: "host",
|
||||||
|
@ -147,7 +148,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sftp:host:/srv/repo",
|
"sftp:host:/srv/repo",
|
||||||
Location{Scheme: "sftp",
|
location.Location{Scheme: "sftp",
|
||||||
Config: &sftp.Config{
|
Config: &sftp.Config{
|
||||||
User: "",
|
User: "",
|
||||||
Host: "host",
|
Host: "host",
|
||||||
|
@ -158,7 +159,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sftp://user@host/srv/repo",
|
"sftp://user@host/srv/repo",
|
||||||
Location{Scheme: "sftp",
|
location.Location{Scheme: "sftp",
|
||||||
Config: &sftp.Config{
|
Config: &sftp.Config{
|
||||||
User: "user",
|
User: "user",
|
||||||
Host: "host",
|
Host: "host",
|
||||||
|
@ -169,7 +170,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sftp://user@host//srv/repo",
|
"sftp://user@host//srv/repo",
|
||||||
Location{Scheme: "sftp",
|
location.Location{Scheme: "sftp",
|
||||||
Config: &sftp.Config{
|
Config: &sftp.Config{
|
||||||
User: "user",
|
User: "user",
|
||||||
Host: "host",
|
Host: "host",
|
||||||
|
@ -181,7 +182,7 @@ var parseTests = []struct {
|
||||||
|
|
||||||
{
|
{
|
||||||
"s3://eu-central-1/bucketname",
|
"s3://eu-central-1/bucketname",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "eu-central-1",
|
Endpoint: "eu-central-1",
|
||||||
Bucket: "bucketname",
|
Bucket: "bucketname",
|
||||||
|
@ -192,7 +193,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3://hostname.foo/bucketname",
|
"s3://hostname.foo/bucketname",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "hostname.foo",
|
Endpoint: "hostname.foo",
|
||||||
Bucket: "bucketname",
|
Bucket: "bucketname",
|
||||||
|
@ -203,7 +204,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3://hostname.foo/bucketname/prefix/directory",
|
"s3://hostname.foo/bucketname/prefix/directory",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "hostname.foo",
|
Endpoint: "hostname.foo",
|
||||||
Bucket: "bucketname",
|
Bucket: "bucketname",
|
||||||
|
@ -214,7 +215,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3:eu-central-1/repo",
|
"s3:eu-central-1/repo",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "eu-central-1",
|
Endpoint: "eu-central-1",
|
||||||
Bucket: "repo",
|
Bucket: "repo",
|
||||||
|
@ -225,7 +226,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3:eu-central-1/repo/prefix/directory",
|
"s3:eu-central-1/repo/prefix/directory",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "eu-central-1",
|
Endpoint: "eu-central-1",
|
||||||
Bucket: "repo",
|
Bucket: "repo",
|
||||||
|
@ -236,7 +237,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3:https://hostname.foo/repo",
|
"s3:https://hostname.foo/repo",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "hostname.foo",
|
Endpoint: "hostname.foo",
|
||||||
Bucket: "repo",
|
Bucket: "repo",
|
||||||
|
@ -247,7 +248,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3:https://hostname.foo/repo/prefix/directory",
|
"s3:https://hostname.foo/repo/prefix/directory",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "hostname.foo",
|
Endpoint: "hostname.foo",
|
||||||
Bucket: "repo",
|
Bucket: "repo",
|
||||||
|
@ -258,7 +259,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"s3:http://hostname.foo/repo",
|
"s3:http://hostname.foo/repo",
|
||||||
Location{Scheme: "s3",
|
location.Location{Scheme: "s3",
|
||||||
Config: &s3.Config{
|
Config: &s3.Config{
|
||||||
Endpoint: "hostname.foo",
|
Endpoint: "hostname.foo",
|
||||||
Bucket: "repo",
|
Bucket: "repo",
|
||||||
|
@ -270,7 +271,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"swift:container17:/",
|
"swift:container17:/",
|
||||||
Location{Scheme: "swift",
|
location.Location{Scheme: "swift",
|
||||||
Config: &swift.Config{
|
Config: &swift.Config{
|
||||||
Container: "container17",
|
Container: "container17",
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
|
@ -280,7 +281,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"swift:container17:/prefix97",
|
"swift:container17:/prefix97",
|
||||||
Location{Scheme: "swift",
|
location.Location{Scheme: "swift",
|
||||||
Config: &swift.Config{
|
Config: &swift.Config{
|
||||||
Container: "container17",
|
Container: "container17",
|
||||||
Prefix: "prefix97",
|
Prefix: "prefix97",
|
||||||
|
@ -290,7 +291,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rest:http://hostname.foo:1234/",
|
"rest:http://hostname.foo:1234/",
|
||||||
Location{Scheme: "rest",
|
location.Location{Scheme: "rest",
|
||||||
Config: &rest.Config{
|
Config: &rest.Config{
|
||||||
URL: parseURL("http://hostname.foo:1234/"),
|
URL: parseURL("http://hostname.foo:1234/"),
|
||||||
Connections: 5,
|
Connections: 5,
|
||||||
|
@ -298,7 +299,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"b2:bucketname:/prefix", Location{Scheme: "b2",
|
"b2:bucketname:/prefix", location.Location{Scheme: "b2",
|
||||||
Config: &b2.Config{
|
Config: &b2.Config{
|
||||||
Bucket: "bucketname",
|
Bucket: "bucketname",
|
||||||
Prefix: "prefix",
|
Prefix: "prefix",
|
||||||
|
@ -307,7 +308,7 @@ var parseTests = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"b2:bucketname", Location{Scheme: "b2",
|
"b2:bucketname", location.Location{Scheme: "b2",
|
||||||
Config: &b2.Config{
|
Config: &b2.Config{
|
||||||
Bucket: "bucketname",
|
Bucket: "bucketname",
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
|
@ -320,7 +321,7 @@ var parseTests = []struct {
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
for i, test := range parseTests {
|
for i, test := range parseTests {
|
||||||
t.Run(test.s, func(t *testing.T) {
|
t.Run(test.s, func(t *testing.T) {
|
||||||
u, err := Parse(test.s)
|
u, err := location.Parse(test.s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -346,7 +347,7 @@ func TestInvalidScheme(t *testing.T) {
|
||||||
|
|
||||||
for _, s := range invalidSchemes {
|
for _, s := range invalidSchemes {
|
||||||
t.Run(s, func(t *testing.T) {
|
t.Run(s, func(t *testing.T) {
|
||||||
_, err := Parse(s)
|
_, err := location.Parse(s)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("error for invalid location %q not found", s)
|
t.Fatalf("error for invalid location %q not found", s)
|
||||||
}
|
}
|
||||||
|
|
94
internal/backend/location/registry.go
Normal file
94
internal/backend/location/registry.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package location
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/backend/limiter"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
factories map[string]Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
factories: make(map[string]Factory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Register(scheme string, factory Factory) {
|
||||||
|
if r.factories[scheme] != nil {
|
||||||
|
panic("duplicate backend")
|
||||||
|
}
|
||||||
|
r.factories[scheme] = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Lookup(scheme string) Factory {
|
||||||
|
return r.factories[scheme]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Factory interface {
|
||||||
|
ParseConfig(s string) (interface{}, error)
|
||||||
|
StripPassword(s string) string
|
||||||
|
Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (restic.Backend, error)
|
||||||
|
Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (restic.Backend, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericBackendFactory[C any, T restic.Backend] struct {
|
||||||
|
parseConfigFn func(s string) (*C, error)
|
||||||
|
stripPasswordFn func(s string) string
|
||||||
|
createFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter) (T, error)
|
||||||
|
openFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter) (T, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *GenericBackendFactory[C, T]) ParseConfig(s string) (interface{}, error) {
|
||||||
|
return f.parseConfigFn(s)
|
||||||
|
}
|
||||||
|
func (f *GenericBackendFactory[C, T]) StripPassword(s string) string {
|
||||||
|
if f.stripPasswordFn != nil {
|
||||||
|
return f.stripPasswordFn(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (f *GenericBackendFactory[C, T]) Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (restic.Backend, error) {
|
||||||
|
return f.createFn(ctx, *cfg.(*C), rt, lim)
|
||||||
|
}
|
||||||
|
func (f *GenericBackendFactory[C, T]) Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (restic.Backend, error) {
|
||||||
|
return f.openFn(ctx, *cfg.(*C), rt, lim)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPBackendFactory[C any, T restic.Backend](parseConfigFn func(s string) (*C, error),
|
||||||
|
stripPasswordFn func(s string) string,
|
||||||
|
createFn func(ctx context.Context, cfg C, rt http.RoundTripper) (T, error),
|
||||||
|
openFn func(ctx context.Context, cfg C, rt http.RoundTripper) (T, error)) *GenericBackendFactory[C, T] {
|
||||||
|
|
||||||
|
return &GenericBackendFactory[C, T]{
|
||||||
|
parseConfigFn: parseConfigFn,
|
||||||
|
stripPasswordFn: stripPasswordFn,
|
||||||
|
createFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter) (T, error) {
|
||||||
|
return createFn(ctx, cfg, rt)
|
||||||
|
},
|
||||||
|
openFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter) (T, error) {
|
||||||
|
return openFn(ctx, cfg, rt)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLimitedBackendFactory[C any, T restic.Backend](parseConfigFn func(s string) (*C, error),
|
||||||
|
stripPasswordFn func(s string) string,
|
||||||
|
createFn func(ctx context.Context, cfg C, lim limiter.Limiter) (T, error),
|
||||||
|
openFn func(ctx context.Context, cfg C, lim limiter.Limiter) (T, error)) *GenericBackendFactory[C, T] {
|
||||||
|
|
||||||
|
return &GenericBackendFactory[C, T]{
|
||||||
|
parseConfigFn: parseConfigFn,
|
||||||
|
stripPasswordFn: stripPasswordFn,
|
||||||
|
createFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter) (T, error) {
|
||||||
|
return createFn(ctx, cfg, lim)
|
||||||
|
},
|
||||||
|
openFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter) (T, error) {
|
||||||
|
return openFn(ctx, cfg, lim)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/limiter"
|
"github.com/restic/restic/internal/backend/limiter"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/backend/rest"
|
"github.com/restic/restic/internal/backend/rest"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
@ -36,6 +37,10 @@ type Backend struct {
|
||||||
conn *StdioConn
|
conn *StdioConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewLimitedBackendFactory(ParseConfig, location.NoPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
// run starts command with args and initializes the StdioConn.
|
// run starts command with args and initializes the StdioConn.
|
||||||
func run(command string, args ...string) (*StdioConn, *sync.WaitGroup, chan struct{}, func() error, error) {
|
func run(command string, args ...string) (*StdioConn, *sync.WaitGroup, chan struct{}, func() error, error) {
|
||||||
cmd := exec.Command(command, args...)
|
cmd := exec.Command(command, args...)
|
||||||
|
@ -283,8 +288,8 @@ func Open(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initializes a new restic repo with rclone.
|
// Create initializes a new restic repo with rclone.
|
||||||
func Create(ctx context.Context, cfg Config) (*Backend, error) {
|
func Create(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||||
be, err := newBackend(ctx, cfg, nil)
|
be, err := newBackend(ctx, cfg, lim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ func newTestSuite(t testing.TB) *test.Suite[rclone.Config] {
|
||||||
// CreateFn is a function that creates a temporary repository for the tests.
|
// CreateFn is a function that creates a temporary repository for the tests.
|
||||||
Create: func(cfg rclone.Config) (restic.Backend, error) {
|
Create: func(cfg rclone.Config) (restic.Backend, error) {
|
||||||
t.Logf("Create()")
|
t.Logf("Create()")
|
||||||
be, err := rclone.Create(context.TODO(), cfg)
|
be, err := rclone.Create(context.TODO(), cfg, nil)
|
||||||
var e *exec.Error
|
var e *exec.Error
|
||||||
if errors.As(err, &e) && e.Err == exec.ErrNotFound {
|
if errors.As(err, &e) && e.Err == exec.ErrNotFound {
|
||||||
t.Skipf("program %q not found", e.Name)
|
t.Skipf("program %q not found", e.Name)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -29,6 +30,10 @@ type Backend struct {
|
||||||
layout.Layout
|
layout.Layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, StripPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
// the REST API protocol version is decided by HTTP request headers, these are the constants.
|
// the REST API protocol version is decided by HTTP request headers, these are the constants.
|
||||||
const (
|
const (
|
||||||
ContentTypeV1 = "application/vnd.x.restic.rest.v1"
|
ContentTypeV1 = "application/vnd.x.restic.rest.v1"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -31,6 +32,10 @@ type Backend struct {
|
||||||
// make sure that *Backend implements backend.Backend
|
// make sure that *Backend implements backend.Backend
|
||||||
var _ restic.Backend = &Backend{}
|
var _ restic.Backend = &Backend{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, location.NoPassword, Create, Open)
|
||||||
|
}
|
||||||
|
|
||||||
const defaultLayout = "default"
|
const defaultLayout = "default"
|
||||||
|
|
||||||
func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
func open(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||||
|
|
|
@ -15,6 +15,8 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/limiter"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -41,6 +43,14 @@ type SFTP struct {
|
||||||
|
|
||||||
var _ restic.Backend = &SFTP{}
|
var _ restic.Backend = &SFTP{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewLimitedBackendFactory(ParseConfig, location.NoPassword, func(ctx context.Context, cfg Config, _ limiter.Limiter) (*SFTP, error) {
|
||||||
|
return Create(ctx, cfg)
|
||||||
|
}, func(ctx context.Context, cfg Config, _ limiter.Limiter) (*SFTP, error) {
|
||||||
|
return Open(ctx, cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const defaultLayout = "default"
|
const defaultLayout = "default"
|
||||||
|
|
||||||
func startClient(cfg Config) (*SFTP, error) {
|
func startClient(cfg Config) (*SFTP, error) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/layout"
|
"github.com/restic/restic/internal/backend/layout"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -34,6 +35,10 @@ type beSwift struct {
|
||||||
// ensure statically that *beSwift implements restic.Backend.
|
// ensure statically that *beSwift implements restic.Backend.
|
||||||
var _ restic.Backend = &beSwift{}
|
var _ restic.Backend = &beSwift{}
|
||||||
|
|
||||||
|
func NewFactory() location.Factory {
|
||||||
|
return location.NewHTTPBackendFactory(ParseConfig, location.NoPassword, Open, Open)
|
||||||
|
}
|
||||||
|
|
||||||
// Open opens the swift backend at a container in region. The container is
|
// Open opens the swift backend at a container in region. The container is
|
||||||
// created if it does not exist yet.
|
// created if it does not exist yet.
|
||||||
func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (restic.Backend, error) {
|
func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (restic.Backend, error) {
|
||||||
|
|
Loading…
Reference in a new issue