Skip to content

Commit

Permalink
feat: sftp
Browse files Browse the repository at this point in the history
test: add tests for ftp and sftp
  • Loading branch information
Vilsol committed Dec 21, 2023
1 parent baacde4 commit a942a6b
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 26 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ jobs:
if: ${{ matrix.os == 'windows-latest' }}
run: tree /F

- name: Boot ftp and sftp
run: docker-compose -f docker-compose-test.yml up -d

- name: Download GQL schema
run: "npx graphqurl https://api.ficsit.dev/v2/query --introspect -H 'content-type: application/json' > schema.graphql"

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,5 @@ dist/
/.graphqlconfig
schema.graphql
*.log
.direnv
.direnv
/SatisfactoryDedicatedServer
6 changes: 6 additions & 0 deletions cfg/test_defaults.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cfg

import (
"log/slog"
"os"
"path/filepath"
"runtime"

Expand All @@ -18,4 +20,8 @@ func SetDefaults() {
viper.SetDefault("api-base", "https://api.ficsit.dev")
viper.SetDefault("graphql-api", "/v2/query")
viper.SetDefault("concurrent-downloads", 5)

slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})))
}
24 changes: 19 additions & 5 deletions cli/disk/ftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func newFTP(path string) (Disk, error) {
return nil, fmt.Errorf("failed to parse ftp url: %w", err)
}

c, err := ftp.Dial(u.Host, ftp.DialWithTimeout(time.Second*5))
c, err := ftp.Dial(u.Host, ftp.DialWithTimeout(time.Second*5), ftp.DialWithForceListHidden(true))
if err != nil {
return nil, fmt.Errorf("failed to dial host %s: %w", u.Host, err)
}
Expand All @@ -63,7 +63,11 @@ func (l *ftpDisk) Exists(path string) error {

slog.Debug("checking if file exists", slog.String("path", path), slog.String("schema", "ftp"))
_, err := l.client.FileSize(path)
return fmt.Errorf("failed to check if file exists: %w", err)
if err != nil {
return fmt.Errorf("failed to check if file exists: %w", err)
}

return nil
}

