diff --git a/internal/backend/azure/azure_test.go b/internal/backend/azure/azure_test.go index ada6ec2ca..4e33e8a39 100644 --- a/internal/backend/azure/azure_test.go +++ b/internal/backend/azure/azure_test.go @@ -30,12 +30,11 @@ func newAzureTestSuite(t testing.TB) *test.Suite { // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - azcfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY")) + cfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY")) if err != nil { return nil, err } - cfg := azcfg.(azure.Config) cfg.AccountName = os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_NAME") cfg.AccountKey = options.NewSecretString(os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_KEY")) cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano()) @@ -141,12 +140,11 @@ func TestUploadLargeFile(t *testing.T) { return } - azcfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY")) + cfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY")) if err != nil { t.Fatal(err) } - cfg := azcfg.(azure.Config) cfg.AccountName = os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_NAME") cfg.AccountKey = options.NewSecretString(os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_KEY")) cfg.Prefix = fmt.Sprintf("test-upload-large-%d", time.Now().UnixNano()) diff --git a/internal/backend/azure/config.go b/internal/backend/azure/config.go index 771fb6c3c..adbc93430 100644 --- a/internal/backend/azure/config.go +++ b/internal/backend/azure/config.go @@ -34,9 +34,9 @@ func init() { // ParseConfig parses the string s and extracts the azure config. The // configuration format is azure:containerName:/[prefix]. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "azure:") { - return nil, errors.New("azure: invalid format") + return Config{}, errors.New("azure: invalid format") } // strip prefix "azure:" @@ -46,7 +46,7 @@ func ParseConfig(s string) (interface{}, error) { // remainder as prefix container, prefix, colon := strings.Cut(s, ":") if !colon { - return nil, errors.New("azure: invalid format: bucket name or path not found") + return Config{}, errors.New("azure: invalid format: bucket name or path not found") } prefix = strings.TrimPrefix(path.Clean(prefix), "/") cfg := NewConfig() diff --git a/internal/backend/b2/b2_test.go b/internal/backend/b2/b2_test.go index 123a61d7c..d7112410a 100644 --- a/internal/backend/b2/b2_test.go +++ b/internal/backend/b2/b2_test.go @@ -31,12 +31,11 @@ func newB2TestSuite(t testing.TB) *test.Suite { // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - b2cfg, err := b2.ParseConfig(os.Getenv("RESTIC_TEST_B2_REPOSITORY")) + cfg, err := b2.ParseConfig(os.Getenv("RESTIC_TEST_B2_REPOSITORY")) if err != nil { return nil, err } - cfg := b2cfg.(b2.Config) cfg.AccountID = os.Getenv("RESTIC_TEST_B2_ACCOUNT_ID") cfg.Key = options.NewSecretString(os.Getenv("RESTIC_TEST_B2_ACCOUNT_KEY")) cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano()) diff --git a/internal/backend/b2/config.go b/internal/backend/b2/config.go index 6572b7dd0..6244c1799 100644 --- a/internal/backend/b2/config.go +++ b/internal/backend/b2/config.go @@ -59,15 +59,15 @@ func checkBucketName(name string) error { // ParseConfig parses the string s and extracts the b2 config. The supported // configuration format is b2:bucketname/prefix. If no prefix is given the // prefix "restic" will be used. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "b2:") { - return nil, errors.New("invalid format, want: b2:bucket-name[:path]") + return Config{}, errors.New("invalid format, want: b2:bucket-name[:path]") } s = s[3:] bucket, prefix, _ := strings.Cut(s, ":") if err := checkBucketName(bucket); err != nil { - return nil, err + return Config{}, err } if len(prefix) > 0 { diff --git a/internal/backend/gs/config.go b/internal/backend/gs/config.go index a81ae565c..204d568e4 100644 --- a/internal/backend/gs/config.go +++ b/internal/backend/gs/config.go @@ -35,9 +35,9 @@ func init() { // ParseConfig parses the string s and extracts the gcs config. The // supported configuration format is gs:bucketName:/[prefix]. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "gs:") { - return nil, errors.New("gs: invalid format") + return Config{}, errors.New("gs: invalid format") } // strip prefix "gs:" @@ -47,7 +47,7 @@ func ParseConfig(s string) (interface{}, error) { // remainder as prefix bucket, prefix, colon := strings.Cut(s, ":") if !colon { - return nil, errors.New("gs: invalid format: bucket name or path not found") + return Config{}, errors.New("gs: invalid format: bucket name or path not found") } prefix = strings.TrimPrefix(path.Clean(prefix), "/") diff --git a/internal/backend/gs/gs_test.go b/internal/backend/gs/gs_test.go index 19ae8b829..20b51e5cc 100644 --- a/internal/backend/gs/gs_test.go +++ b/internal/backend/gs/gs_test.go @@ -27,12 +27,11 @@ func newGSTestSuite(t testing.TB) *test.Suite { // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - gscfg, err := gs.ParseConfig(os.Getenv("RESTIC_TEST_GS_REPOSITORY")) + cfg, err := gs.ParseConfig(os.Getenv("RESTIC_TEST_GS_REPOSITORY")) if err != nil { return nil, err } - cfg := gscfg.(gs.Config) cfg.ProjectID = os.Getenv("RESTIC_TEST_GS_PROJECT_ID") cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano()) return cfg, nil diff --git a/internal/backend/local/config.go b/internal/backend/local/config.go index e59d1f693..c68ae7765 100644 --- a/internal/backend/local/config.go +++ b/internal/backend/local/config.go @@ -27,9 +27,9 @@ func init() { } // ParseConfig parses a local backend config. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "local:") { - return nil, errors.New(`invalid format, prefix "local" not found`) + return Config{}, errors.New(`invalid format, prefix "local" not found`) } cfg := NewConfig() diff --git a/internal/backend/location/location.go b/internal/backend/location/location.go index a732233cc..6b7683be5 100644 --- a/internal/backend/location/location.go +++ b/internal/backend/location/location.go @@ -29,18 +29,24 @@ type parser struct { 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", b2.ParseConfig, noPassword}, - {"local", local.ParseConfig, noPassword}, - {"sftp", sftp.ParseConfig, noPassword}, - {"s3", s3.ParseConfig, noPassword}, - {"gs", gs.ParseConfig, noPassword}, - {"azure", azure.ParseConfig, noPassword}, - {"swift", swift.ParseConfig, noPassword}, - {"rest", rest.ParseConfig, rest.StripPassword}, - {"rclone", rclone.ParseConfig, noPassword}, + {"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) diff --git a/internal/backend/rclone/config.go b/internal/backend/rclone/config.go index f8dc0d84d..0f1eef332 100644 --- a/internal/backend/rclone/config.go +++ b/internal/backend/rclone/config.go @@ -34,9 +34,9 @@ func NewConfig() Config { } // ParseConfig parses the string s and extracts the remote server URL. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "rclone:") { - return nil, errors.New("invalid rclone backend specification") + return Config{}, errors.New("invalid rclone backend specification") } s = s[7:] diff --git a/internal/backend/rest/config.go b/internal/backend/rest/config.go index 388153fce..b0b2b93f8 100644 --- a/internal/backend/rest/config.go +++ b/internal/backend/rest/config.go @@ -26,16 +26,16 @@ func NewConfig() Config { } // ParseConfig parses the string s and extracts the REST server URL. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "rest:") { - return nil, errors.New("invalid REST backend specification") + return Config{}, errors.New("invalid REST backend specification") } s = prepareURL(s) u, err := url.Parse(s) if err != nil { - return nil, errors.WithStack(err) + return Config{}, errors.WithStack(err) } cfg := NewConfig() diff --git a/internal/backend/rest/rest_test.go b/internal/backend/rest/rest_test.go index 9d7073bc5..edbb3141c 100644 --- a/internal/backend/rest/rest_test.go +++ b/internal/backend/rest/rest_test.go @@ -130,12 +130,10 @@ func TestBackendRESTExternalServer(t *testing.T) { t.Fatal(err) } - c := cfg.(rest.Config) - ctx, cancel := context.WithCancel(context.Background()) defer cancel() - newTestSuite(ctx, t, c.URL, true).RunTests(t) + newTestSuite(ctx, t, cfg.URL, true).RunTests(t) } func BenchmarkBackendREST(t *testing.B) { diff --git a/internal/backend/s3/config.go b/internal/backend/s3/config.go index 681065b60..76e9f526b 100644 --- a/internal/backend/s3/config.go +++ b/internal/backend/s3/config.go @@ -45,7 +45,7 @@ func init() { // supported configuration formats are s3://host/bucketname/prefix and // s3:host/bucketname/prefix. The host can also be a valid s3 region // name. If no prefix is given the prefix "restic" will be used. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { switch { case strings.HasPrefix(s, "s3:http"): // assume that a URL has been specified, parse it and @@ -53,11 +53,11 @@ func ParseConfig(s string) (interface{}, error) { // bucket name and prefix url, err := url.Parse(s[3:]) if err != nil { - return nil, errors.WithStack(err) + return Config{}, errors.WithStack(err) } if url.Path == "" { - return nil, errors.New("s3: bucket name not found") + return Config{}, errors.New("s3: bucket name not found") } bucket, path, _ := strings.Cut(url.Path[1:], "/") @@ -67,7 +67,7 @@ func ParseConfig(s string) (interface{}, error) { case strings.HasPrefix(s, "s3:"): s = s[3:] default: - return nil, errors.New("s3: invalid format") + return Config{}, errors.New("s3: invalid format") } // use the first entry of the path as the endpoint and the // remainder as bucket name and prefix @@ -76,9 +76,9 @@ func ParseConfig(s string) (interface{}, error) { return createConfig(endpoint, bucket, prefix, false) } -func createConfig(endpoint, bucket, prefix string, useHTTP bool) (interface{}, error) { +func createConfig(endpoint, bucket, prefix string, useHTTP bool) (Config, error) { if endpoint == "" { - return nil, errors.New("s3: invalid format, host/region or bucket name not found") + return Config{}, errors.New("s3: invalid format, host/region or bucket name not found") } if prefix != "" { diff --git a/internal/backend/s3/s3_test.go b/internal/backend/s3/s3_test.go index c024251a9..6df81232a 100644 --- a/internal/backend/s3/s3_test.go +++ b/internal/backend/s3/s3_test.go @@ -229,12 +229,11 @@ func newS3TestSuite(t testing.TB) *test.Suite { // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - s3cfg, err := s3.ParseConfig(os.Getenv("RESTIC_TEST_S3_REPOSITORY")) + cfg, err := s3.ParseConfig(os.Getenv("RESTIC_TEST_S3_REPOSITORY")) if err != nil { return nil, err } - cfg := s3cfg.(s3.Config) cfg.KeyID = os.Getenv("RESTIC_TEST_S3_KEY") cfg.Secret = options.NewSecretString(os.Getenv("RESTIC_TEST_S3_SECRET")) cfg.Prefix = fmt.Sprintf("test-%d", time.Now().UnixNano()) diff --git a/internal/backend/sftp/config.go b/internal/backend/sftp/config.go index 1a7309de3..b07aa0c20 100644 --- a/internal/backend/sftp/config.go +++ b/internal/backend/sftp/config.go @@ -35,14 +35,14 @@ func init() { // and sftp:user@host:directory. The directory will be path Cleaned and can // be an absolute path if it starts with a '/' (e.g. // sftp://user@host//absolute and sftp:user@host:/absolute). -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { var user, host, port, dir string switch { case strings.HasPrefix(s, "sftp://"): // parse the "sftp://user@host/path" url format url, err := url.Parse(s) if err != nil { - return nil, errors.WithStack(err) + return Config{}, errors.WithStack(err) } if url.User != nil { user = url.User.Username() @@ -51,7 +51,7 @@ func ParseConfig(s string) (interface{}, error) { port = url.Port() dir = url.Path if dir == "" { - return nil, errors.Errorf("invalid backend %q, no directory specified", s) + return Config{}, errors.Errorf("invalid backend %q, no directory specified", s) } dir = dir[1:] @@ -63,7 +63,7 @@ func ParseConfig(s string) (interface{}, error) { var colon bool host, dir, colon = strings.Cut(s, ":") if !colon { - return nil, errors.New("sftp: invalid format, hostname or path not found") + return Config{}, errors.New("sftp: invalid format, hostname or path not found") } // split user and host at the "@" data := strings.SplitN(host, "@", 3) @@ -75,12 +75,12 @@ func ParseConfig(s string) (interface{}, error) { host = data[1] } default: - return nil, errors.New(`invalid format, does not start with "sftp:"`) + return Config{}, errors.New(`invalid format, does not start with "sftp:"`) } p := path.Clean(dir) if strings.HasPrefix(p, "~") { - return nil, errors.New("sftp path starts with the tilde (~) character, that fails for most sftp servers.\nUse a relative directory, most servers interpret this as relative to the user's home directory") + return Config{}, errors.New("sftp path starts with the tilde (~) character, that fails for most sftp servers.\nUse a relative directory, most servers interpret this as relative to the user's home directory") } cfg := NewConfig() diff --git a/internal/backend/swift/config.go b/internal/backend/swift/config.go index ced256752..2b4daeff0 100644 --- a/internal/backend/swift/config.go +++ b/internal/backend/swift/config.go @@ -50,19 +50,19 @@ func NewConfig() Config { } // ParseConfig parses the string s and extract swift's container name and prefix. -func ParseConfig(s string) (interface{}, error) { +func ParseConfig(s string) (Config, error) { if !strings.HasPrefix(s, "swift:") { - return nil, errors.New("invalid URL, expected: swift:container-name:/[prefix]") + return Config{}, errors.New("invalid URL, expected: swift:container-name:/[prefix]") } s = strings.TrimPrefix(s, "swift:") container, prefix, _ := strings.Cut(s, ":") if prefix == "" { - return nil, errors.Errorf("prefix is empty") + return Config{}, errors.Errorf("prefix is empty") } if prefix[0] != '/' { - return nil, errors.Errorf("prefix does not start with slash (/)") + return Config{}, errors.Errorf("prefix does not start with slash (/)") } prefix = prefix[1:] diff --git a/internal/backend/swift/config_test.go b/internal/backend/swift/config_test.go index 35f091a9b..57002a924 100644 --- a/internal/backend/swift/config_test.go +++ b/internal/backend/swift/config_test.go @@ -33,16 +33,11 @@ var configTests = []struct { func TestParseConfig(t *testing.T) { for _, test := range configTests { t.Run("", func(t *testing.T) { - v, err := ParseConfig(test.s) + cfg, err := ParseConfig(test.s) if err != nil { t.Fatalf("parsing %q failed: %v", test.s, err) } - cfg, ok := v.(Config) - if !ok { - t.Fatalf("wrong type returned, want Config, got %T", cfg) - } - if cfg != test.cfg { t.Fatalf("wrong output for %q, want:\n %#v\ngot:\n %#v", test.s, test.cfg, cfg) diff --git a/internal/backend/swift/swift_test.go b/internal/backend/swift/swift_test.go index 0912e4f7e..18c8db93c 100644 --- a/internal/backend/swift/swift_test.go +++ b/internal/backend/swift/swift_test.go @@ -43,12 +43,11 @@ func newSwiftTestSuite(t testing.TB) *test.Suite { // NewConfig returns a config for a new temporary backend that will be used in tests. NewConfig: func() (interface{}, error) { - swiftcfg, err := swift.ParseConfig(os.Getenv("RESTIC_TEST_SWIFT")) + cfg, err := swift.ParseConfig(os.Getenv("RESTIC_TEST_SWIFT")) if err != nil { return nil, err } - cfg := swiftcfg.(swift.Config) if err = swift.ApplyEnvironment("RESTIC_TEST_", &cfg); err != nil { return nil, err }