mirror of
https://github.com/restic/restic.git
synced 2025-12-03 22:21:47 +00:00
Moves files
This commit is contained in:
107
internal/backend/location/location.go
Normal file
107
internal/backend/location/location.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Package location implements parsing the restic repository location from a string.
|
||||
package location
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"restic/backend/b2"
|
||||
"restic/backend/local"
|
||||
"restic/backend/rest"
|
||||
"restic/backend/s3"
|
||||
"restic/backend/sftp"
|
||||
"restic/backend/swift"
|
||||
"restic/errors"
|
||||
)
|
||||
|
||||
// Location specifies the location of a repository, including the method of
|
||||
// access and (possibly) credentials needed for access.
|
||||
type Location struct {
|
||||
Scheme string
|
||||
Config interface{}
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
scheme string
|
||||
parse func(string) (interface{}, error)
|
||||
}
|
||||
|
||||
// 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},
|
||||
{"local", local.ParseConfig},
|
||||
{"sftp", sftp.ParseConfig},
|
||||
{"s3", s3.ParseConfig},
|
||||
{"swift", swift.ParseConfig},
|
||||
{"rest", rest.ParseConfig},
|
||||
}
|
||||
|
||||
func isPath(s string) bool {
|
||||
if strings.HasPrefix(s, "../") || strings.HasPrefix(s, `..\`) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "/") || strings.HasPrefix(s, `\`) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(s) < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
// check for drive paths
|
||||
drive := s[0]
|
||||
if !(drive >= 'a' && drive <= 'z') && !(drive >= 'A' && drive <= 'Z') {
|
||||
return false
|
||||
}
|
||||
|
||||
if s[1] != ':' {
|
||||
return false
|
||||
}
|
||||
|
||||
if s[2] != '\\' && s[2] != '/' {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Parse extracts repository location information from the string s. If s
|
||||
// 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
|
||||
// as the name of a directory.
|
||||
func Parse(s string) (u Location, err error) {
|
||||
scheme := extractScheme(s)
|
||||
u.Scheme = scheme
|
||||
|
||||
for _, parser := range parsers {
|
||||
if parser.scheme != scheme {
|
||||
continue
|
||||
}
|
||||
|
||||
u.Config, err = parser.parse(s)
|
||||
if err != nil {
|
||||
return Location{}, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// if s is not a path or contains ":", it's ambiguous
|
||||
if !isPath(s) && strings.ContainsRune(s, ':') {
|
||||
return Location{}, errors.New("invalid backend\nIf the repo is in a local directory, you need to add a `local:` prefix")
|
||||
}
|
||||
|
||||
u.Scheme = "local"
|
||||
u.Config, err = local.ParseConfig("local:" + s)
|
||||
if err != nil {
|
||||
return Location{}, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func extractScheme(s string) string {
|
||||
data := strings.SplitN(s, ":", 2)
|
||||
return data[0]
|
||||
}
|
||||
339
internal/backend/location/location_test.go
Normal file
339
internal/backend/location/location_test.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package location
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"restic/backend/b2"
|
||||
"restic/backend/local"
|
||||
"restic/backend/rest"
|
||||
"restic/backend/s3"
|
||||
"restic/backend/sftp"
|
||||
"restic/backend/swift"
|
||||
)
|
||||
|
||||
func parseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
var parseTests = []struct {
|
||||
s string
|
||||
u Location
|
||||
}{
|
||||
{
|
||||
"local:/srv/repo",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "/srv/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"local:dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"local:dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"/dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "/dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"local:../dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "../dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"/dir1/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "/dir1/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"/dir1:foobar/dir2",
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: "/dir1:foobar/dir2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
`\dir1\foobar\dir2`,
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: `\dir1\foobar\dir2`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
`c:\dir1\foobar\dir2`,
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: `c:\dir1\foobar\dir2`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
`C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: `C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
`c:/dir1/foobar/dir2`,
|
||||
Location{Scheme: "local",
|
||||
Config: local.Config{
|
||||
Path: `c:/dir1/foobar/dir2`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sftp:user@host:/srv/repo",
|
||||
Location{Scheme: "sftp",
|
||||
Config: sftp.Config{
|
||||
User: "user",
|
||||
Host: "host",
|
||||
Path: "/srv/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sftp:host:/srv/repo",
|
||||
Location{Scheme: "sftp",
|
||||
Config: sftp.Config{
|
||||
User: "",
|
||||
Host: "host",
|
||||
Path: "/srv/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sftp://user@host/srv/repo",
|
||||
Location{Scheme: "sftp",
|
||||
Config: sftp.Config{
|
||||
User: "user",
|
||||
Host: "host",
|
||||
Path: "srv/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sftp://user@host//srv/repo",
|
||||
Location{Scheme: "sftp",
|
||||
Config: sftp.Config{
|
||||
User: "user",
|
||||
Host: "host",
|
||||
Path: "/srv/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"s3://eu-central-1/bucketname",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "eu-central-1",
|
||||
Bucket: "bucketname",
|
||||
Prefix: "restic",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3://hostname.foo/bucketname",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "hostname.foo",
|
||||
Bucket: "bucketname",
|
||||
Prefix: "restic",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3://hostname.foo/bucketname/prefix/directory",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "hostname.foo",
|
||||
Bucket: "bucketname",
|
||||
Prefix: "prefix/directory",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3:eu-central-1/repo",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "eu-central-1",
|
||||
Bucket: "repo",
|
||||
Prefix: "restic",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3:eu-central-1/repo/prefix/directory",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "eu-central-1",
|
||||
Bucket: "repo",
|
||||
Prefix: "prefix/directory",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3:https://hostname.foo/repo",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "hostname.foo",
|
||||
Bucket: "repo",
|
||||
Prefix: "restic",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3:https://hostname.foo/repo/prefix/directory",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "hostname.foo",
|
||||
Bucket: "repo",
|
||||
Prefix: "prefix/directory",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"s3:http://hostname.foo/repo",
|
||||
Location{Scheme: "s3",
|
||||
Config: s3.Config{
|
||||
Endpoint: "hostname.foo",
|
||||
Bucket: "repo",
|
||||
Prefix: "restic",
|
||||
UseHTTP: true,
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"swift:container17:/",
|
||||
Location{Scheme: "swift",
|
||||
Config: swift.Config{
|
||||
Container: "container17",
|
||||
Prefix: "",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"swift:container17:/prefix97",
|
||||
Location{Scheme: "swift",
|
||||
Config: swift.Config{
|
||||
Container: "container17",
|
||||
Prefix: "prefix97",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"rest:http://hostname.foo:1234/",
|
||||
Location{Scheme: "rest",
|
||||
Config: rest.Config{
|
||||
URL: parseURL("http://hostname.foo:1234/"),
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"b2:bucketname:/prefix", Location{Scheme: "b2",
|
||||
Config: b2.Config{
|
||||
Bucket: "bucketname",
|
||||
Prefix: "prefix",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"b2:bucketname", Location{Scheme: "b2",
|
||||
Config: b2.Config{
|
||||
Bucket: "bucketname",
|
||||
Prefix: "",
|
||||
Connections: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
for i, test := range parseTests {
|
||||
t.Run(test.s, func(t *testing.T) {
|
||||
u, err := Parse(test.s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if test.u.Scheme != u.Scheme {
|
||||
t.Errorf("test %d: scheme does not match, want %q, got %q",
|
||||
i, test.u.Scheme, u.Scheme)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.u.Config, u.Config) {
|
||||
t.Errorf("test %d: cfg map does not match, want:\n %#v\ngot: \n %#v",
|
||||
i, test.u.Config, u.Config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidScheme(t *testing.T) {
|
||||
var invalidSchemes = []string{
|
||||
"foobar:xxx",
|
||||
"foobar:/dir/dir2",
|
||||
}
|
||||
|
||||
for _, s := range invalidSchemes {
|
||||
t.Run(s, func(t *testing.T) {
|
||||
_, err := Parse(s)
|
||||
if err == nil {
|
||||
t.Fatalf("error for invalid location %q not found", s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user