Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example for interacting with remote file servers #1061

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions examples/using-add-filestore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Add FileStore Example

This GoFr example demonstrates a CMD application that can be used to interact with a remote file server using FTP or SFTP protocol

### Setting up an FTP server in local machine
- https://security.appspot.com/vsftpd.html
- https://pypi.org/project/pyftpdlib/

Choose a library listed above and follow their respective documentation to configure an FTP server in your local machine and replace the configs/env file with correct HOST,USER_NAME,PASSWORD,PORT and REMOTE_DIR_PATH details.

### To run the example use the commands below:
To print the current working directory of the configured remote file server
```
go run main.go pwd
```
To get the list of all directories or files in the given path of the configured remote file server

```
go run main.go ls -path=<path>

Eg:- go run main.go ls -path=/
```
Comment on lines +16 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs refactoring, as it seems duplicate.. Also, in below snapshot please see that copy button will copy complete block to clipboard and it won't be useful for user. Ideally, we should only add required command reference, so user can just copy and change filePath.

Screenshot 2024-10-01 at 5 24 51 PM

To grep the list of all files and directories in the given path that is matching with the keyword provided

```
go run main.go grep -keyword=fi -path=<path>

Eg:- go run main.go grep -keyword=fi -path=/
```

To create a file in the current working directory with the provided filename
```
go run main.go createfile -filename=<filename>

Eg:- go run main.go createfile -filename=file.txt
```

To remove the file with the provided filename from the current working directory
```
go run main.go rm -filename=<filename>

Eg:- go run main.go rm -filename=file.txt
```
5 changes: 5 additions & 0 deletions examples/using-add-filestore/configs/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
HOST="localhost"
USER_NAME="anonymous"
PASSWORD="test"
PORT=21
REMOTE_DIR_PATH="/"
Comment on lines +1 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configs ain't necessarily needed to pass within double quotes ("")

130 changes: 130 additions & 0 deletions examples/using-add-filestore/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"fmt"
"strconv"
"strings"

"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/file"
"gofr.dev/pkg/gofr/datasource/file/ftp"
)

type FileServerType int

const (
FTP FileServerType = iota
SFTP
)

// This can be a common function to configure both FTP and SFTP server.
func configureFileServer(app *gofr.App) file.FileSystemProvider {
port, _ := strconv.Atoi(app.Config.Get("PORT"))

return ftp.New(&ftp.Config{
Host: app.Config.Get("HOST"),
User: app.Config.Get("USER_NAME"),
Password: app.Config.Get("PASSWORD"),
Port: port,
RemoteDir: app.Config.Get("REMOTE_DIR_PATH"),
})
}

func printFiles(files []file.FileInfo, err error) {
if err != nil {
fmt.Println(err)
} else {
for _, f := range files {
fmt.Println(f.Name())
}
}
}

func grepFiles(files []file.FileInfo, keyword string, err error) {
if err != nil {
fmt.Println(err)
} else {
for _, f := range files {
if strings.HasPrefix(f.Name(), keyword) {
fmt.Println(f.Name())
}
}
}
}

func registerPwdCommand(app *gofr.App, fs file.FileSystemProvider) {
app.SubCommand("pwd", func(_ *gofr.Context) (interface{}, error) {
workingDirectory, err := fs.Getwd()

return workingDirectory, err
})
}

func registerLsCommand(app *gofr.App, fs file.FileSystemProvider) {
app.SubCommand("ls", func(c *gofr.Context) (interface{}, error) {
path := c.Param("path")
files, err := fs.ReadDir(path)
printFiles(files, err)

return "", err
})
}

func registerGrepCommand(app *gofr.App, fs file.FileSystemProvider) {
app.SubCommand("grep", func(c *gofr.Context) (interface{}, error) {
keyword := c.Param("keyword")
path := c.Param("path")
files, err := fs.ReadDir(path)
grepFiles(files, keyword, err)

return "", err
})
}

func registerCreateFileCommand(app *gofr.App, fs file.FileSystemProvider) {
app.SubCommand("createfile", func(c *gofr.Context) (interface{}, error) {
fileName := c.Param("filename")
fmt.Printf("Creating file :%s", fileName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we use gofr logger instead of fmt.Printf?

_, err := fs.Create(fileName)

if err == nil {
fmt.Printf("Successfully created file:%s", fileName)
}

return "", err
})
}

func registerRmCommand(app *gofr.App, fs file.FileSystemProvider) {
app.SubCommand("rm", func(c *gofr.Context) (interface{}, error) {
fileName := c.Param("filename")
fmt.Printf("Removing file :%s", fileName)
err := fs.Remove(fileName)

if err == nil {
fmt.Printf("Successfully removed file:%s", fileName)
}

return "", err
})
}

func main() {
app := gofr.NewCMD()

fileSystemProvider := configureFileServer(app)

app.AddFileStore(fileSystemProvider)

registerPwdCommand(app, fileSystemProvider)

registerLsCommand(app, fileSystemProvider)

registerGrepCommand(app, fileSystemProvider)

registerCreateFileCommand(app, fileSystemProvider)

registerRmCommand(app, fileSystemProvider)

app.Run()
}
155 changes: 155 additions & 0 deletions examples/using-add-filestore/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package main

import (
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/file"
"gofr.dev/pkg/gofr/testutil"
)

type mockFileInfo struct {
name string
}

func (m mockFileInfo) Name() string { return m.name }
func (mockFileInfo) Size() int64 { return 0 }
func (mockFileInfo) Mode() os.FileMode { return 0 }
func (mockFileInfo) ModTime() time.Time { return time.Now() }
func (mockFileInfo) IsDir() bool { return false }
func (mockFileInfo) Sys() interface{} { return nil }

func TestPwdCommand(t *testing.T) {
os.Args = []string{"command", "pwd"}
logs := testutil.StdoutOutputForFunc(func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

app := gofr.NewCMD()
mock := file.NewMockFileSystemProvider(ctrl)

mock.EXPECT().UseLogger(app.Logger())
mock.EXPECT().UseMetrics(app.Metrics())
mock.EXPECT().Connect()
mock.EXPECT().Getwd().DoAndReturn(func() (string, error) {
return "/", nil
})
app.AddFileStore(mock)
registerPwdCommand(app, mock)
app.Run()
})
assert.Contains(t, logs, "/", "Test failed")
}

