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 := ®istry{ 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 := ®istrypb.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 := ®istrypb.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 := ®istrypb.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 := ®istrypb.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, ®istrypb.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 }