2022-06-22 22:24:35 +00:00
|
|
|
package disk
|
|
|
|
|
|
|
|
import (
|
2023-12-28 00:13:09 +00:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-06-22 22:24:35 +00:00
|
|
|
"io"
|
2023-12-28 00:13:09 +00:00
|
|
|
"log/slog"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/pkg/sftp"
|
|
|
|
"golang.org/x/crypto/ssh"
|
2022-06-22 22:24:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var _ Disk = (*sftpDisk)(nil)
|
|
|
|
|
|
|
|
type sftpDisk struct {
|
2023-12-28 00:13:09 +00:00
|
|
|
client *sftp.Client
|
|
|
|
path string
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
type sftpEntry struct {
|
|
|
|
os.FileInfo
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (f sftpEntry) IsDir() bool {
|
|
|
|
return f.FileInfo.IsDir()
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (f sftpEntry) Name() string {
|
|
|
|
return f.FileInfo.Name()
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func newSFTP(path string) (Disk, error) {
|
|
|
|
u, err := url.Parse(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to parse sftp url: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
password, ok := u.User.Password()
|
|
|
|
var auth []ssh.AuthMethod
|
|
|
|
if ok {
|
|
|
|
auth = append(auth, ssh.Password(password))
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := ssh.Dial("tcp", u.Host, &ssh.ClientConfig{
|
|
|
|
User: u.User.Username(),
|
|
|
|
Auth: auth,
|
|
|
|
|
|
|
|
// TODO Somehow use systems hosts file
|
|
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to connect to ssh server: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := sftp.NewClient(conn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create sftp client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
slog.Info("logged into sftp")
|
|
|
|
|
|
|
|
return sftpDisk{
|
|
|
|
path: path,
|
|
|
|
client: client,
|
|
|
|
}, nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) Exists(path string) (bool, error) {
|
|
|
|
slog.Debug("checking if file exists", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
s, err := l.client.Stat(clean(path))
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, fmt.Errorf("failed to check if file exists: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return s != nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l sftpDisk) Read(path string) ([]byte, error) {
|
|
|
|
slog.Debug("reading file", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
f, err := l.client.Open(clean(path))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to retrieve path: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
data, err := io.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return data, nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) Write(path string, data []byte) error {
|
|
|
|
slog.Debug("writing to file", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
file, err := l.client.Create(clean(path))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
if _, err = io.Copy(file, bytes.NewReader(data)); err != nil {
|
|
|
|
return fmt.Errorf("failed to write file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) Remove(path string) error {
|
|
|
|
slog.Debug("deleting path", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
if err := l.client.Remove(clean(path)); err != nil {
|
|
|
|
if err := l.client.RemoveAll(clean(path)); err != nil {
|
|
|
|
return fmt.Errorf("failed to delete path: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) MkDir(path string) error {
|
|
|
|
slog.Debug("making directory", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
if err := l.client.MkdirAll(clean(path)); err != nil {
|
|
|
|
return fmt.Errorf("failed to make directory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) ReadDir(path string) ([]Entry, error) {
|
|
|
|
slog.Debug("reading directory", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
dir, err := l.client.ReadDir(clean(path))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to list files in directory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
entries := make([]Entry, len(dir))
|
|
|
|
for i, entry := range dir {
|
|
|
|
entries[i] = sftpEntry{
|
|
|
|
FileInfo: entry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries, nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-28 00:13:09 +00:00
|
|
|
func (l sftpDisk) Open(path string, _ int) (io.WriteCloser, error) {
|
|
|
|
slog.Debug("opening for writing", slog.String("path", clean(path)), slog.String("schema", "sftp"))
|
|
|
|
|
|
|
|
f, err := l.client.Create(clean(path))
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("failed to open file", slog.Any("err", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return f, nil
|
2022-06-22 22:24:35 +00:00
|
|
|
}
|