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

Add a Reva SDK #1280

Merged
merged 12 commits into from
Oct 27, 2020
5 changes: 5 additions & 0 deletions changelog/unreleased/reva-sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Add a Reva SDK

A Reva SDK has been added to make working with a remote Reva instance much easier by offering a high-level API that hides all the underlying details of the CS3API.

https://github.com/cs3org/reva/pull/1280
70 changes: 70 additions & 0 deletions docs/content/en/docs/tutorials/sdk-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: "Reva SDK"
linkTitle: "Reva SDK"
weight: 5
description: >
Use the Reva SDK to easily access and work with a remote Reva instance.
---
The Reva SDK (located under `/pkg/sdk/`) is a simple software development kit to work with Reva through the [CS3API](https://github.com/cs3org/go-cs3apis). It's goal is to make working with Reva as easy as possible by providing a high-level API which hides all the details of the underlying CS3API.

## Design
The main design goal of the SDK is _simplicity_. This means that the code is extremely easy to use instead of being particularly fancy or feature-rich.

There are two central kinds of objects you'll be using: a _session_ and various _actions_. The session represents the connection to the Reva server through its gRPC gateway client; the actions are then used to perform operations like up- and downloading or enumerating all files located at a specific remote path.

## Using the SDK
### 1. Session creation
The first step when using the SDK is to create a session and establish a connection to Reva (which actually results in a token-creation and not a permanent connection, but this should not bother you in any way):

```
session := sdk.MustNewSession() // Panics if this fails (should usually not happen)
session.Initiate("reva.host.com:443", false)
session.BasicLogin("my-login", "my-pass")
```

Note that error checking is omitted here for brevity, but nearly all methods in the SDK return an error which should be checked upon.

If the session has been created successfully - which can also be verified by calling `session.IsValid()` -, you can use one of the various actions to perform the actual operations.

### 2. Performing operations
An overview of all currently supported operations can be found below; here is an example of how to upload a file using the `UploadAction`:

```
act := action.MustNewUploadAction(session)
info, err := act.UploadBytes([]byte("HELLO WORLD!\n"), "/home/mytest/hello.txt")
// Check error...
fmt.Printf("Uploaded file: %s [%db] -- %s", info.Path, info.Size, info.Type)
```

As you can see, you first need to create an instance of the desired action by either calling its corresponding `New...Action` or `MustNew...Action` function; these creators always require you to pass the previously created session object. The actual operations are then performed by using the appropriate methods offered by the action object.

A more extensive example of how to use the SDK can also be found in `/examples/sdk/sdk.go`.

## Supported operations
An action object often bundles various operations; the `FileOperationsAction`, for example, allows you to create directories, check if a file exists or remove an entire path. Below is an alphabetically sorted table of the available actions and their supported operations:

| Action | Operation | Description |
| --- | --- | --- |
| `DownloadAction` | `Download` | Downloads a specific resource identified by a `ResourceInfo` object |
| | `DownloadFile` | Downloads a specific file |
| `EnumFilesAction`<sup>1</sup> | `ListAll` | Lists all files and directories in a given path |
| | `ListAllWithFilter` | Lists all files and directories in a given path that fulfill a given predicate |
| | `ListDirs` | Lists all directories in a given path |
| | `ListFiles` | Lists all files in a given path |
| `FileOperationsAction` | `DirExists` | Checks whether the specified directory exists |
| | `FileExists` | Checks whether the specified file exists |
| | `MakePath` | Creates the entire directory tree specified by a path |
| | `Move` | Moves a specified resource to a new target |
| | `MoveTo` | Moves a specified resource to a new directory, creating it if necessary |
| | `Remove` | Deletes the specified resource |
| | `ResourceExists` | Checks whether the specified resource exists |
| | `Stat` | Queries information of a resource |
| `UploadAction`<sup>2</sup> | `Upload` | Uploads data from a reader to a target file |
| | `UploadBytes` | Uploads byte data to a target file |
| | `UploadFile` | Uploads a file to a target file |
| | `UploadFileTo` | Uploads a file to a target directory |

* <sup>1</sup> All enumeration operations support recursion.
* <sup>2</sup> The `UploadAction` creates the target directory automatically if necessary.

_Note that not all features of the CS3API are currently implemented._
139 changes: 139 additions & 0 deletions examples/sdk/sdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package main

import (
"fmt"
"log"

"github.com/cs3org/reva/pkg/sdk"
"github.com/cs3org/reva/pkg/sdk/action"
)

func runActions(session *sdk.Session) {
// Try creating a directory
{
act := action.MustNewFileOperationsAction(session)
if err := act.MakePath("/home/subdir/subsub"); err == nil {
log.Println("Created path /home/subdir/subsub")
} else {
log.Println("Could not create path /home/subdir/subsub")
}
fmt.Println()
}

// Try deleting a directory
{
act := action.MustNewFileOperationsAction(session)
if err := act.Remove("/home/subdir/subsub"); err == nil {
log.Println("Removed path /home/subdir/subsub")
} else {
log.Println("Could not remove path /home/subdir/subsub")
}
fmt.Println()
}

// Try uploading
{
act := action.MustNewUploadAction(session)
act.EnableTUS = true
if info, err := act.UploadBytes([]byte("HELLO WORLD!\n"), "/home/subdir/tests.txt"); err == nil {
log.Printf("Uploaded file: %s [%db] -- %s", info.Path, info.Size, info.Type)
} else {
log.Printf("Can't upload file: %v", err)
}
fmt.Println()
}

// Try moving
{
act := action.MustNewFileOperationsAction(session)
if err := act.MoveTo("/home/subdir/tests.txt", "/home/sub2"); err == nil {
log.Println("Moved tests.txt around")
} else {
log.Println("Could not move tests.txt around")
}
fmt.Println()
}

// Try listing and downloading
{
act := action.MustNewEnumFilesAction(session)
if files, err := act.ListFiles("/home", true); err == nil {
for _, info := range files {
log.Printf("%s [%db] -- %s", info.Path, info.Size, info.Type)

// Download the file
actDl := action.MustNewDownloadAction(session)
if data, err := actDl.Download(info); err == nil {
log.Printf("Downloaded %d bytes for '%v'", len(data), info.Path)
} else {
log.Printf("Unable to download data for '%v': %v", info.Path, err)
}

log.Println("---")
}
} else {
log.Printf("Can't list files: %v", err)
}
fmt.Println()
}

// Try accessing some files and directories
{
act := action.MustNewFileOperationsAction(session)
if act.FileExists("/home/blargh.txt") {
log.Println("File '/home/blargh.txt' found")
} else {
log.Println("File '/home/blargh.txt' NOT found")
}

if act.DirExists("/home") {
log.Println("Directory '/home' found")
} else {
log.Println("Directory '/home' NOT found")
}
fmt.Println()
}
}

func main() {
session := sdk.MustNewSession()
if err := session.Initiate("sciencemesh-test.uni-muenster.de:9600", false); err != nil {
log.Fatalf("Can't initiate Reva session: %v", err)
}

if methods, err := session.GetLoginMethods(); err == nil {
fmt.Println("Supported login methods:")
for _, m := range methods {
fmt.Printf("* %v\n", m)
}
fmt.Println()
} else {
log.Fatalf("Can't list login methods: %v", err)
}

if err := session.BasicLogin("daniel", "danielpass"); err == nil {
log.Printf("Successfully logged into Reva (token=%v)", session.Token())
fmt.Println()
runActions(session)
} else {
log.Fatalf("Can't log in to Reva: %v", err)
}
}
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8=
github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4=
github.com/cs3org/go-cs3apis v0.0.0-20200929101248-821df597ec8d h1:YDnGz3eTIYQDXzJd/zefEsl0qbz/P63e8KWjSjYlb5Q=
github.com/cs3org/go-cs3apis v0.0.0-20200929101248-821df597ec8d/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00 h1:LVl25JaflluOchVvaHWtoCynm5OaM+VNai0IYkcCSe0=
github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
38 changes: 38 additions & 0 deletions pkg/sdk/action/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package action

import (
"fmt"

"github.com/cs3org/reva/pkg/sdk"
)

type action struct {
session *sdk.Session
}

func (act *action) initAction(session *sdk.Session) error {
if !session.IsValid() {
return fmt.Errorf("no valid session provided")
}
act.session = session

return nil
}
114 changes: 114 additions & 0 deletions pkg/sdk/action/action_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package action_test

import (
"fmt"
"testing"

"github.com/cs3org/reva/pkg/sdk/action"
testintl "github.com/cs3org/reva/pkg/sdk/common/testing"
)

func TestActions(t *testing.T) {
tests := []struct {
host string
username string
password string
}{
{"sciencemesh-test.uni-muenster.de:9600", "test", "testpass"},
}

for _, test := range tests {
t.Run(test.host, func(t *testing.T) {
// Prepare the session
if session, err := testintl.CreateTestSession(test.host, test.username, test.password); err == nil {
// Try creating a directory
if act, err := action.NewFileOperationsAction(session); err == nil {
if err := act.MakePath("/home/subdir/subsub"); err != nil {
t.Errorf(testintl.FormatTestError("FileOperationsAction.MakePath", err, "/home/subdir/subsub"))
}
} else {
t.Errorf(testintl.FormatTestError("NewFileOperationsAction", err, session))
}

// Try uploading
if act, err := action.NewUploadAction(session); err == nil {
act.EnableTUS = true
if _, err := act.UploadBytes([]byte("HELLO WORLD!\n"), "/home/subdir/tests.txt"); err != nil {
t.Errorf(testintl.FormatTestError("UploadAction.UploadBytes", err, []byte("HELLO WORLD!\n"), "/home/subdir/tests.txt"))
}
} else {
t.Errorf(testintl.FormatTestError("NewUploadAction", err, session))
}

// Try moving
if act, err := action.NewFileOperationsAction(session); err == nil {
if err := act.MoveTo("/home/subdir/tests.txt", "/home/subdir/subtest"); err != nil {
t.Errorf(testintl.FormatTestError("FileOperationsAction.MoveTo", err, "/home/subdir/tests.txt", "/home/subdir/subtest"))
}
} else {
t.Errorf(testintl.FormatTestError("NewFileOperationsAction", err, session))
}

// Try downloading
if act, err := action.NewDownloadAction(session); err == nil {
if _, err := act.DownloadFile("/home/subdir/subtest/tests.txt"); err != nil {
t.Errorf(testintl.FormatTestError("DownloadAction.DownloadFile", err, "/home/subdir/subtest/tests.txt"))
}
} else {
t.Errorf(testintl.FormatTestError("NewDownloadAction", err, session))
}

// Try listing
if act, err := action.NewEnumFilesAction(session); err == nil {
if _, err := act.ListFiles("/home", true); err != nil {
t.Errorf(testintl.FormatTestError("EnumFilesAction.ListFiles", err, "/home", true))
}
} else {
t.Errorf(testintl.FormatTestError("NewEnumFilesAction", err, session))
}

// Try deleting a directory
if act, err := action.NewFileOperationsAction(session); err == nil {
if err := act.Remove("/home/subdir"); err != nil {
t.Errorf(testintl.FormatTestError("FileOperationsAction.Remove", err, "/home/subdir"))
}
} else {
t.Errorf(testintl.FormatTestError("NewFileOperationsAction", err, session))
}

// Try accessing some files and directories
if act, err := action.NewFileOperationsAction(session); err == nil {
if act.FileExists("/home/blargh.txt") {
t.Errorf(testintl.FormatTestError("FileOperationsAction.FileExists", fmt.Errorf("non-existing file reported as existing"), "/home/blargh.txt"))
}

if !act.DirExists("/home") {
t.Errorf(testintl.FormatTestError("FileOperationsAction.DirExists", fmt.Errorf("/home dir reported as non-existing"), "/home"))
}
} else {
t.Errorf(testintl.FormatTestError("NewFileOperationsAction", err, session))
}
} else {
t.Errorf(testintl.FormatTestError("CreateTestSession", err))
}
})
}
}
Loading