github.com/cs3org/reva/v2@v2.27.7/pkg/storage/registry/spaces/spaces.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package spaces
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"encoding/json"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strconv"
    29  	"strings"
    30  	"text/template"
    31  
    32  	"github.com/Masterminds/sprig"
    33  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    34  	providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    35  	registrypb "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
    36  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    37  	"github.com/cs3org/reva/v2/pkg/appctx"
    38  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    39  	"github.com/cs3org/reva/v2/pkg/errtypes"
    40  	"github.com/cs3org/reva/v2/pkg/logger"
    41  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    42  	"github.com/cs3org/reva/v2/pkg/sharedconf"
    43  	"github.com/cs3org/reva/v2/pkg/storage"
    44  	pkgregistry "github.com/cs3org/reva/v2/pkg/storage/registry/registry"
    45  	"github.com/cs3org/reva/v2/pkg/storagespace"
    46  	"github.com/cs3org/reva/v2/pkg/utils"
    47  	"github.com/mitchellh/mapstructure"
    48  	"google.golang.org/grpc"
    49  )
    50  
    51  func init() {
    52  	pkgregistry.Register("spaces", NewDefault)
    53  }
    54  
    55  type spaceConfig struct {
    56  	// MountPoint determines where a space is mounted. Can be a regex
    57  	// It is used to determine which storage provider is responsible when only a path is given in the request
    58  	MountPoint string `mapstructure:"mount_point"`
    59  	// PathTemplate is used to build the path of an individual space. Layouts can access {{.Space...}} and {{.CurrentUser...}}
    60  	PathTemplate string `mapstructure:"path_template"`
    61  	template     *template.Template
    62  	// filters
    63  	OwnerIsCurrentUser bool   `mapstructure:"owner_is_current_user"`
    64  	ID                 string `mapstructure:"id"`
    65  	// TODO description?
    66  }
    67  
    68  // SpacePath generates a layout based on space data.
    69  func (sc *spaceConfig) SpacePath(currentUser *userpb.User, space *providerpb.StorageSpace) (string, error) {
    70  	b := bytes.Buffer{}
    71  	if err := sc.template.Execute(&b, templateData{CurrentUser: currentUser, Space: space}); err != nil {
    72  		return "", err
    73  	}
    74  	return b.String(), nil
    75  }
    76  
    77  // Provider holds information on Spaces
    78  type Provider struct {
    79  	// Spaces is a map from space type to space config
    80  	Spaces     map[string]*spaceConfig `mapstructure:"spaces"`
    81  	ProviderID string                  `mapstructure:"providerid"`
    82  }
    83  
    84  type templateData struct {
    85  	CurrentUser *userpb.User
    86  	Space       *providerpb.StorageSpace
    87  }
    88  
    89  // StorageProviderClient is the interface the spaces registry uses to interact with storage providers
    90  type StorageProviderClient interface {
    91  	ListStorageSpaces(ctx context.Context, in *providerpb.ListStorageSpacesRequest, opts ...grpc.CallOption) (*providerpb.ListStorageSpacesResponse, error)
    92  }
    93  
    94  type config struct {
    95  	Providers map[string]*Provider `mapstructure:"providers"`
    96  }
    97  
    98  func (c *config) init() {
    99  
   100  	if len(c.Providers) == 0 {
   101  		c.Providers = map[string]*Provider{
   102  			sharedconf.GetGatewaySVC(""): {
   103  				Spaces: map[string]*spaceConfig{
   104  					"personal":   {MountPoint: "/users", PathTemplate: "/users/{{.Space.Owner.Id.OpaqueId}}"},
   105  					"project":    {MountPoint: "/projects", PathTemplate: "/projects/{{.Space.Name}}"},
   106  					"virtual":    {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares"},
   107  					"grant":      {MountPoint: "."},
   108  					"mountpoint": {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", PathTemplate: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}"},
   109  					"public":     {MountPoint: "/public"},
   110  				},
   111  			},
   112  		}
   113  	}
   114  
   115  	// cleanup space paths
   116  	for _, provider := range c.Providers {
   117  		for _, space := range provider.Spaces {
   118  
   119  			if space.MountPoint == "" {
   120  				space.MountPoint = "/"
   121  			}
   122  
   123  			// if the path template is not explicitly set use the mount point as path template
   124  			if space.PathTemplate == "" {
   125  				space.PathTemplate = space.MountPoint
   126  			}
   127  
   128  			// cleanup path templates
   129  			space.PathTemplate = filepath.Join("/", space.PathTemplate)
   130  
   131  			// compile given template tpl
   132  			var err error
   133  			space.template, err = template.New("path_template").Funcs(sprig.TxtFuncMap()).Parse(space.PathTemplate)
   134  			if err != nil {
   135  				logger.New().Fatal().Err(err).Interface("space", space).Msg("error parsing template")
   136  			}
   137  		}
   138  
   139  		// TODO connect to provider, (List Spaces,) ListContainerStream
   140  	}
   141  }
   142  
   143  func parseConfig(m map[string]interface{}) (*config, error) {
   144  	c := &config{}
   145  	if err := mapstructure.Decode(m, c); err != nil {
   146  		return nil, err
   147  	}
   148  	return c, nil
   149  }
   150  
   151  // New creates an implementation of the storage.Registry interface that
   152  // uses the available storage spaces from the configured storage providers
   153  func New(m map[string]interface{}, getClientFunc GetSpacesProviderServiceClientFunc) (storage.Registry, error) {
   154  	c, err := parseConfig(m)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	c.init()
   159  	r := &registry{
   160  		c:                              c,
   161  		resources:                      make(map[string][]*registrypb.ProviderInfo),
   162  		resourceNameCache:              make(map[string]string),
   163  		getSpacesProviderServiceClient: getClientFunc,
   164  	}
   165  	return r, nil
   166  }
   167  
   168  // NewDefault creates an implementation of the storage.Registry interface that
   169  // uses the available storage spaces from the configured storage providers
   170  func NewDefault(m map[string]interface{}) (storage.Registry, error) {
   171  	getClientFunc := func(addr string) (StorageProviderClient, error) {
   172  		return pool.GetSpacesProviderServiceClient(addr)
   173  	}
   174  	return New(m, getClientFunc)
   175  }
   176  
   177  // GetStorageProviderServiceClientFunc is a callback used to pass in a StorageProviderClient during testing
   178  type GetSpacesProviderServiceClientFunc func(addr string) (StorageProviderClient, error)
   179  
   180  type registry struct {
   181  	c *config
   182  	// a map of resources to providers
   183  	resources         map[string][]*registrypb.ProviderInfo
   184  	resourceNameCache map[string]string
   185  
   186  	getSpacesProviderServiceClient GetSpacesProviderServiceClientFunc
   187  }
   188  
   189  // GetProvider return the storage provider for the given spaces according to the rule configuration
   190  func (r *registry) GetProvider(ctx context.Context, space *providerpb.StorageSpace) (*registrypb.ProviderInfo, error) {
   191  	for address, provider := range r.c.Providers {
   192  		for spaceType, sc := range provider.Spaces {
   193  			spacePath := ""
   194  			var err error
   195  			if space.SpaceType != "" && spaceType != space.SpaceType {
   196  				continue
   197  			}
   198  			if space.Owner != nil {
   199  				user := ctxpkg.ContextMustGetUser(ctx)
   200  				spacePath, err = sc.SpacePath(user, space)
   201  				if err != nil {
   202  					continue
   203  				}
   204  				if match, err := regexp.MatchString(sc.MountPoint, spacePath); err != nil || !match {
   205  					continue
   206  				}
   207  			}
   208  
   209  			setPath(space, spacePath)
   210  
   211  			p := &registrypb.ProviderInfo{
   212  				Address: address,
   213  			}
   214  			validSpaces := []*providerpb.StorageSpace{space}
   215  			if err := setSpaces(p, validSpaces); err != nil {
   216  				appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Interface("spaces", validSpaces).Msg("marshaling spaces failed, continuing")
   217  				continue
   218  			}
   219  			return p, nil // return the first match we find
   220  		}
   221  	}
   222  	return nil, errtypes.NotFound("no provider found for space")
   223  }
   224  
   225  // FIXME the config takes the mount path of a provider as key,
   226  // - it will always be used as the Providerpath
   227  // - if the mount path is a regex, the provider config needs a providerpath config that is used instead of the regex
   228  // - the gateway ALWAYS replaces the mountpath with the spaceid? and builds a relative reference which is forwarded to the responsible provider
   229  
   230  // FindProviders will return all providers that need to be queried for a request
   231  // - for an id based or relative request it will return the providers that serve the storage space
   232  // - for a path based request it will return the provider with the most specific mount path, as
   233  //   well as all spaces mounted below the requested path. Stat and ListContainer requests need
   234  //   to take their etag/mtime into account.
   235  // The list of providers also contains the space that should be used as the root for the relative path
   236  //
   237  // Given providers mounted at /home, /personal, /public, /shares, /foo and /foo/sub
   238  // When a stat for / arrives
   239  // Then the gateway needs all providers below /
   240  // -> all providers
   241  //
   242  // When a stat for /home arrives
   243  // Then the gateway needs all providers below /home
   244  // -> only the /home provider
   245  //
   246  // When a stat for /foo arrives
   247  // Then the gateway needs all providers below /foo
   248  // -> the /foo and /foo/sub providers
   249  //
   250  // Given providers mounted at /foo, /foo/sub and /foo/sub/bar
   251  // When a MKCOL for /foo/bif arrives
   252  // Then the ocdav will make a stat for /foo/bif
   253  // Then the gateway only needs the provider /foo
   254  // -> only the /foo provider
   255  
   256  // When a MKCOL for /foo/sub/mob arrives
   257  // Then the ocdav will make a stat for /foo/sub/mob
   258  // Then the gateway needs all providers below /foo/sub
   259  // -> only the /foo/sub provider
   260  //
   261  // requested path   provider path
   262  // above   = /foo           <=> /foo/bar        -> stat(spaceid, .)    -> add metadata for /foo/bar
   263  // above   = /foo           <=> /foo/bar/bif    -> stat(spaceid, .)    -> add metadata for /foo/bar
   264  // matches = /foo/bar       <=> /foo/bar        -> list(spaceid, .)
   265  // below   = /foo/bar/bif   <=> /foo/bar        -> list(spaceid, ./bif)
   266  func (r *registry) ListProviders(ctx context.Context, filters map[string]string) ([]*registrypb.ProviderInfo, error) {
   267  	unique, _ := strconv.ParseBool(filters["unique"])
   268  	unrestricted, _ := strconv.ParseBool(filters["unrestricted"])
   269  	mask := filters["mask"]
   270  	switch {
   271  	case filters["space_id"] != "":
   272  
   273  		findMountpoint := filters["type"] == "mountpoint"
   274  		findGrant := !findMountpoint && filters["path"] == "" // relative references, by definition, occur in the correct storage, so do not look for grants
   275  		// If opaque_id is empty, we assume that we are looking for a space root
   276  		if filters["opaque_id"] == "" {
   277  			filters["opaque_id"] = filters["space_id"]
   278  		}
   279  		id := storagespace.FormatResourceID(&providerpb.ResourceId{
   280  			StorageId: filters["storage_id"],
   281  			SpaceId:   filters["space_id"],
   282  			OpaqueId:  filters["opaque_id"],
   283  		})
   284  
   285  		return r.findProvidersForResource(ctx, id, findMountpoint, findGrant, unrestricted, mask), nil
   286  	case filters["path"] != "":
   287  		return r.findProvidersForAbsolutePathReference(ctx, filters["path"], unique, unrestricted, mask), nil
   288  	case len(filters) == 0:
   289  		// return all providers
   290  		return r.findAllProviders(ctx, mask), nil
   291  	default:
   292  		return r.findProvidersForFilter(ctx, r.buildFilters(filters), unrestricted, mask), nil
   293  	}
   294  }
   295  
   296  func (r *registry) buildFilters(filterMap map[string]string) []*providerpb.ListStorageSpacesRequest_Filter {
   297  	filters := []*providerpb.ListStorageSpacesRequest_Filter{}
   298  	for k, f := range filterMap {
   299  		switch k {
   300  		case "space_id":
   301  			filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   302  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
   303  				Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
   304  					Id: &providerpb.StorageSpaceId{
   305  						OpaqueId: f,
   306  					},
   307  				},
   308  			})
   309  		case "space_type":
   310  			filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   311  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
   312  				Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
   313  					SpaceType: f,
   314  				},
   315  			})
   316  		}
   317  	}
   318  	if filterMap["user_id"] != "" {
   319  		filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   320  			Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_USER,
   321  			Term: &providerpb.ListStorageSpacesRequest_Filter_User{
   322  				User: &userpb.UserId{
   323  					Idp:      filterMap["user_idp"],
   324  					OpaqueId: filterMap["user_id"],
   325  				},
   326  			},
   327  		})
   328  	}
   329  	if filterMap["owner_id"] != "" && filterMap["owner_idp"] != "" {
   330  		filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   331  			Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_OWNER,
   332  			Term: &providerpb.ListStorageSpacesRequest_Filter_Owner{
   333  				Owner: &userpb.UserId{
   334  					Idp:      filterMap["owner_idp"],
   335  					OpaqueId: filterMap["owner_id"],
   336  				},
   337  			},
   338  		})
   339  	}
   340  	return filters
   341  }
   342  
   343  func (r *registry) findProvidersForFilter(ctx context.Context, filters []*providerpb.ListStorageSpacesRequest_Filter, unrestricted bool, _ string) []*registrypb.ProviderInfo {
   344  
   345  	var requestedSpaceType string
   346  	for _, f := range filters {
   347  		if f.Type == providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE {
   348  			requestedSpaceType = f.GetSpaceType()
   349  		}
   350  	}
   351  
   352  	currentUser := ctxpkg.ContextMustGetUser(ctx)
   353  	providerInfos := []*registrypb.ProviderInfo{}
   354  	for address, provider := range r.c.Providers {
   355  
   356  		// when a specific space type is requested we may skip this provider altogether if it is not configured for that type
   357  		// we have to ignore a space type filter with +grant or +mountpoint type because they can live on any provider
   358  		if requestedSpaceType != "" && !strings.HasPrefix(requestedSpaceType, "+") {
   359  			found := false
   360  			for spaceType := range provider.Spaces {
   361  				if spaceType == requestedSpaceType {
   362  					found = true
   363  				}
   364  			}
   365  			if !found {
   366  				continue
   367  			}
   368  		}
   369  		p := &registrypb.ProviderInfo{
   370  			Address: address,
   371  		}
   372  		spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, unrestricted)
   373  		if err != nil {
   374  			appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider by id failed, continuing")
   375  			continue
   376  		}
   377  
   378  		validSpaces := []*providerpb.StorageSpace{}
   379  		if len(spaces) > 0 {
   380  			for _, space := range spaces {
   381  				var sc *spaceConfig
   382  				var ok bool
   383  				var spacePath string
   384  				// filter unconfigured space types
   385  				if sc, ok = provider.Spaces[space.SpaceType]; !ok {
   386  					continue
   387  				}
   388  				spacePath, err = sc.SpacePath(currentUser, space)
   389  				if err != nil {
   390  					appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing")
   391  					continue
   392  				}
   393  
   394  				setPath(space, spacePath)
   395  				validSpaces = append(validSpaces, space)
   396  			}
   397  
   398  			if len(validSpaces) > 0 {
   399  				if err := setSpaces(p, validSpaces); err != nil {
   400  					appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Interface("spaces", validSpaces).Msg("marshaling spaces failed, continuing")
   401  					continue
   402  				}
   403  				providerInfos = append(providerInfos, p)
   404  			}
   405  		}
   406  	}
   407  	return providerInfos
   408  }
   409  
   410  // findProvidersForResource looks up storage providers based on a resource id
   411  // for the root of a space the res.SpaceId is the same as the res.OpaqueId
   412  // for share spaces the res.SpaceId tells the registry the spaceid and res.OpaqueId is a node in that space
   413  func (r *registry) findProvidersForResource(ctx context.Context, id string, findMoundpoint, findGrant, unrestricted bool, mask string) []*registrypb.ProviderInfo {
   414  	currentUser := ctxpkg.ContextMustGetUser(ctx)
   415  	providerInfos := []*registrypb.ProviderInfo{}
   416  	rid, err := storagespace.ParseID(id)
   417  	if err != nil {
   418  		appctx.GetLogger(ctx).Error().Err(err).Msg("splitting spaceid failed")
   419  		return nil
   420  	}
   421  
   422  	for address, provider := range r.c.Providers {
   423  		p := &registrypb.ProviderInfo{
   424  			Address:    address,
   425  			ProviderId: rid.StorageId,
   426  		}
   427  		// try to find provider based on storageproviderid prefix if only root is requested
   428  		if provider.ProviderID != "" && rid.StorageId != "" && mask == "root" {
   429  			match, err := regexp.MatchString("^"+provider.ProviderID+"$", rid.StorageId)
   430  			if err != nil || !match {
   431  				continue
   432  			}
   433  			// construct space based on configured properties without actually making a ListStorageSpaces call
   434  			space := &providerpb.StorageSpace{
   435  				Id:   &providerpb.StorageSpaceId{OpaqueId: id},
   436  				Root: &rid,
   437  			}
   438  			// this is a request for requests by id
   439  			// setPath(space, provider.Path) // hmm not enough info to build a path.... the space alias is no longer known here we would need to query the provider
   440  
   441  			validSpaces := []*providerpb.StorageSpace{space}
   442  			if err := setSpaces(p, validSpaces); err != nil {
   443  				appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("spaces", validSpaces).Msg("marshaling spaces failed, continuing")
   444  				return nil
   445  			}
   446  			providerInfos = append(providerInfos, p)
   447  			return providerInfos
   448  		}
   449  		if provider.ProviderID != "" && rid.StorageId != "" {
   450  			match, err := regexp.MatchString("^"+provider.ProviderID+"$", rid.StorageId)
   451  			if err != nil || !match {
   452  				// skip mismatching storageproviders
   453  				continue
   454  			}
   455  		}
   456  		filters := []*providerpb.ListStorageSpacesRequest_Filter{{
   457  			Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
   458  			Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
   459  				Id: &providerpb.StorageSpaceId{
   460  					OpaqueId: id,
   461  				},
   462  			},
   463  		}}
   464  		if findMoundpoint {
   465  			// when listing by id return also grants and mountpoints
   466  			filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   467  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
   468  				Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
   469  					SpaceType: "+mountpoint",
   470  				},
   471  			})
   472  		}
   473  		if findGrant {
   474  			// when listing by id return also grants and mountpoints
   475  			filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
   476  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
   477  				Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
   478  					SpaceType: "+grant",
   479  				},
   480  			})
   481  		}
   482  		spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, unrestricted)
   483  		if err != nil {
   484  			appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider by id failed, continuing")
   485  			continue
   486  		}
   487  
   488  		switch len(spaces) {
   489  		case 0:
   490  			// nothing to do, will continue with next provider
   491  		case 1:
   492  			space := spaces[0]
   493  
   494  			var sc *spaceConfig
   495  			var ok bool
   496  			var spacePath string
   497  
   498  			if space.SpaceType == "grant" {
   499  				spacePath = "." // a . indicates a grant, the gateway will do a findMountpoint for it
   500  			} else {
   501  				if findMoundpoint && space.SpaceType != "mountpoint" {
   502  					continue
   503  				}
   504  				// filter unwanted space types. type mountpoint is not explicitly configured but requested by the gateway
   505  				if sc, ok = provider.Spaces[space.SpaceType]; !ok && space.SpaceType != "mountpoint" {
   506  					continue
   507  				}
   508  
   509  				spacePath, err = sc.SpacePath(currentUser, space)
   510  				if err != nil {
   511  					appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing")
   512  					continue
   513  				}
   514  
   515  				setPath(space, spacePath)
   516  			}
   517  			validSpaces := []*providerpb.StorageSpace{space}
   518  			if err := setSpaces(p, validSpaces); err != nil {
   519  				appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Interface("spaces", validSpaces).Msg("marshaling spaces failed, continuing")
   520  				continue
   521  			}
   522  			// we can stop after we found the first space
   523  			// TODO to improve lookup time the registry could cache which provider last was responsible for a space? could be invalidated by simple ttl? would that work for shares?
   524  			// return []*registrypb.ProviderInfo{p}
   525  			providerInfos = append(providerInfos, p) // hm we need to query all providers ... or the id based lookup might only see the spaces storage provider
   526  		default:
   527  			// there should not be multiple spaces with the same id per provider
   528  			appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("spaces", spaces).Msg("multiple spaces returned, ignoring")
   529  		}
   530  	}
   531  	return providerInfos
   532  }
   533  
   534  // findProvidersForAbsolutePathReference takes a path and returns the storage provider with the longest matching path prefix
   535  // FIXME use regex to return the correct provider when multiple are configured
   536  func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, requestedPath string, unique, unrestricted bool, _ string) []*registrypb.ProviderInfo {
   537  	currentUser := ctxpkg.ContextMustGetUser(ctx)
   538  
   539  	pathSegments := strings.Split(strings.TrimPrefix(requestedPath, string(os.PathSeparator)), string(os.PathSeparator))
   540  	deepestMountPath := ""
   541  	var deepestMountSpace *providerpb.StorageSpace
   542  	var deepestMountPathProvider *registrypb.ProviderInfo
   543  	providers := map[string]*registrypb.ProviderInfo{}
   544  	for address, provider := range r.c.Providers {
   545  		p := &registrypb.ProviderInfo{
   546  			Opaque:  &typesv1beta1.Opaque{Map: map[string]*typesv1beta1.OpaqueEntry{}},
   547  			Address: address,
   548  		}
   549  		var spaces []*providerpb.StorageSpace
   550  		var err error
   551  
   552  		// check if any space in the provider has a valid mountpoint
   553  		containsRelatedSpace := false
   554  
   555  	spaceLoop:
   556  		for _, space := range provider.Spaces {
   557  			spacePath, _ := space.SpacePath(currentUser, nil)
   558  			spacePathSegments := strings.Split(strings.TrimPrefix(spacePath, string(os.PathSeparator)), string(os.PathSeparator))
   559  
   560  			for i, segment := range spacePathSegments {
   561  				if i >= len(pathSegments) {
   562  					break
   563  				}
   564  				if pathSegments[i] != segment {
   565  					if segment != "" && !strings.Contains(segment, "{{") {
   566  						// Mount path points elsewhere -> irrelevant
   567  						continue spaceLoop
   568  					}
   569  					// Encountered a template which couldn't be filled -> potentially relevant
   570  					break
   571  				}
   572  			}
   573  
   574  			containsRelatedSpace = true
   575  			break
   576  		}
   577  
   578  		if !containsRelatedSpace {
   579  			continue
   580  		}
   581  
   582  		// when listing paths also return mountpoints
   583  		filters := []*providerpb.ListStorageSpacesRequest_Filter{
   584  			{
   585  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_PATH,
   586  				Term: &providerpb.ListStorageSpacesRequest_Filter_Path{
   587  					Path: strings.TrimPrefix(requestedPath, p.ProviderPath), // FIXME this no longer has an effect as the p.Providerpath is always empty
   588  				},
   589  			},
   590  			{
   591  				Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
   592  				Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
   593  					SpaceType: "+mountpoint",
   594  				},
   595  			},
   596  		}
   597  
   598  		spaces, err = r.findStorageSpaceOnProvider(ctx, p.Address, filters, unrestricted)
   599  		if err != nil {
   600  			appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider failed, continuing")
   601  			continue
   602  		}
   603  
   604  		validSpaces := []*providerpb.StorageSpace{}
   605  		for _, space := range spaces {
   606  			var sc *spaceConfig
   607  			var ok bool
   608  
   609  			if space.SpaceType == "grant" {
   610  				setPath(space, ".") // a . indicates a grant, the gateway will do a findMountpoint for it
   611  				validSpaces = append(validSpaces, space)
   612  				continue
   613  			}
   614  
   615  			// filter unwanted space types. type mountpoint is not explicitly configured but requested by the gateway
   616  			if sc, ok = provider.Spaces[space.SpaceType]; !ok {
   617  				continue
   618  			}
   619  			spacePath, err := sc.SpacePath(currentUser, space)
   620  			if err != nil {
   621  				appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing")
   622  				continue
   623  			}
   624  			setPath(space, spacePath)
   625  
   626  			// determine deepest mount point
   627  			switch {
   628  			case spacePath == requestedPath && unique:
   629  				validSpaces = append(validSpaces, space)
   630  
   631  				deepestMountPath = spacePath
   632  				deepestMountSpace = space
   633  				deepestMountPathProvider = p
   634  
   635  			case !unique && isSubpath(spacePath, requestedPath):
   636  				// and add all providers below and exactly matching the path
   637  				// requested /foo, mountPath /foo/sub
   638  				validSpaces = append(validSpaces, space)
   639  				if len(spacePath) > len(deepestMountPath) {
   640  					deepestMountPath = spacePath
   641  					deepestMountSpace = space
   642  					deepestMountPathProvider = p
   643  				}
   644  
   645  			case isSubpath(requestedPath, spacePath) && len(spacePath) > len(deepestMountPath):
   646  				// eg. three providers: /foo, /foo/sub, /foo/sub/bar
   647  				// requested /foo/sub/mob
   648  				deepestMountPath = spacePath
   649  				deepestMountSpace = space
   650  				deepestMountPathProvider = p
   651  			}
   652  		}
   653  
   654  		if len(validSpaces) > 0 {
   655  			if err := setSpaces(p, validSpaces); err != nil {
   656  				appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("marshaling spaces failed, continuing")
   657  				continue
   658  			}
   659  			providers[p.Address] = p
   660  		}
   661  	}
   662  
   663  	if deepestMountPathProvider != nil {
   664  		if _, ok := providers[deepestMountPathProvider.Address]; !ok {
   665  			if err := setSpaces(deepestMountPathProvider, []*providerpb.StorageSpace{deepestMountSpace}); err == nil {
   666  				providers[deepestMountPathProvider.Address] = deepestMountPathProvider
   667  			} else {
   668  				appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", deepestMountPathProvider).Interface("space", deepestMountSpace).Msg("marshaling space failed, continuing")
   669  			}
   670  		}
   671  	}
   672  
   673  	providerInfos := []*registrypb.ProviderInfo{}
   674  	for _, p := range providers {
   675  		providerInfos = append(providerInfos, p)
   676  	}
   677  	return providerInfos
   678  }
   679  
   680  // findAllProviders returns a list of all storage providers
   681  // This is a dumb call that does not call ListStorageSpaces() on the providers: ListStorageSpaces() in the gateway can cache that better.
   682  func (r *registry) findAllProviders(ctx context.Context, _ string) []*registrypb.ProviderInfo {
   683  	pis := make([]*registrypb.ProviderInfo, 0, len(r.c.Providers))
   684  	for address := range r.c.Providers {
   685  		pis = append(pis, &registrypb.ProviderInfo{
   686  			Address: address,
   687  		})
   688  	}
   689  	return pis
   690  }
   691  
   692  func setPath(space *providerpb.StorageSpace, path string) {
   693  	if space.Opaque == nil {
   694  		space.Opaque = &typesv1beta1.Opaque{}
   695  	}
   696  	if space.Opaque.Map == nil {
   697  		space.Opaque.Map = map[string]*typesv1beta1.OpaqueEntry{}
   698  	}
   699  	if _, ok := space.Opaque.Map["path"]; !ok {
   700  		space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "path", path)
   701  	}
   702  }
   703  func setSpaces(providerInfo *registrypb.ProviderInfo, spaces []*providerpb.StorageSpace) error {
   704  	if providerInfo.Opaque == nil {
   705  		providerInfo.Opaque = &typesv1beta1.Opaque{}
   706  	}
   707  	if providerInfo.Opaque.Map == nil {
   708  		providerInfo.Opaque.Map = map[string]*typesv1beta1.OpaqueEntry{}
   709  	}
   710  	spacesBytes, err := json.Marshal(spaces)
   711  	if err != nil {
   712  		return err
   713  	}
   714  	providerInfo.Opaque.Map["spaces"] = &typesv1beta1.OpaqueEntry{
   715  		Decoder: "json",
   716  		Value:   spacesBytes,
   717  	}
   718  	return nil
   719  }
   720  
   721  func (r *registry) findStorageSpaceOnProvider(ctx context.Context, addr string, filters []*providerpb.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*providerpb.StorageSpace, error) {
   722  	c, err := r.getSpacesProviderServiceClient(addr)
   723  	if err != nil {
   724  		return nil, err
   725  	}
   726  	req := &providerpb.ListStorageSpacesRequest{
   727  		Opaque: &typesv1beta1.Opaque{
   728  			Map: map[string]*typesv1beta1.OpaqueEntry{
   729  				"unrestricted": {
   730  					Decoder: "plain",
   731  					Value:   []byte(strconv.FormatBool(unrestricted)),
   732  				},
   733  			},
   734  		},
   735  		Filters: filters,
   736  	}
   737  
   738  	res, err := c.ListStorageSpaces(ctx, req)
   739  	if err != nil {
   740  		// ignore errors
   741  		return nil, nil
   742  	}
   743  	return res.StorageSpaces, nil
   744  }
   745  
   746  // isSubpath determines if `p` is a subpath of `path`
   747  func isSubpath(p string, path string) bool {
   748  	if p == path {
   749  		return true
   750  	}
   751  
   752  	r, err := filepath.Rel(path, p)
   753  	if err != nil {
   754  		return false
   755  	}
   756  
   757  	return r != ".." && !strings.HasPrefix(r, "../")
   758  }