func TestLSCommand(t *testing.T) {
path := "/"
os.Args = []string{"command", "ls", fmt.Sprintf("-path=%s", path)}
logs := testutil.StdoutOutputForFunc(func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

app := gofr.NewCMD()
mock := file.NewMockFileSystemProvider(ctrl)

mock.EXPECT().UseLogger(app.Logger())
mock.EXPECT().UseMetrics(app.Metrics())
mock.EXPECT().Connect()
mock.EXPECT().ReadDir(path).DoAndReturn(func(_ string) ([]file.FileInfo, error) {
files := []file.FileInfo{
mockFileInfo{name: "file1.txt"},
mockFileInfo{name: "file2.txt"},
}

return files, nil
})
app.AddFileStore(mock)
registerLsCommand(app, mock)
app.Run()
})
assert.Contains(t, logs, "file1.txt", "Test failed")
assert.Contains(t, logs, "file2.txt", "Test failed")
assert.NotContains(t, logs, "file3.txt", "Test failed")
}

func TestGrepCommand(t *testing.T) {
path := "/"
os.Args = []string{"command", "grep", "-keyword=fi", fmt.Sprintf("-path=%s", path)}
logs := testutil.StdoutOutputForFunc(func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

app := gofr.NewCMD()
mock := file.NewMockFileSystemProvider(ctrl)

mock.EXPECT().UseLogger(app.Logger())
mock.EXPECT().UseMetrics(app.Metrics())
mock.EXPECT().Connect()
mock.EXPECT().ReadDir("/").DoAndReturn(func(_ string) ([]file.FileInfo, error) {
files := []file.FileInfo{
mockFileInfo{name: "file1.txt"},
mockFileInfo{name: "file2.txt"},
}

return files, nil
})
app.AddFileStore(mock)
registerGrepCommand(app, mock)
app.Run()
})
assert.Contains(t, logs, "file1.txt", "Test failed")
assert.Contains(t, logs, "file2.txt", "Test failed")
assert.NotContains(t, logs, "file3.txt", "Test failed")
}

func TestCreateFileCommand(t *testing.T) {
fileName := "file.txt"
os.Args = []string{"command", "createfile", fmt.Sprintf("-filename=%s", fileName)}
logs := testutil.StdoutOutputForFunc(func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

app := gofr.NewCMD()
mock := file.NewMockFileSystemProvider(ctrl)

mock.EXPECT().UseLogger(app.Logger())
mock.EXPECT().UseMetrics(app.Metrics())
mock.EXPECT().Connect()
mock.EXPECT().Create(fileName).DoAndReturn(func(_ string) (file.File, error) {
return &file.MockFile{}, nil
})
app.AddFileStore(mock)
registerCreateFileCommand(app, mock)
app.Run()
})
assert.Contains(t, logs, "Creating file :file.txt", "Test failed")
assert.Contains(t, logs, "Successfully created file:file.txt", "Test failed")
}

func TestRmCommand(t *testing.T) {
fileName := "file.txt"
os.Args = []string{"command", "rm", fmt.Sprintf("-filename=%s", fileName)}
logs := testutil.StdoutOutputForFunc(func() {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

app := gofr.NewCMD()
mock := file.NewMockFileSystemProvider(ctrl)

mock.EXPECT().UseLogger(app.Logger())
mock.EXPECT().UseMetrics(app.Metrics())
mock.EXPECT().Connect()
mock.EXPECT().Remove("file.txt").DoAndReturn(func(_ string) error {
return nil
})
app.AddFileStore(mock)
registerRmCommand(app, mock)
app.Run()
})
assert.Contains(t, logs, "Removing file :file.txt", "Test failed")
assert.Contains(t, logs, "Successfully removed file:file.txt", "Test failed")
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/jlaffaye/ftp v0.2.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand All @@ -88,6 +91,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
gofr.dev/pkg/gofr/datasource/file/ftp v0.0.0-20240823110359-085d180867dd // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,15 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down Expand Up @@ -252,6 +259,8 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
gofr.dev/pkg/gofr/datasource/file/ftp v0.0.0-20240823110359-085d180867dd h1:YTiTFoRmx/TK+uzmSVAP7ZPodeZaDauLmhD2ov+lgFE=
gofr.dev/pkg/gofr/datasource/file/ftp v0.0.0-20240823110359-085d180867dd/go.mod h1:Pau/kQnk86g7U3Nwuc5/g05PaPFxfn4VFXmOa8Z4xrM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
Loading