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

View File

@ -635,6 +635,8 @@ type folderK8sHandler struct {
// #TODO check if it makes more sense to move this to FolderAPIBuilder
accesscontrolService accesscontrol.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,
accesscontrolService: hs.accesscontrolService,
userService: hs.userService,
folderService: hs.folderService,
}
}
@ -700,7 +703,7 @@ func (fk8s *folderK8sHandler) createFolder(c *contextmodel.ReqContext) {
}
fk8s.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
folderDTO, err := fk8s.newToFolderDto(c, *out)
folderDTO, err := fk8s.newToFolderDto(c, *out, c.SignedInUser.GetOrgID())
if err != nil {
fk8s.writeError(c, err)
return
@ -795,10 +798,11 @@ func (fk8s *folderK8sHandler) writeError(c *contextmodel.ReqContext, err error)
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()
f := internalfolders.UnstructuredToLegacyFolder(item)
f := internalfolders.UnstructuredToLegacyFolder(item, orgID)
fDTO, err := internalfolders.UnstructuredToLegacyFolderDTO(item)
if err != nil {
@ -817,8 +821,8 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
return userID, nil
}
toDTO := func(f *folder.Folder, checkCanView bool) (dtos.Folder, error) {
g, err := guardian.NewByFolder(c.Req.Context(), f, c.SignedInUser.GetOrgID(), c.SignedInUser)
toDTO := 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
}
@ -830,6 +834,8 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
// Finding creator and last updater of the folder
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 {
id, err := toID(fDTO.CreatedBy)
if err != nil {
@ -845,7 +851,7 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
updater = fk8s.getUserLogin(ctx, id)
}
acMetadata, _ := fk8s.getFolderACMetadata(c, f)
acMetadata, _ := fk8s.getFolderACMetadata(c, fold)
if checkCanView {
canView, _ := g.CanView()
@ -865,6 +871,9 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
fDTO.CreatedBy = creator
fDTO.UpdatedBy = updater
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
}
@ -875,24 +884,54 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
return dtos.Folder{}, err
}
// TODO: handle parents
/*
parents, err := fk8s.folderService.GetParents(ctx, folder.GetParentsQuery{UID: f.UID, OrgID: f.OrgID})
parents := []*folder.Folder{}
if folderDTO.ParentUID != "" {
parents, err = fk8s.folderService.GetParents(
c.Req.Context(),
folder.GetParentsQuery{
UID: folderDTO.UID,
OrgID: folderDTO.OrgID,
})
if err != nil {
// log the error instead of failing
fk8s.log.Error("failed to fetch folder parents", "folder", f.UID, "org", f.OrgID, "error", err)
return dtos.Folder{}, 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))
for _, f := range parents {
DTO, err := toDTO(f, true)
if err != nil {
// fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err)
continue
if checkCanView {
canView, _ := g.CanView()
if !canView {
return dtos.Folder{
UID: REDACTED,
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
}
@ -914,20 +953,24 @@ func (fk8s *folderK8sHandler) getFolderACMetadata(c *contextmodel.ReqContext, f
return nil, nil
}
folderIDs := map[string]bool{f.UID: true}
// TODO: handle parents
/*
parents, err := fk8s.folderService.GetParents(c.Req.Context(), folder.GetParentsQuery{UID: f.UID, OrgID: c.SignedInUser.GetOrgID()})
var err error
parents := []*folder.Folder{}
if f.ParentUID != "" {
parents, err = fk8s.folderService.GetParents(
c.Req.Context(),
folder.GetParentsQuery{
UID: f.UID,
OrgID: c.SignedInUser.GetOrgID(),
})
if err != nil {
return nil, err
}
}
folderIDs := map[string]bool{f.UID: true}
for _, p := range parents {
folderIDs[p.UID] = true
}
*/
folderIDs := map[string]bool{f.UID: true}
for _, p := range parents {
folderIDs[p.UID] = true
}
allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
metadata := map[string]bool{}

View File

@ -50,13 +50,47 @@ func LegacyUpdateCommandToUnstructured(cmd folder.UpdateFolderCommand) unstructu
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)
return &folder.Folder{
UID: item.GetName(),
Title: spec["title"].(string),
// #TODO add other fields
uid := item.GetName()
title := spec["title"].(string)
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) {

View File

@ -1,6 +1,9 @@
package sql
import (
"context"
"github.com/grafana/authlib/claims"
infraDB "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"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()
}
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)
}