Skip to content
This repository has been archived by the owner on Oct 22, 2021. It is now read-only.

Implement storager #5

Merged
merged 7 commits into from
Jul 12, 2021
Merged
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
3 changes: 3 additions & 0 deletions Makefile.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export STORAGE_GDRIVE_NAME=demo
export STORAGE_GDRIVE_INTEGRATION_TEST=on
export STORAGE_GDRIVE_CREDENTIAL=apikey:apikey
83 changes: 30 additions & 53 deletions generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ module github.com/beyondstorage/go-service-gdrive

go 1.15

require github.com/beyondstorage/go-storage/v4 v4.2.0
require (
github.com/beyondstorage/go-integration-test/v4 v4.1.1
github.com/beyondstorage/go-storage/v4 v4.2.1-0.20210709064026-793dd83d71d1
github.com/google/uuid v1.2.0
google.golang.org/api v0.50.0
)
491 changes: 487 additions & 4 deletions go.sum

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions service.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name = "gdrive"

[namespace.storage]

[namespace.storage.new]
required = ["name","credential"]
optional = ["work_dir"]
[namespace.storage.op.create]
optional = ["object_mode"]

Expand Down
31 changes: 31 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## How run integration tests

### Run tests locally

Copy example files and update corresponding values.

```shell
cp Makefile.env.exmaple Makefile.env
```

Run tests

```shell
make integration_test
```

### Run tests in CI

Set following environment variables:

```shell
export STORAGE_GDRIVE_INTEGRATION_TEST=on
export STORAGE_GDRIVE_NAME=demo
export STORAGE_GDRIVE_CREDENTIAL=apikey:apikey
```

Run tests

```shell
make integration_test
```
15 changes: 15 additions & 0 deletions tests/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tests

import (
"os"
"testing"

tests "github.com/beyondstorage/go-integration-test/v4"
)

func TestStorage(t *testing.T) {
if os.Getenv("STORAGE_GDRIVE_INTEGRATION_TEST") != "on" {
t.Skipf("STORAGE_GDRIVE_INTEGRATION_TEST is not 'on', skipped")
}
tests.TestStorager(t, setupTest(t))
}
33 changes: 33 additions & 0 deletions tests/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package tests

import (
"os"
"testing"

"github.com/beyondstorage/go-service-gdrive"
ps "github.com/beyondstorage/go-storage/v4/pairs"
"github.com/beyondstorage/go-storage/v4/types"

"github.com/google/uuid"
)

func setupTest(t *testing.T) types.Storager {
t.Log("Setup test for gdrive")

store, err := gdrive.NewStorager(
ps.WithName(os.Getenv("STORAGE_GDRIVE_NAME")),
ps.WithCredential(os.Getenv("STORAGE_GDRIVE_CREDENTIAL")),
ps.WithWorkDir("/"+uuid.New().String()),
)
if err != nil {
t.Errorf("new storager: %v", err)
}

t.Cleanup(func() {
err = store.Delete("")
if err != nil {
t.Errorf("cleanup: %v", err)
}
})
return store
}
99 changes: 96 additions & 3 deletions utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
package gdrive

import (
"context"
"fmt"

ps "github.com/beyondstorage/go-storage/v4/pairs"
"github.com/beyondstorage/go-storage/v4/pkg/credential"
"github.com/beyondstorage/go-storage/v4/services"
"github.com/beyondstorage/go-storage/v4/types"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"google.golang.org/api/option"
)

// Storage is the example client.
type Storage struct {
name string
workDir string
service *drive.Service
defaultPairs DefaultStoragePairs
features StorageFeatures

Expand All @@ -14,14 +26,95 @@ type Storage struct {

// String implements Storager.String
func (s *Storage) String() string {
panic("implement me")
return fmt.Sprintf(
"Storager gdrive {Name: %s, WorkDir: %s}",
s.name, s.workDir,
)
}

// NewStorager will create Storager only.
func NewStorager(pairs ...types.Pair) (types.Storager, error) {
panic("implement me")
return newStorager(pairs...)
}

func newStorager(pairs ...types.Pair) (store *Storage, err error) {
defer func() {
if err != nil {
err = services.InitError{Op: "new_storager", Type: Type, Err: formatError(err), Pairs: pairs}
}
}()

opt, err := parsePairStorageNew(pairs)
if err != nil {
return nil, err
}

store = &Storage{
name: opt.Name,
workDir: "/",
}
if opt.HasWorkDir {
store.workDir = opt.WorkDir
}
cred, err := credential.Parse(opt.Credential)
if err != nil {
return nil, err
}

//TODO: To make it easier, we just support authorized it
//via API key, maybe we can support OAuth in the future.
var token string
switch cred.Protocol() {
case credential.ProtocolAPIKey:
token = cred.APIKey()
default:
return nil, services.PairUnsupportedError{Pair: ps.WithCredential(opt.Credential)}
}

ctx := context.Background()
store.service, err = drive.NewService(ctx, option.WithAPIKey(token))
return store, nil
}

func formatError(err error) error {
if _, ok := err.(services.InternalError); ok {
return err
}

e, ok := err.(*googleapi.Error)
if !ok {
return fmt.Errorf("%w: %v", services.ErrUnexpected, err)
}

//According to the docs, errors with the same error code may have
//multiple causes, to determine the specific type of error,
//we should evaluate the reason filed of the returned JSON
//Ref: https://developers.google.com/drive/api/v3/handle-errors
switch e.Errors[0].Reason {
case "authError":
return fmt.Errorf("%w: %v", credential.ErrInvalidValue, err)
case "dailyLimitExceeded", "rateLimitExceeded", "userRateLimitExceeded":
return fmt.Errorf("%w: %v", services.ErrRequestThrottled, err)
case "backendError":
return fmt.Errorf("%w: %v", services.ErrServiceInternal, err)
case "notFound":
return fmt.Errorf("%w: %v", services.ErrObjectNotExist, err)
case "insufficientFilePermissions", "appNotAuthorizedToFile":
return fmt.Errorf("%w: %v", services.ErrPermissionDenied, err)
default:
return fmt.Errorf("%w: %v", services.ErrUnexpected, err)
}
}

func (s *Storage) formatError(op string, err error, path ...string) error {
panic("implement me")
if err == nil {
return nil
}

return services.StorageError{
Op: op,
Err: formatError(err),
Storager: s,
Path: path,
}
}