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

Layout template #476

Merged
merged 11 commits into from
Feb 6, 2020
1 change: 1 addition & 0 deletions examples/separate/storage-home.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enable_home_creation = true

[grpc.services.storageprovider.drivers.owncloud]
datadirectory = "/var/tmp/reva/data"
layout = "{{.FirstLetter}}/{{.UsernameLower}}"

[http]
address = "0.0.0.0:12001"
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/fatih/color v1.7.0 // indirect
github.com/go-openapi/strfmt v0.19.2 // indirect
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gogo/protobuf v1.2.0 // indirect
github.com/golang/protobuf v1.3.2
github.com/gomodule/redigo v2.0.0+incompatible
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
Expand All @@ -29,13 +30,15 @@ require (
github.com/rs/zerolog v1.17.2
go.opencensus.io v0.22.1
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
google.golang.org/grpc v1.26.0
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect
gopkg.in/ldap.v2 v2.5.1
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect
)

go 1.13
32 changes: 25 additions & 7 deletions pkg/storage/fs/eos/eos.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"strings"

"github.com/cs3org/reva/pkg/storage/fs/registry"
"github.com/cs3org/reva/pkg/storage/helper"

"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/eosclient"
Expand Down Expand Up @@ -119,6 +120,9 @@ type config struct {

// SingleUsername is the username to use when SingleUserMode is enabled
SingleUsername string `mapstructure:"single_username"`

// Layout
Layout string `mapstructure:"layout"`
}

