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

Manage Spaces Attributes #2588

Merged
merged 8 commits into from
Oct 8, 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
77 changes: 77 additions & 0 deletions docs/extensions/storage/spaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: "Spaces"
date: 2020-04-27T18:46:00+01:00
weight: 38
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/docs/extensions/storage
geekdocFilePath: spaces.md
---

{{< toc >}}

## Editing a Storage Space

The OData specification allows for a mirage of ways of addressing an entity. We will support addressing a Drive entity by its unique identifier, which is the one the graph-api returns when listing spaces, and its format is:

```json
{
"id": "1284d238-aa92-42ce-bdc4-0b0000009157!b6e2c9cc-9dbe-42f0-b522-4f2d3e175e9c"
}
```

This is an extract of an element of the list spaces response. An entire object has the following shape:

```json
{
"driveType": "project",
"id": "1284d238-aa92-42ce-bdc4-0b0000009157!b6e2c9cc-9dbe-42f0-b522-4f2d3e175e9c",
"lastModifiedDateTime": "2021-10-07T11:06:43.245418+02:00",
"name": "marketing",
"owner": {
"user": {
"id": "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
}
},
"quota": {
"total": 65536
},
"root": {
"id": "1284d238-aa92-42ce-bdc4-0b0000009157!b6e2c9cc-9dbe-42f0-b522-4f2d3e175e9c",
"webDavUrl": "https://localhost:9200/dav/spaces/1284d238-aa92-42ce-bdc4-0b0000009157!b6e2c9cc-9dbe-42f0-b522-4f2d3e175e9c"
}
}
```

### Updating a space property

Having introduced the above, one can refer to a Drive with the following URL format:

```console
'https://localhost:9200/graph/v1.0/Drive(1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570")
```

Udating an entity attribute:

```console
curl -X PATCH 'https://localhost:9200/graph/v1.0/Drive("1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570)' -d '{"name":"42"}' -v
```

The previous URL resource path segment (`Drive(1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570)`) is parsed and handed over to the storage registry in order to apply the patch changes in the body, in this case update the space name attribute to `42`. Since space names are not unique we only support addressing them by their unique identifiers, any other query would render too ambiguous and explode in complexity.


### Updating a space description

Since every space is the root of a webdav directory, following some conventions we can make use of this to set a default storage description and image. In order to do so, every space is created with a hidden `.space` folder at its root, which can be used to store such data.

```curl
curl -k -X PUT https://localhost:9200/dav/spaces/1284d238-aa92-42ce-bdc4-0b0000009157\!07c26b3a-9944-4f2b-ab33-b0b326fc7570/.space/description.md -d "Add a description to your spaces" -u admin:admin
```

Verify the description was updated:

```curl
❯ curl -k https://localhost:9200/dav/spaces/1284d238-aa92-42ce-bdc4-0b0000009157\!07c26b3a-9944-4f2b-ab33-b0b326fc7570/.space/description.md -u admin:admin
Add a description to your spaces
```

This feature makes use of the internal storage layout and is completely abstracted from the end user.
65 changes: 65 additions & 0 deletions graph/pkg/service/v0/drives.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"net/http"
"net/url"
"path"
"strings"
"time"

"github.com/CiscoM31/godata"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
Expand Down Expand Up @@ -209,6 +211,69 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
}
}

func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
// wildcards however addressed here is not yet supported. We want to address drives by their unique
// identifiers. Any open queries need to be implemented. Same applies for sub-entities.
// For further reading: http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_AddressingaSubsetofaCollection

// strip "/graph/v1.0/" out and parse the rest. This is how godata input is expected.
//https://github.com/CiscoM31/godata/blob/d70e191d2908191623be84401fecc40d6af4afde/url_parser_test.go#L10
sanitized := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")

req, err := godata.ParseRequest(sanitized, r.URL.Query(), true)
if err != nil {
panic(err)
}

if req.FirstSegment.Identifier.Get() == "" {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Errorf("identifier cannot be empty").Error())
return
}

drive := msgraph.Drive{}
if err = json.NewDecoder(r.Body).Decode(&drive); err != nil {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Errorf("invalid request body: %v", r.Body).Error())
return
}

identifierParts := strings.Split(req.FirstSegment.Identifier.Get(), "!")
if len(identifierParts) != 2 {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Errorf("invalid resource id: %v", req.FirstSegment.Identifier.Get()).Error())
w.WriteHeader(http.StatusInternalServerError)
}

storageID, opaqueID := identifierParts[0], identifierParts[1]

client, err := g.GetClient()
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}

updateSpaceRequest := &provider.UpdateStorageSpaceRequest{
// Prepare the object to apply the diff from. The properties on StorageSpace will overwrite
// the original storage space.
StorageSpace: &provider.StorageSpace{
Root: &provider.ResourceId{
StorageId: storageID,
OpaqueId: opaqueID,
},
Name: *drive.Name,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you also add quota? Setting quota on create already works.

Copy link
Member Author

Choose a reason for hiding this comment

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

not part of the acceptance criteria

},
}

resp, err := client.UpdateStorageSpace(r.Context(), updateSpaceRequest)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}

if resp.GetStatus().GetCode() != v1beta11.Code_CODE_OK {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, fmt.Errorf("").Error())
}

w.WriteHeader(http.StatusNoContent)
}

func cs3TimestampToTime(t *types.Timestamp) time.Time {
return time.Unix(int64(t.Seconds), int64(t.Nanos))
}
Expand Down
14 changes: 9 additions & 5 deletions graph/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package svc
import (
"net/http"

"github.com/owncloud/ocis/ocis-pkg/account"
opkgm "github.com/owncloud/ocis/ocis-pkg/middleware"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/owncloud/ocis/ocis-pkg/account"
opkgm "github.com/owncloud/ocis/ocis-pkg/middleware"
)

// Service defines the extension handlers.
Expand Down Expand Up @@ -53,12 +52,17 @@ func NewService(opts ...Option) Service {
r.Get("/", svc.GetGroup)
})
})
r.Route("/drives", func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(opkgm.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret)),
)
r.Post("/", svc.CreateDrive)
r.Route("/drives", func(r chi.Router) {
r.Post("/", svc.CreateDrive)
})
r.Route("/Drive({firstSegmentIdentifier})", func(r chi.Router) {
r.Patch("/*", svc.UpdateDrive)
})
})
})
})
Expand Down