Merge pull request #9377 from fschade/bump-reva-7fb5f11a24cf

enhancement: bump reva and remove explicit automount mountpoint naming
This commit is contained in:
Roman Perekhod
2024-06-19 18:18:48 +02:00
committed by GitHub
14 changed files with 278 additions and 256 deletions
+9 -117
View File
@@ -3,17 +3,14 @@ package command
import (
"context"
"errors"
"fmt"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/utils"
"go-micro.dev/v4/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
@@ -21,15 +18,13 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
"github.com/owncloud/ocis/v2/services/frontend/pkg/config"
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
"go-micro.dev/v4/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
)
@@ -116,41 +111,23 @@ func AutoAcceptShares(ev events.ShareCreated, autoAcceptDefault bool, l log.Logg
return
}
uids, err := getUserIDs(ctx, gatewaySelector, ev.GranteeUserID, ev.GranteeGroupID)
userIDs, err := getUserIDs(ctx, gatewaySelector, ev.GranteeUserID, ev.GranteeGroupID)
if err != nil {
l.Error().Err(err).Msg("cannot get grantees")
return
}
gwc, err = gatewaySelector.Next()
if err != nil {
l.Error().Err(err).Msg("cannot get gateway client")
return
}
info, err := utils.GetResourceByID(ctx, ev.ItemID, gwc)
if err != nil {
l.Error().Err(err).Msg("error getting resource")
return
}
for _, uid := range uids {
for _, uid := range userIDs {
if !autoAcceptShares(ctx, uid, autoAcceptDefault, vs) {
continue
}
mp, err := getMountPoint(ctx, l, ev.ItemID, uid, gatewaySelector, info)
if err != nil {
l.Error().Err(err).Msg("error getting mount point")
continue
}
gwc, err := gatewaySelector.Next()
if err != nil {
l.Error().Err(err).Msg("cannot get gateway client")
continue
}
resp, err := gwc.UpdateReceivedShare(ctx, updateShareRequest(ev.ShareID, uid, mp))
resp, err := gwc.UpdateReceivedShare(ctx, updateShareRequest(ev.ShareID, uid))
if err != nil {
l.Error().Err(err).Msg("error sending grpc request")
continue
@@ -163,66 +140,6 @@ func AutoAcceptShares(ev events.ShareCreated, autoAcceptDefault bool, l log.Logg
}
func getMountPoint(ctx context.Context, l log.Logger, itemid *provider.ResourceId, uid *user.UserId, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], info *provider.ResourceInfo) (string, error) {
lrs, err := getSharesList(ctx, gatewaySelector, uid)
if err != nil {
return "", err
}
// we need to sort the received shares by mount point in order to make things easier to evaluate.
base := path.Base(info.GetPath())
mount := base
mounts := make([]string, 0, len(lrs.Shares))
var exists bool
for _, s := range lrs.Shares {
if s.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
// we don't care about unaccepted shares
continue
}
if utils.ResourceIDEqual(s.GetShare().GetResourceId(), itemid) {
// a share to the same resource already exists and is mounted
return s.GetMountPoint().GetPath(), nil
}
if s.GetMountPoint().GetPath() == mount {
// does the shared resource still exist?
gwc, err := gatewaySelector.Next()
if err != nil {
l.Error().Err(err).Msg("cannot get gateway client")
continue
}
_, err = utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
if err == nil {
exists = true
}
// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
}
// collect all mount points
mounts = append(mounts, s.GetMountPoint().GetPath())
}
// If the mount point really already exists, we need to insert a number into the filename
if exists {
// now we have a list of shares, we want to iterate over all of them and check for name collisions agents a mount points list
for i := 1; i <= len(mounts)+1; i++ {
ext := filepath.Ext(base)
name := strings.TrimSuffix(base, ext)
// be smart about .tar.(gz|bz) files
if strings.HasSuffix(name, ".tar") {
name = strings.TrimSuffix(name, ".tar")
ext = ".tar" + ext
}
mount = name + " (" + strconv.Itoa(i) + ")" + ext
if !slices.Contains(mounts, mount) {
return mount, nil
}
}
}
return mount, nil
}
func getUserIDs(ctx context.Context, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], uid *user.UserId, gid *group.GroupId) ([]*user.UserId, error) {
if uid != nil {
return []*user.UserId{uid}, nil
@@ -257,40 +174,15 @@ func autoAcceptShares(ctx context.Context, uid *user.UserId, defaultValue bool,
return defaultValue
}
func updateShareRequest(shareID *collaboration.ShareId, uid *user.UserId, path string) *collaboration.UpdateReceivedShareRequest {
func updateShareRequest(shareID *collaboration.ShareId, uid *user.UserId) *collaboration.UpdateReceivedShareRequest {
return &collaboration.UpdateReceivedShareRequest{
Opaque: utils.AppendJSONToOpaque(nil, "userid", uid),
Share: &collaboration.ReceivedShare{
Share: &collaboration.Share{
Id: shareID,
},
MountPoint: &provider.Reference{
Path: path,
},
State: collaboration.ShareState_SHARE_STATE_ACCEPTED,
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
}
}
// getSharesList gets the list of all shares for the given user.
func getSharesList(ctx context.Context, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], uid *user.UserId) (*collaboration.ListReceivedSharesResponse, error) {
gwc, err := gatewaySelector.Next()
if err != nil {
return nil, err
}
shares, err := gwc.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{
Opaque: utils.AppendJSONToOpaque(nil, "userid", uid),
})
if err != nil {
return nil, err
}
if shares.Status.Code != rpc.Code_CODE_OK {
if shares.Status.Code == rpc.Code_CODE_NOT_FOUND {
return nil, fmt.Errorf("not found")
}
return nil, fmt.Errorf(shares.GetStatus().GetMessage())
}
return shares, nil
}
+6 -4
View File
@@ -5,6 +5,7 @@ import (
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/utils"
)
@@ -19,11 +20,12 @@ import (
// This function is particularly useful when dealing with CS3 responses,
// and a unified error handling within the application is necessary.
func FromCS3Status(status *cs3rpc.Status, inerr error, ignore ...cs3rpc.Code) error {
if inerr != nil {
return Error{msg: inerr.Error(), errorCode: GeneralException}
}
err := Error{errorCode: GeneralException, msg: "unspecified error has occurred", origin: ErrorOriginCS3}
err := Error{errorCode: GeneralException, msg: "unspecified error has occurred"}
if inerr != nil {
err.msg = inerr.Error()
return err
}
if status != nil {
err.msg = status.GetMessage()
+6 -1
View File
@@ -44,6 +44,11 @@ func TestFromCS3Status(t *testing.T) {
}
for _, test := range tests {
var e errorcode.Error
if errors.As(test.expected, &e) {
test.expected = e.WithOrigin(errorcode.ErrorOriginCS3)
}
if got := errorcode.FromCS3Status(test.status, test.err, test.ignore...); !reflect.DeepEqual(got, test.expected) {
t.Error("Test Failed: {} expected, received: {}", test.expected, got)
}
@@ -56,7 +61,7 @@ func TestFromStat(t *testing.T) {
err error
result error
}{
{nil, errors.New("some error"), errorcode.New(errorcode.GeneralException, "some error")},
{nil, errors.New("some error"), errorcode.New(errorcode.GeneralException, "some error").WithOrigin(errorcode.ErrorOriginCS3)},
{&provider.StatResponse{Status: &cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}}, nil, nil},
}
+43 -8
View File
@@ -12,15 +12,28 @@ import (
libregraph "github.com/owncloud/libre-graph-api-go"
)
// ErrorCode defines code as used in MS Graph - see https://docs.microsoft.com/en-us/graph/errors?context=graph%2Fapi%2F1.0&view=graph-rest-1.0
type ErrorCode int
// Error defines a custom error struct, containing and MS Graph error code and a textual error message
type Error struct {
errorCode ErrorCode
msg string
origin ErrorOrigin
}
// ErrorOrigin gives information about where the error originated
type ErrorOrigin int
const (
// ErrorOriginUnknown is the default error source
// and indicates that the error does not have any information about its origin
ErrorOriginUnknown ErrorOrigin = iota
// ErrorOriginCS3 indicates that the error originated from a CS3 service
ErrorOriginCS3
)
// ErrorCode defines code as used in MS Graph - see https://docs.microsoft.com/en-us/graph/errors?context=graph%2Fapi%2F1.0&view=graph-rest-1.0
type ErrorCode int
// List taken from https://github.com/microsoft/microsoft-graph-docs-1/blob/main/concepts/errors.md#code-property
const (
// AccessDenied defines the error if the caller doesn't have permission to perform the action.
@@ -148,7 +161,7 @@ func (e ErrorCode) String() string {
return errorCodes[e]
}
// Error return the concatenation of the error string and optional message
// Error returns the concatenation of the error string and optional message
func (e Error) Error() string {
errString := errorCodes[e.errorCode]
if e.msg != "" {
@@ -161,13 +174,35 @@ func (e Error) GetCode() ErrorCode {
return e.errorCode
}
// GetOrigin returns the source of the error
func (e Error) GetOrigin() ErrorOrigin {
return e.origin
}
// WithOrigin returns a new Error with the provided origin
func (e Error) WithOrigin(o ErrorOrigin) Error {
e.origin = o
return e
}
// RenderError render the Graph Error based on a code or default one
func RenderError(w http.ResponseWriter, r *http.Request, err error) {
var errcode Error
if errors.As(err, &errcode) {
errcode.Render(w, r)
e, ok := ToError(err)
if !ok {
GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
e.Render(w, r)
}
// ToError checks if the error is of type Error and returns it,
// the second parameter indicates if the error conversion was successful
func ToError(err error) (Error, bool) {
var e Error
if errors.As(err, &e) {
return e, true
}
return Error{}, false
}
@@ -17,12 +17,6 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
roleconversions "github.com/cs3org/reva/v2/pkg/conversions"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
"github.com/go-chi/chi/v5"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@@ -31,6 +25,13 @@ import (
"github.com/tidwall/gjson"
"google.golang.org/grpc"
roleconversions "github.com/cs3org/reva/v2/pkg/conversions"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/mocks"
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
@@ -249,7 +250,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
statResponse.Status = status.NewNotFound(context.Background(), "not found")
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, "not found")))
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, "not found").WithOrigin(errorcode.ErrorOriginCS3)))
Expect(permission).To(BeZero())
})
})
@@ -999,7 +1000,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
driveItemPermission.SetExpirationDateTime(expiration)
res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission)
Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "expiration date is in the past")))
Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "expiration date is in the past").WithOrigin(errorcode.ErrorOriginCS3)))
Expect(res).To(BeZero())
})
})
@@ -360,7 +360,7 @@ func (api DrivesDriveItemApi) DeleteDriveItem(w http.ResponseWriter, r *http.Req
shareID := ExtractShareIdFromResourceId(itemID)
if err := api.drivesDriveItemService.UnmountShare(ctx, shareID); err != nil {
api.logger.Debug().Err(err).Msg(err.Error())
api.logger.Debug().Err(err).Msg(ErrUnmountShare.Error())
errorcode.RenderError(w, r, err)
return
}
@@ -518,8 +518,15 @@ func (api DrivesDriveItemApi) CreateDriveItem(w http.ResponseWriter, r *http.Req
mountedShares, err := api.drivesDriveItemService.MountShare(ctx, &resourceId, requestDriveItem.GetName())
if err != nil {
api.logger.Debug().Err(err).Msg(err.Error())
errorcode.RenderError(w, r, err)
api.logger.Debug().Err(err).Msg(ErrMountShare.Error())
switch e, ok := errorcode.ToError(err); {
case ok && e.GetOrigin() == errorcode.ErrorOriginCS3 && e.GetCode() == errorcode.ItemNotFound:
ErrDriveItemConversion.Render(w, r)
default:
errorcode.RenderError(w, r, err)
}
return
}
@@ -152,7 +152,7 @@ var _ = Describe("DrivesDriveItemService", func() {
Once()
_, err := drivesDriveItemService.GetShare(context.Background(), &collaborationv1beta1.ShareId{})
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
})
It("fails if share lookup does not report an error but the status is off", func() {
@@ -161,12 +161,12 @@ var _ = Describe("DrivesDriveItemService", func() {
EXPECT().
GetReceivedShare(context.Background(), mock.Anything, mock.Anything).
Return(&collaborationv1beta1.GetReceivedShareResponse{
Status: status.NewNotFound(context.Background(), someErr.Error()),
Status: status.NewInvalid(context.Background(), someErr.Error()),
}, nil).
Once()
_, err := drivesDriveItemService.GetShare(context.Background(), &collaborationv1beta1.ShareId{})
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
})
It("successfully returns a share", func() {
@@ -219,7 +219,7 @@ var _ = Describe("DrivesDriveItemService", func() {
request.Share.State = collaborationv1beta1.ShareState_SHARE_STATE_ACCEPTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, "state")
})
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
})
It("fails if share update does not report an error but the status is off", func() {
@@ -236,7 +236,7 @@ var _ = Describe("DrivesDriveItemService", func() {
request.Share.State = collaborationv1beta1.ShareState_SHARE_STATE_ACCEPTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, "state")
})
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
})
})
@@ -265,7 +265,7 @@ var _ = Describe("DrivesDriveItemService", func() {
request.Share.State = collaborationv1beta1.ShareState_SHARE_STATE_ACCEPTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, "state")
})
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
Expect(err.(interface{ Unwrap() []error }).Unwrap()).To(HaveLen(2))
Expect(shares).To(HaveLen(1))
})
@@ -281,7 +281,7 @@ var _ = Describe("DrivesDriveItemService", func() {
Once()
err := drivesDriveItemService.UnmountShare(context.Background(), &collaborationv1beta1.ShareId{})
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
})
It("requests only accepted shares to be unmounted", func() {
@@ -358,7 +358,7 @@ var _ = Describe("DrivesDriveItemService", func() {
Times(1)
err := drivesDriveItemService.UnmountShare(context.Background(), &collaborationv1beta1.ShareId{})
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
Expect(err.(interface{ Unwrap() []error }).Unwrap()).To(HaveLen(1))
})
})
@@ -436,7 +436,7 @@ var _ = Describe("DrivesDriveItemService", func() {
Times(3)
shares, err := drivesDriveItemService.MountShare(context.Background(), nil, "some")
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error())))
Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, someErr.Error()).WithOrigin(errorcode.ErrorOriginCS3)))
Expect(err.(interface{ Unwrap() []error }).Unwrap()).To(HaveLen(3))
Expect(shares).To(HaveLen(0))
})