func (l *ftpDisk) Read(path string) ([]byte, error) {
Expand Down Expand Up @@ -103,9 +107,17 @@ func (l *ftpDisk) Remove(path string) error {
l.stepLock.Lock()
defer l.stepLock.Unlock()

slog.Debug("going to root directory", slog.String("schema", "ftp"))
err := l.client.ChangeDir("/")
if err != nil {
return fmt.Errorf("failed to change directory: %w", err)
}

slog.Debug("deleting path", slog.String("path", path), slog.String("schema", "ftp"))
if err := l.client.Delete(path); err != nil {
return fmt.Errorf("failed to delete path: %w", err)
if err := l.client.RemoveDirRecur(path); err != nil {
return fmt.Errorf("failed to delete path: %w", err)
}
}

return nil
Expand All @@ -128,6 +140,8 @@ func (l *ftpDisk) MkDir(path string) error {
return err
}

currentDir, _ := l.client.CurrentDir()

foundDir := false
for _, entry := range dir {
if entry.IsDir() && entry.Name() == s {
Expand All @@ -137,13 +151,13 @@ func (l *ftpDisk) MkDir(path string) error {
}

if !foundDir {
slog.Debug("making directory", slog.String("dir", s), slog.String("schema", "ftp"))
slog.Debug("making directory", slog.String("dir", s), slog.String("cwd", currentDir), slog.String("schema", "ftp"))
if err := l.client.MakeDir(s); err != nil {
return fmt.Errorf("failed to make directory: %w", err)
}
}

slog.Debug("entering directory", slog.String("dir", s), slog.String("schema", "ftp"))
slog.Debug("entering directory", slog.String("dir", s), slog.String("cwd", currentDir), slog.String("schema", "ftp"))
if err := l.client.ChangeDir(s); err != nil {
return fmt.Errorf("failed to enter directory: %w", err)
}
Expand Down
159 changes: 140 additions & 19 deletions cli/disk/sftp.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,172 @@
package disk

import (
"bytes"
"fmt"
"io"
"log/slog"
"net/url"
"os"
"strings"

"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)

var _ Disk = (*sftpDisk)(nil)

type sftpDisk struct {
path string
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) {
return sftpDisk{path: path}, nil
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)
}

return sftpDisk{
path: path,
client: client,
}, nil
}

func (l sftpDisk) Exists(path string) error { //nolint
panic("implement me")
slog.Debug("checking if file exists", slog.String("path", path), slog.String("schema", "sftp"))

_, err := l.client.Stat(path)
if err != nil {
return fmt.Errorf("failed to check if file exists: %w", err)
}

return nil
}

func (l sftpDisk) Read(path string) ([]byte, error) { //nolint
panic("implement me")
func (l sftpDisk) Read(path string) ([]byte, error) {
slog.Debug("reading file", slog.String("path", path), slog.String("schema", "sftp"))

f, err := l.client.Open(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 { //nolint
panic("implement me")
func (l sftpDisk) Write(path string, data []byte) error {
slog.Debug("writing to file", slog.String("path", path), slog.String("schema", "sftp"))

file, err := l.client.Create(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 { //nolint
panic("implement me")
func (l sftpDisk) Remove(path string) error {
slog.Debug("deleting path", slog.String("path", path), slog.String("schema", "sftp"))
if err := l.client.Remove(path); err != nil {
if err := l.client.RemoveAll(path); err != nil {
return fmt.Errorf("failed to delete path: %w", err)
}
}

return nil
}

func (l sftpDisk) MkDir(path string) error { //nolint
panic("implement me")
func (l sftpDisk) MkDir(path string) error {
slog.Debug("making directory", slog.String("path", path), slog.String("schema", "sftp"))

if err := l.client.MkdirAll(path); err != nil {
return fmt.Errorf("failed to make directory: %w", err)
}

return nil
}

func (l sftpDisk) ReadDir(path string) ([]Entry, error) { //nolint
panic("implement me")
func (l sftpDisk) ReadDir(path string) ([]Entry, error) {
dir, err := l.client.ReadDir(path)

slog.Debug("reading directory", slog.String("path", path), slog.String("schema", "sftp"))

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) IsNotExist(err error) bool { //nolint
panic("implement me")
func (l sftpDisk) IsNotExist(err error) bool {
return err != nil && strings.Contains(err.Error(), "file does not exist")
}

func (l sftpDisk) IsExist(err error) bool { //nolint
panic("implement me")
func (l sftpDisk) IsExist(_ error) bool {
return false
}

func (l sftpDisk) Open(path string, flag int) (io.WriteCloser, error) { //nolint
panic("implement me")
func (l sftpDisk) Open(path string, _ int) (io.WriteCloser, error) {
slog.Debug("opening for writing", slog.String("path", path), slog.String("schema", "sftp"))

f, err := l.client.Create(path)
if err != nil {
slog.Error("failed to open file", slog.Any("err", err))
}

return f, nil
}
2 changes: 2 additions & 0 deletions cli/installations.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ func (i *Installation) Install(ctx *GlobalContext, updates chan<- InstallUpdate)
return fmt.Errorf("failed to install mods: %w", err)
}

slog.Info("installation completed", slog.String("path", i.Path))

return nil
}

Expand Down
77 changes: 76 additions & 1 deletion cli/installations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestInstallationsInit(t *testing.T) {
testza.AssertNotNil(t, installations)
}

func TestAddInstallation(t *testing.T) {
func TestAddLocalInstallation(t *testing.T) {
ctx, err := InitCLI(false)
testza.AssertNoError(t, err)

Expand Down Expand Up @@ -50,4 +50,79 @@ func TestAddInstallation(t *testing.T) {
err = installation.Install(ctx, installWatcher())
testza.AssertNoError(t, err)
}

err = ctx.Wipe()
testza.AssertNoError(t, err)
}

func TestAddFTPInstallation(t *testing.T) {
ctx, err := InitCLI(false)
testza.AssertNoError(t, err)

err = ctx.Wipe()
testza.AssertNoError(t, err)

err = ctx.ReInit()
testza.AssertNoError(t, err)

ctx.Provider = MockProvider{}

profileName := "InstallationTest"
profile, err := ctx.Profiles.AddProfile(profileName)
testza.AssertNoError(t, err)
testza.AssertNoError(t, profile.AddMod("AreaActions", "1.6.5"))
testza.AssertNoError(t, profile.AddMod("RefinedPower", "3.2.10"))

serverLocation := os.Getenv("SF_DEDICATED_SERVER")
if serverLocation != "" {
installation, err := ctx.Installations.AddInstallation(ctx, "ftp://user:pass@localhost:2121/server", profileName)
testza.AssertNoError(t, err)
testza.AssertNotNil(t, installation)

err = installation.Install(ctx, installWatcher())
testza.AssertNoError(t, err)

installation.Vanilla = true
err = installation.Install(ctx, installWatcher())
testza.AssertNoError(t, err)
}

err = ctx.Wipe()
testza.AssertNoError(t, err)
}

func TestAddSFTPInstallation(t *testing.T) {
ctx, err := InitCLI(false)
testza.AssertNoError(t, err)

err = ctx.Wipe()
testza.AssertNoError(t, err)

err = ctx.ReInit()
testza.AssertNoError(t, err)

ctx.Provider = MockProvider{}

profileName := "InstallationTest"
profile, err := ctx.Profiles.AddProfile(profileName)
testza.AssertNoError(t, err)
testza.AssertNoError(t, profile.AddMod("AreaActions", "1.6.5"))
testza.AssertNoError(t, profile.AddMod("RefinedPower", "3.2.10"))

serverLocation := os.Getenv("SF_DEDICATED_SERVER")
if serverLocation != "" {
installation, err := ctx.Installations.AddInstallation(ctx, "sftp://user:pass@localhost:2222/home/user/server", profileName)
testza.AssertNoError(t, err)
testza.AssertNotNil(t, installation)

err = installation.Install(ctx, installWatcher())
testza.AssertNoError(t, err)

installation.Vanilla = true
err = installation.Install(ctx, installWatcher())
testza.AssertNoError(t, err)
}

err = ctx.Wipe()
testza.AssertNoError(t, err)
}
Loading

0 comments on commit a942a6b

Please sign in to comment.