Unified Storage /Folders: Allow Unified Storage subfolders creation (#94327)

* Add parents field to folder DTO
* Allow subfolder creation when folder flag is enabled
* Update UnstructuredToLegacyFolder
* Include parents field when creating folder
This commit is contained in:
Arati R.
2024-10-07 16:48:56 +02:00
committed by GitHub
parent 0d67b5a6e9
commit ea5cf7c51f
3 changed files with 125 additions and 34 deletions
+72 -29
View File
@@ -635,6 +635,8 @@ type folderK8sHandler struct {
// #TODO check if it makes more sense to move this to FolderAPIBuilder // #TODO check if it makes more sense to move this to FolderAPIBuilder
accesscontrolService accesscontrol.Service accesscontrolService accesscontrol.Service
userService user.Service userService user.Service
// #TODO remove after we handle the nested folder case
folderService folder.Service
} }
//----------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------
@@ -648,6 +650,7 @@ func newFolderK8sHandler(hs *HTTPServer) *folderK8sHandler {
clientConfigProvider: hs.clientConfigProvider, clientConfigProvider: hs.clientConfigProvider,
accesscontrolService: hs.accesscontrolService, accesscontrolService: hs.accesscontrolService,
userService: hs.userService, userService: hs.userService,
folderService: hs.folderService,
} }
} }
@@ -700,7 +703,7 @@ func (fk8s *folderK8sHandler) createFolder(c *contextmodel.ReqContext) {
} }
fk8s.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) fk8s.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
folderDTO, err := fk8s.newToFolderDto(c, *out) folderDTO, err := fk8s.newToFolderDto(c, *out, c.SignedInUser.GetOrgID())
if err != nil { if err != nil {
fk8s.writeError(c, err) fk8s.writeError(c, err)
return return
@@ -795,10 +798,11 @@ func (fk8s *folderK8sHandler) writeError(c *contextmodel.ReqContext, err error)
errhttp.Write(c.Req.Context(), err, c.Resp) errhttp.Write(c.Req.Context(), err, c.Resp)
} }
func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item unstructured.Unstructured) (dtos.Folder, error) { func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item unstructured.Unstructured, orgID int64) (dtos.Folder, error) {
// #TODO revisit how/where we get orgID
ctx := c.Req.Context() ctx := c.Req.Context()
f := internalfolders.UnstructuredToLegacyFolder(item) f := internalfolders.UnstructuredToLegacyFolder(item, orgID)
fDTO, err := internalfolders.UnstructuredToLegacyFolderDTO(item) fDTO, err := internalfolders.UnstructuredToLegacyFolderDTO(item)
if err != nil { if err != nil {
@@ -817,8 +821,8 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
return userID, nil return userID, nil
} }
toDTO := func(f *folder.Folder, checkCanView bool) (dtos.Folder, error) { toDTO := func(fold *folder.Folder, checkCanView bool) (dtos.Folder, error) {
g, err := guardian.NewByFolder(c.Req.Context(), f, c.SignedInUser.GetOrgID(), c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), fold, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil { if err != nil {
return dtos.Folder{}, err return dtos.Folder{}, err
} }
@@ -830,6 +834,8 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
// Finding creator and last updater of the folder // Finding creator and last updater of the folder
updater, creator := anonString, anonString updater, creator := anonString, anonString
// #TODO refactor the various conversions of the folder so that we either set created by in folder.Folder or
// we convert from unstructured to folder DTO without an intermediate conversion to folder.Folder
if len(fDTO.CreatedBy) > 0 { if len(fDTO.CreatedBy) > 0 {
id, err := toID(fDTO.CreatedBy) id, err := toID(fDTO.CreatedBy)
if err != nil { if err != nil {
@@ -845,7 +851,7 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
updater = fk8s.getUserLogin(ctx, id) updater = fk8s.getUserLogin(ctx, id)
} }
acMetadata, _ := fk8s.getFolderACMetadata(c, f) acMetadata, _ := fk8s.getFolderACMetadata(c, fold)
if checkCanView { if checkCanView {
canView, _ := g.CanView() canView, _ := g.CanView()
@@ -865,6 +871,9 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
fDTO.CreatedBy = creator fDTO.CreatedBy = creator
fDTO.UpdatedBy = updater fDTO.UpdatedBy = updater
fDTO.AccessControl = acMetadata fDTO.AccessControl = acMetadata
fDTO.OrgID = f.OrgID
// #TODO version doesn't seem to be used--confirm or set it properly
fDTO.Version = 1
return *fDTO, nil return *fDTO, nil
} }
@@ -875,24 +884,54 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
return dtos.Folder{}, err return dtos.Folder{}, err
} }
// TODO: handle parents parents := []*folder.Folder{}
/* if folderDTO.ParentUID != "" {
parents, err := fk8s.folderService.GetParents(ctx, folder.GetParentsQuery{UID: f.UID, OrgID: f.OrgID}) parents, err = fk8s.folderService.GetParents(
c.Req.Context(),
folder.GetParentsQuery{
UID: folderDTO.UID,
OrgID: folderDTO.OrgID,
})
if err != nil { if err != nil {
// log the error instead of failing return dtos.Folder{}, err
fk8s.log.Error("failed to fetch folder parents", "folder", f.UID, "org", f.OrgID, "error", err) }
}
// #TODO refactor so that we have just one function for converting to folder DTO
toParentDTO := func(fold *folder.Folder, checkCanView bool) (dtos.Folder, error) {
g, err := guardian.NewByFolder(c.Req.Context(), fold, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return dtos.Folder{}, err
} }
folderDTO.Parents = make([]dtos.Folder, 0, len(parents)) if checkCanView {
for _, f := range parents { canView, _ := g.CanView()
DTO, err := toDTO(f, true) if !canView {
if err != nil { return dtos.Folder{
// fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err) UID: REDACTED,
continue Title: REDACTED,
}, nil
} }
folderDTO.Parents = append(folderDTO.Parents, DTO)
} }
*/ metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc()
return dtos.Folder{
UID: fold.UID,
Title: fold.Title,
URL: fold.URL,
}, nil
}
folderDTO.Parents = make([]dtos.Folder, 0, len(parents))
for _, f := range parents {
DTO, err := toParentDTO(f, true)
if err != nil {
// #TODO add logging
// fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err)
continue
}
folderDTO.Parents = append(folderDTO.Parents, DTO)
}
return folderDTO, nil return folderDTO, nil
} }
@@ -914,20 +953,24 @@ func (fk8s *folderK8sHandler) getFolderACMetadata(c *contextmodel.ReqContext, f
return nil, nil return nil, nil
} }
folderIDs := map[string]bool{f.UID: true} var err error
parents := []*folder.Folder{}
// TODO: handle parents if f.ParentUID != "" {
/* parents, err = fk8s.folderService.GetParents(
parents, err := fk8s.folderService.GetParents(c.Req.Context(), folder.GetParentsQuery{UID: f.UID, OrgID: c.SignedInUser.GetOrgID()}) c.Req.Context(),
folder.GetParentsQuery{
UID: f.UID,
OrgID: c.SignedInUser.GetOrgID(),
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
folderIDs := map[string]bool{f.UID: true} folderIDs := map[string]bool{f.UID: true}
for _, p := range parents { for _, p := range parents {
folderIDs[p.UID] = true folderIDs[p.UID] = true
} }
*/
allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs) allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
metadata := map[string]bool{} metadata := map[string]bool{}
+39 -5
View File
@@ -50,13 +50,47 @@ func LegacyUpdateCommandToUnstructured(cmd folder.UpdateFolderCommand) unstructu
return obj return obj
} }
func UnstructuredToLegacyFolder(item unstructured.Unstructured) *folder.Folder { func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *folder.Folder {
// #TODO reduce duplication of the different conversion functions
spec := item.Object["spec"].(map[string]any) spec := item.Object["spec"].(map[string]any)
return &folder.Folder{ uid := item.GetName()
UID: item.GetName(), title := spec["title"].(string)
Title: spec["title"].(string),
// #TODO add other fields meta, err := utils.MetaAccessor(&item)
if err != nil {
return nil
} }
id, err := getLegacyID(meta)
if err != nil {
return nil
}
created, err := getCreated(meta)
if err != nil {
return nil
}
// avoid panic
var createdTime time.Time
if created != nil {
createdTime = created.Local()
}
f := &folder.Folder{
UID: uid,
Title: title,
ID: id,
ParentUID: meta.GetFolder(),
// #TODO add created by field if necessary
// CreatedBy: meta.GetCreatedBy(),
// UpdatedBy: meta.GetCreatedBy(),
URL: getURL(meta, title),
Created: createdTime,
Updated: createdTime,
OrgID: orgID,
}
return f
} }
func UnstructuredToLegacyFolderDTO(item unstructured.Unstructured) (*dtos.Folder, error) { func UnstructuredToLegacyFolderDTO(item unstructured.Unstructured) (*dtos.Folder, error) {
+14
View File
@@ -1,6 +1,9 @@
package sql package sql
import ( import (
"context"
"github.com/grafana/authlib/claims"
infraDB "github.com/grafana/grafana/pkg/infra/db" infraDB "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -31,5 +34,16 @@ func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, features featuremgmt.Fea
opts.Index = resource.NewResourceIndexServer() opts.Index = resource.NewResourceIndexServer()
} }
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFolders) {
opts.WriteAccess = resource.WriteAccessHooks{
Folder: func(ctx context.Context, user claims.AuthInfo, uid string) bool {
// #TODO build on the logic here
// #TODO only enable write access when the resource being written in the folder
// is another folder
return true
},
}
}
return resource.NewResourceServer(opts) return resource.NewResourceServer(opts)
} }