func getUser(ctx context.Context) (*userpb.User, error) {
Expand Down Expand Up @@ -155,6 +159,10 @@ func (c *config) init() {
if c.CacheDirectory == "" {
c.CacheDirectory = os.TempDir()
}

if c.Layout == "" {
c.Layout = "{{.Username}}"
}
}

// New returns a new implementation of the storage.FS interface that connects to EOS.
Expand Down Expand Up @@ -197,11 +205,14 @@ func New(m map[string]interface{}) (storage.FS, error) {
return eosStorage, nil
}

func (fs *eosStorage) getHomeForUser(u *userpb.User) string {
// TODO(labkode): define home path layout in configuration
// like home: %letter%/%username% and then do string substitution.
home := path.Join(fs.mountpoint, u.Username)
return home
func (fs *eosStorage) getHomeForUser(u *userpb.User) (string, error) {
userhome, err := helper.GetUserHomePath(u, fs.conf.Layout)
if err != nil {
return "", err
}

home := path.Join(fs.mountpoint, userhome)
return home, nil
}

func (fs *eosStorage) Shutdown(ctx context.Context) error {
Expand Down Expand Up @@ -571,7 +582,11 @@ func (fs *eosStorage) GetHome(ctx context.Context) (string, error) {
return "", errors.Wrap(err, "eos: no user in ctx")
}

home := fs.getHomeForUser(u)
home, err := fs.getHomeForUser(u)
if err != nil {
return "", err
}

return home, nil
}

Expand All @@ -581,7 +596,10 @@ func (fs *eosStorage) CreateHome(ctx context.Context) error {
return errors.Wrap(err, "eos: no user in ctx")
}

home := fs.getHomeForUser(u)
home, err := fs.getHomeForUser(u)
if err != nil {
return err
}

_, err = fs.c.GetFileInfoByPath(ctx, "root", home)
if err == nil { // home already exists
Expand Down
28 changes: 22 additions & 6 deletions pkg/storage/fs/owncloud/owncloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/cs3org/reva/pkg/mime"
"github.com/cs3org/reva/pkg/storage"
"github.com/cs3org/reva/pkg/storage/fs/registry"
"github.com/cs3org/reva/pkg/storage/helper"
"github.com/cs3org/reva/pkg/user"
"github.com/gofrs/uuid"
"github.com/gomodule/redigo/redis"
Expand Down Expand Up @@ -153,6 +154,7 @@ type config struct {
DataDirectory string `mapstructure:"datadirectory"`
Scan bool `mapstructure:"scan"`
Redis string `mapstructure:"redis"`
Layout string `mapstructure:"layout"`
}

func parseConfig(m map[string]interface{}) (*config, error) {
Expand All @@ -168,6 +170,9 @@ func (c *config) init(m map[string]interface{}) {
if c.Redis == "" {
c.Redis = ":6379"
}
if c.Layout == "" {
c.Layout = "{{.Username}}"
}
// default to scanning if not configured
if _, ok := m["scan"]; !ok {
c.Scan = true
Expand Down Expand Up @@ -849,17 +854,25 @@ func (fs *ocFS) GetQuota(ctx context.Context) (int, int, error) {
return 0, 0, nil
}

func (fs *ocFS) getHomeForUser(u *userpb.User) string {
return path.Join("/", u.Username)
func (fs *ocFS) getHomeForUser(u *userpb.User) (string, error) {
userhome, err := helper.GetUserHomePath(u, fs.c.Layout)
if err != nil {
return "", err
}

return path.Join("/", userhome), nil
}

func (fs *ocFS) CreateHome(ctx context.Context) error {
u, err := getUser(ctx)
if err != nil {
return errors.Wrap(err, "eos: no user in ctx")
return errors.Wrap(err, "ocFS: no user in ctx")
}

home := fs.getHomeForUser(u)
home, err := fs.getHomeForUser(u)
if err != nil {
return err
}

homePaths := []string{
path.Join(fs.c.DataDirectory, home, "files"),
Expand All @@ -879,10 +892,13 @@ func (fs *ocFS) CreateHome(ctx context.Context) error {
func (fs *ocFS) GetHome(ctx context.Context) (string, error) {
u, err := getUser(ctx)
if err != nil {
return "", errors.Wrap(err, "eos: no user in ctx")
return "", errors.Wrap(err, "ocFS: no user in ctx")
}

home := fs.getHomeForUser(u)
home, err := fs.getHomeForUser(u)
if err != nil {
return "", err
}
return home, nil
}

Expand Down
71 changes: 71 additions & 0 deletions pkg/storage/helper/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2018-2019 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 helper

import (
"bytes"
"fmt"
"strings"
"text/template"

"github.com/cs3org/reva/pkg/errtypes"
"github.com/pkg/errors"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
)

type layoutTemplate struct {
Username string //the username
UsernameLower string //the username in lowercase
FirstLetter string //first letter of username in lowercase
Copy link
Contributor

Choose a reason for hiding this comment

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

Currently, FirstLetter limits that dir to a single rune. I'd like to see a variable length to use as the name for the parent dir. I lack the words for a better name of the Parent directory. Prefix does not cut it. Maybe bucket. For s3 storages we were considering the first n chars of an md5 / sha1 of the username to spread users among multiple buckets. But never followed up on it because randomly putting users into buckets seems nice, but in practice you'll end up with unevenly filled buckets when a few powerusers land in the same bucket. Which is why we stored a users home in the account table. That allows moving a single user to a different storage ...

This is better than the current code so still a +1 from me ;-)

Copy link
Contributor Author

@madsi1m madsi1m Jan 21, 2020

Choose a reason for hiding this comment

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

Good idea, added {{.UsernamePrefixCount.x}} which at least solves the ability to set the x to 3 so you can make valid s3 buckets. I'm not sure how to spread users across "buckets" evenly as we cant dictate how a user will use the storage.

Provider string //Provider/domain of user in lowercase
}

// GetUserHomePath converts username into user's home path according to layout
func GetUserHomePath(u *userpb.User, layout string) (string, error) {
if u.Username == "" {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), "user has no username")
}

usernameSplit := strings.Split(u.Username, "@")
if len(usernameSplit) == 1 {
usernameSplit = append(usernameSplit, "_unknown")
}
if usernameSplit[1] == "" {
usernameSplit[1] = "_unknown"
}

pathTemplate := layoutTemplate{
Username: u.Username,
UsernameLower: strings.ToLower(u.Username),
FirstLetter: strings.ToLower(string([]rune(usernameSplit[0])[0])),
Provider: strings.ToLower(usernameSplit[1]),
}
tmpl, err := template.New("userhomepath").Parse(layout)
if err != nil {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), fmt.Sprintf("template parse error: %s", err.Error()))
}
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, pathTemplate)
if err != nil {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), fmt.Sprintf("template execute error: %s", err.Error()))
}

return buf.String(), nil
}
29 changes: 17 additions & 12 deletions pkg/storage/pw/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ package context

import (
"context"
"fmt"
"path"
"strings"

"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/storage"
"github.com/cs3org/reva/pkg/storage/helper"
"github.com/cs3org/reva/pkg/storage/pw/registry"
"github.com/cs3org/reva/pkg/user"
"github.com/mitchellh/mapstructure"
Expand All @@ -37,10 +39,11 @@ func init() {

type config struct {
Prefix string `mapstructure:"prefix"`
Layout string `mapstructure:"layout"`
}

func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
c := &config{Layout: "{{.Username}}"}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
Expand All @@ -55,33 +58,31 @@ func New(m map[string]interface{}) (storage.PathWrapper, error) {
if err != nil {
return nil, err
}
return &pw{prefix: c.Prefix}, nil
return &pw{prefix: c.Prefix, layout: c.Layout}, nil
}

type pw struct {
prefix string
layout string
}

// Only works when a user is in context
func (pw *pw) Unwrap(ctx context.Context, rp string) (string, error) {

// TODO how do we get the users home path?
// - construct based on homedir prefix + username/userid?
// - look into context?
// - query preferences?
// - do nothing
// -> screams for a wrapper/unwrapper 'strategy'

u, ok := user.ContextGetUser(ctx)
if !ok {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")
}
if u.Username == "" {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), "user has no username")
}

return path.Join("/", pw.prefix, u.Username, rp), nil
userhome, err := helper.GetUserHomePath(u, pw.layout)
if err != nil {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), fmt.Sprintf("template error: %s", err.Error()))
}
return path.Join("/", pw.prefix, userhome, rp), nil
}

func (pw *pw) Wrap(ctx context.Context, rp string) (string, error) {
u, ok := user.ContextGetUser(ctx)
if !ok {
Expand All @@ -90,5 +91,9 @@ func (pw *pw) Wrap(ctx context.Context, rp string) (string, error) {
if u.Username == "" {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), "user has no username")
}
return strings.TrimPrefix(rp, path.Join("/", u.Username)), nil
userhome, err := helper.GetUserHomePath(u, pw.layout)
if err != nil {
return "", errors.Wrap(errtypes.UserRequired("userrequired"), fmt.Sprintf("template error: %s", err.Error()))
}
return strings.TrimPrefix(rp, path.Join("/", userhome)), nil
}