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:
+72
-29
@@ -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{}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user