mirror of
https://github.com/octoleo/restic.git
synced 2024-11-02 11:46:36 +00:00
sftp: Integrate command
This commit is contained in:
parent
d3b6f75848
commit
c26dd6b76f
@ -11,6 +11,7 @@ import (
|
||||
// Config collects all information required to connect to an sftp server.
|
||||
type Config struct {
|
||||
User, Host, Dir string
|
||||
Command string `option:"command"`
|
||||
}
|
||||
|
||||
// ParseConfig parses the string s and extracts the sftp config. The
|
||||
|
@ -37,6 +37,7 @@ type SFTP struct {
|
||||
var _ restic.Backend = &SFTP{}
|
||||
|
||||
func startClient(program string, args ...string) (*SFTP, error) {
|
||||
debug.Log("start client %v %v", program, args)
|
||||
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||
// command. This assumes that passwordless login is correctly configured.
|
||||
cmd := exec.Command(program, args...)
|
||||
@ -114,11 +115,11 @@ func (r *SFTP) clientError() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open opens an sftp backend. When the command is started via
|
||||
// open opens an sftp backend. When the command is started via
|
||||
// exec.Command, it is expected to speak sftp on stdin/stdout. The backend
|
||||
// is expected at the given path. `dir` must be delimited by forward slashes
|
||||
// ("/"), which is required by sftp.
|
||||
func Open(dir string, program string, args ...string) (*SFTP, error) {
|
||||
func open(dir string, program string, args ...string) (*SFTP, error) {
|
||||
debug.Log("open backend with program %v, %v at %v", program, args, dir)
|
||||
sftp, err := startClient(program, args...)
|
||||
if err != nil {
|
||||
@ -155,15 +156,25 @@ func buildSSHCommand(cfg Config) []string {
|
||||
// OpenWithConfig opens an sftp backend as described by the config by running
|
||||
// "ssh" with the appropriate arguments.
|
||||
func OpenWithConfig(cfg Config) (*SFTP, error) {
|
||||
debug.Log("open with config %v", cfg)
|
||||
return Open(cfg.Dir, "ssh", buildSSHCommand(cfg)...)
|
||||
debug.Log("config %#v", cfg)
|
||||
|
||||
if cfg.Command == "" {
|
||||
return open(cfg.Dir, "ssh", buildSSHCommand(cfg)...)
|
||||
}
|
||||
|
||||
cmd, args, err := SplitShellArgs(cfg.Command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return open(cfg.Dir, cmd, args...)
|
||||
}
|
||||
|
||||
// Create creates all the necessary files and directories for a new sftp
|
||||
// create creates all the necessary files and directories for a new sftp
|
||||
// backend at dir. Afterwards a new config blob should be created. `dir` must
|
||||
// be delimited by forward slashes ("/"), which is required by sftp.
|
||||
func Create(dir string, program string, args ...string) (*SFTP, error) {
|
||||
debug.Log("%v %v", program, args)
|
||||
func create(dir string, program string, args ...string) (*SFTP, error) {
|
||||
debug.Log("create() %v %v", program, args)
|
||||
sftp, err := startClient(program, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -178,6 +189,7 @@ func Create(dir string, program string, args ...string) (*SFTP, error) {
|
||||
// create paths for data, refs and temp blobs
|
||||
for _, d := range paths(dir) {
|
||||
err = sftp.mkdirAll(d, backend.Modes.Dir)
|
||||
debug.Log("mkdirAll %v -> %v", d, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -189,14 +201,23 @@ func Create(dir string, program string, args ...string) (*SFTP, error) {
|
||||
}
|
||||
|
||||
// open backend
|
||||
return Open(dir, program, args...)
|
||||
return open(dir, program, args...)
|
||||
}
|
||||
|
||||
// CreateWithConfig creates an sftp backend as described by the config by running
|
||||
// "ssh" with the appropriate arguments.
|
||||
func CreateWithConfig(cfg Config) (*SFTP, error) {
|
||||
debug.Log("config %v", cfg)
|
||||
return Create(cfg.Dir, "ssh", buildSSHCommand(cfg)...)
|
||||
debug.Log("config %#v", cfg)
|
||||
if cfg.Command == "" {
|
||||
return create(cfg.Dir, "ssh", buildSSHCommand(cfg)...)
|
||||
}
|
||||
|
||||
cmd, args, err := SplitShellArgs(cfg.Command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return create(cfg.Dir, cmd, args...)
|
||||
}
|
||||
|
||||
// Location returns this backend's location (the directory name).
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sftp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -50,7 +51,9 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"-e"}
|
||||
cfg := sftp.Config{
|
||||
Command: fmt.Sprintf("%q -e", sftpserver),
|
||||
}
|
||||
|
||||
test.CreateFn = func() (restic.Backend, error) {
|
||||
err := createTempdir()
|
||||
@ -58,7 +61,9 @@ func init() {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sftp.Create(tempBackendDir, sftpserver, args...)
|
||||
cfg.Dir = tempBackendDir
|
||||
|
||||
return sftp.CreateWithConfig(cfg)
|
||||
}
|
||||
|
||||
test.OpenFn = func() (restic.Backend, error) {
|
||||
@ -66,7 +71,10 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sftp.Open(tempBackendDir, sftpserver, args...)
|
||||
|
||||
cfg.Dir = tempBackendDir
|
||||
|
||||
return sftp.OpenWithConfig(cfg)
|
||||
}
|
||||
|
||||
test.CleanupFn = func() error {
|
||||
|
@ -43,7 +43,7 @@ func (s *shellSplitter) isSplitChar(c rune) bool {
|
||||
}
|
||||
|
||||
// SplitShellArgs returns the list of arguments from a shell command string.
|
||||
func SplitShellArgs(data string) (list []string, err error) {
|
||||
func SplitShellArgs(data string) (cmd string, args []string, err error) {
|
||||
s := &shellSplitter{}
|
||||
|
||||
// derived from strings.SplitFunc
|
||||
@ -51,7 +51,7 @@ func SplitShellArgs(data string) (list []string, err error) {
|
||||
for i, rune := range data {
|
||||
if s.isSplitChar(rune) {
|
||||
if fieldStart >= 0 {
|
||||
list = append(list, data[fieldStart:i])
|
||||
args = append(args, data[fieldStart:i])
|
||||
fieldStart = -1
|
||||
}
|
||||
} else if fieldStart == -1 {
|
||||
@ -59,15 +59,21 @@ func SplitShellArgs(data string) (list []string, err error) {
|
||||
}
|
||||
}
|
||||
if fieldStart >= 0 { // Last field might end at EOF.
|
||||
list = append(list, data[fieldStart:])
|
||||
args = append(args, data[fieldStart:])
|
||||
}
|
||||
|
||||
switch s.quote {
|
||||
case '\'':
|
||||
return nil, errors.New("single-quoted string not terminated")
|
||||
return "", nil, errors.New("single-quoted string not terminated")
|
||||
case '"':
|
||||
return nil, errors.New("double-quoted string not terminated")
|
||||
return "", nil, errors.New("double-quoted string not terminated")
|
||||
}
|
||||
|
||||
return list, nil
|
||||
if len(args) == 0 {
|
||||
return "", nil, errors.New("command string is empty")
|
||||
}
|
||||
|
||||
cmd, args = args[0], args[1:]
|
||||
|
||||
return cmd, args, nil
|
||||
}
|
||||
|
@ -8,56 +8,62 @@ import (
|
||||
func TestShellSplitter(t *testing.T) {
|
||||
var tests = []struct {
|
||||
data string
|
||||
want []string
|
||||
cmd string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
`foo`,
|
||||
[]string{"foo"},
|
||||
"foo", []string{},
|
||||
},
|
||||
{
|
||||
`'foo'`,
|
||||
[]string{"foo"},
|
||||
"foo", []string{},
|
||||
},
|
||||
{
|
||||
`foo bar baz`,
|
||||
[]string{"foo", "bar", "baz"},
|
||||
"foo", []string{"bar", "baz"},
|
||||
},
|
||||
{
|
||||
`foo 'bar' baz`,
|
||||
[]string{"foo", "bar", "baz"},
|
||||
"foo", []string{"bar", "baz"},
|
||||
},
|
||||
{
|
||||
`foo 'bar box' baz`,
|
||||
[]string{"foo", "bar box", "baz"},
|
||||
`'bar box' baz`,
|
||||
"bar box", []string{"baz"},
|
||||
},
|
||||
{
|
||||
`"bar 'box'" baz`,
|
||||
[]string{"bar 'box'", "baz"},
|
||||
"bar 'box'", []string{"baz"},
|
||||
},
|
||||
{
|
||||
`'bar "box"' baz`,
|
||||
[]string{`bar "box"`, "baz"},
|
||||
`bar "box"`, []string{"baz"},
|
||||
},
|
||||
{
|
||||
`\"bar box baz`,
|
||||
[]string{`"bar`, "box", "baz"},
|
||||
`"bar`, []string{"box", "baz"},
|
||||
},
|
||||
{
|
||||
`"bar/foo/x" "box baz"`,
|
||||
[]string{"bar/foo/x", "box baz"},
|
||||
"bar/foo/x", []string{"box baz"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
res, err := SplitShellArgs(test.data)
|
||||
cmd, args, err := SplitShellArgs(test.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(res, test.want) {
|
||||
t.Fatalf("wrong data returned, want:\n %#v\ngot:\n %#v",
|
||||
test.want, res)
|
||||
if cmd != test.cmd {
|
||||
t.Fatalf("wrong cmd returned, want:\n %#v\ngot:\n %#v",
|
||||
test.cmd, cmd)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(args, test.args) {
|
||||
t.Fatalf("wrong args returned, want:\n %#v\ngot:\n %#v",
|
||||
test.args, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -88,7 +94,7 @@ func TestShellSplitterInvalid(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
res, err := SplitShellArgs(test.data)
|
||||
cmd, args, err := SplitShellArgs(test.data)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error not found: %v", test.err)
|
||||
}
|
||||
@ -97,8 +103,12 @@ func TestShellSplitterInvalid(t *testing.T) {
|
||||
t.Fatalf("expected error not found, want:\n %q\ngot:\n %q", test.err, err.Error())
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
t.Fatalf("splitter returned fields from invalid data: %v", res)
|
||||
if cmd != "" {
|
||||
t.Fatalf("splitter returned cmd from invalid data: %v", cmd)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
t.Fatalf("splitter returned fields from invalid data: %v", args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user