2672b17f44
* feat: sftp test: add tests for ftp and sftp * chore: ci fixes * chore: potential race fix * fix: simplify existence checks * fix: split path differently for ftp * fix: 🤷 * chore: add debug print * chore: lint * chore: idk dude * chore: ? * chore: more logs * chore: wipe mods before tests * chore: logs * chore: wat * chore: wait? * chore: no errors * chore: gh actions are 💩 * fix: always sync after copy * chore: remove some test logs * chore: remove test progress watcher * refactor: change progress to writer * chore: logs * chore: different logs * chore: whoops * chore: moar logs * chore: even moar logs * chore: what is life * chore: why are we here * chore: we are just bags of water floating through space * chore: are you real? * chore: ? * chore: if you get a single update now I call bs * chore: ok what if we just do one? * chore: ok what if we do two? * chore: this should not work * chore: wait no, this one * chore: fml * chore: remove logs * chore: what if we just wait a little * chore: retry * chore: move error * chore: verbose log * chore: remove explicit sleep * chore: remove debug * fix: linux pathing on windows * fix: clean paths properly * fix: fuck ftp * fix: send update on vanilla * feat: parallel ftp * fix: remove potential credential leak
169 lines
3.7 KiB
Go
169 lines
3.7 KiB
Go
package disk
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/url"
|
|
"os"
|
|
|
|
"github.com/pkg/sftp"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
var _ Disk = (*sftpDisk)(nil)
|
|
|
|
type sftpDisk struct {
|
|
client *sftp.Client
|
|
path string
|
|
}
|
|
|
|
type sftpEntry struct {
|
|
os.FileInfo
|
|
}
|
|
|
|
func (f sftpEntry) IsDir() bool {
|
|
return f.FileInfo.IsDir()
|
|
}
|
|
|
|
func (f sftpEntry) Name() string {
|
|
return f.FileInfo.Name()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|