K8s/Folders: Allow recursive creation of DTO (#96439)

* Fix toDTO
* Remove conversion function for folder dto
* Convert toDTO to a standalone function

---------

Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
This commit is contained in:
Arati R.
2024-11-15 15:21:57 +01:00
committed by GitHub
parent 7ae0d551fe
commit 2e62f75166
2 changed files with 97 additions and 131 deletions

View File

@@ -675,7 +675,7 @@ func (fk8s *folderK8sHandler) searchFolders(c *contextmodel.ReqContext) {
query := strings.ToUpper(c.Query("query"))
folders := []folder.Folder{}
for _, item := range out.Items {
p := internalfolders.UnstructuredToLegacyFolder(item, c.SignedInUser.GetOrgID())
p, _ := internalfolders.UnstructuredToLegacyFolder(item, c.SignedInUser.GetOrgID())
if p == nil {
continue
}
@@ -821,76 +821,12 @@ func (fk8s *folderK8sHandler) writeError(c *contextmodel.ReqContext, err 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, orgID)
fDTO, err := internalfolders.UnstructuredToLegacyFolderDTO(item)
if err != nil {
return dtos.Folder{}, err
}
// #TODO Is there a preexisting function we can use instead, something along the lines of UserIdentifier?
toUID := func(rawIdentifier string) string {
parts := strings.Split(rawIdentifier, ":")
if len(parts) < 2 {
return ""
}
return parts[1]
}
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
}
canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave()
canAdmin, _ := g.CanAdmin()
canDelete, _ := g.CanDelete()
// 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 {
creator = fk8s.getUserLogin(ctx, toUID(fDTO.CreatedBy))
}
if len(fDTO.UpdatedBy) > 0 {
updater = fk8s.getUserLogin(ctx, toUID(fDTO.UpdatedBy))
}
acMetadata, _ := fk8s.getFolderACMetadata(c, fold)
if checkCanView {
canView, _ := g.CanView()
if !canView {
return dtos.Folder{
UID: REDACTED,
Title: REDACTED,
}, nil
}
}
metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc()
fDTO.CanSave = canSave
fDTO.CanEdit = canEdit
fDTO.CanAdmin = canAdmin
fDTO.CanDelete = canDelete
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
}
f, createdBy := internalfolders.UnstructuredToLegacyFolder(item, orgID)
dontCheckCanView := false
checkCanView := true
// no need to check view permission for the starting folder since it's already checked by the callers
folderDTO, err := toDTO(f, false)
folderDTO, err := fk8s.toDTO(c, f, createdBy, dontCheckCanView)
if err != nil {
return dtos.Folder{}, err
}
@@ -917,12 +853,18 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
uid := parentsFullPathUIDs[1:][i]
url := dashboards.GetFolderURL(uid, slug)
parents = append(parents, dtos.Folder{
ff := folder.Folder{
UID: uid,
OrgID: c.SignedInUser.GetOrgID(),
Title: v,
URL: url,
})
}
parentDTO, err := fk8s.toDTO(c, &ff, "", checkCanView)
if err != nil {
// #TODO should we log this error?
return dtos.Folder{}, err
}
parents = append(parents, parentDTO)
}
folderDTO.Parents = parents
@@ -930,6 +872,74 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
return folderDTO, nil
}
func toUID(rawIdentifier string) string {
// #TODO Is there a preexisting function we can use instead, something along the lines of UserIdentifier?
parts := strings.Split(rawIdentifier, ":")
if len(parts) < 2 {
return ""
}
return parts[1]
}
func (fk8s *folderK8sHandler) toDTO(c *contextmodel.ReqContext, fold *folder.Folder, createdBy string, checkCanView bool) (dtos.Folder, error) {
// #TODO revisit how/where we get orgID
ctx := c.Req.Context()
g, err := guardian.NewByFolder(c.Req.Context(), fold, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return dtos.Folder{}, err
}
canEdit, _ := g.CanEdit()
canSave, _ := g.CanSave()
canAdmin, _ := g.CanAdmin()
canDelete, _ := g.CanDelete()
// 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(createdBy) > 0 {
creator = fk8s.getUserLogin(ctx, toUID(createdBy))
}
if len(createdBy) > 0 {
updater = fk8s.getUserLogin(ctx, toUID(createdBy))
}
acMetadata, _ := fk8s.getFolderACMetadata(c, fold)
if checkCanView {
canView, _ := g.CanView()
if !canView {
return dtos.Folder{
UID: REDACTED,
Title: REDACTED,
}, nil
}
}
metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc()
return dtos.Folder{
ID: fold.ID, // nolint:staticcheck
UID: fold.UID,
Title: fold.Title,
URL: fold.URL,
HasACL: fold.HasACL,
CanSave: canSave,
CanEdit: canEdit,
CanAdmin: canAdmin,
CanDelete: canDelete,
CreatedBy: creator,
Created: fold.Created,
UpdatedBy: updater,
Updated: fold.Updated,
// #TODO version doesn't seem to be used--confirm or set it properly
Version: 1,
AccessControl: acMetadata,
ParentUID: fold.ParentUID,
}, nil
}
func (fk8s *folderK8sHandler) getUserLogin(ctx context.Context, userUID string) string {
ctx, span := tracer.Start(ctx, "api.getUserLogin")
defer span.End()

View File

@@ -9,7 +9,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/slugify"
@@ -64,7 +63,7 @@ func LegacyUpdateCommandToUnstructured(cmd folder.UpdateFolderCommand) (unstruct
return obj, nil
}
func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *folder.Folder {
func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) (*folder.Folder, string) {
// #TODO reduce duplication of the different conversion functions
spec := item.Object["spec"].(map[string]any)
uid := item.GetName()
@@ -72,22 +71,24 @@ func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *fo
meta, err := utils.MetaAccessor(&item)
if err != nil {
return nil
return nil, ""
}
id, err := getLegacyID(meta)
if err != nil {
return nil
return nil, ""
}
created, err := getCreated(meta)
if err != nil {
return nil
return nil, ""
}
// avoid panic
var createdTime time.Time
if created != nil {
// #TODO Fix this time format. The legacy time format seems to be along the lines of time.Now()
// which includes a part that represents a fraction of a second. Format should be "2024-09-12T15:37:41.09466+02:00"
createdTime = created.Local()
}
@@ -97,10 +98,11 @@ func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *fo
ID: id,
ParentUID: meta.GetFolder(),
// #TODO add created by field if necessary
// CreatedBy: meta.GetCreatedBy(),
// UpdatedBy: meta.GetCreatedBy(),
URL: getURL(meta, title),
URL: getURL(meta, title),
// #TODO get Created in format "2024-09-12T15:37:41.09466+02:00"
Created: createdTime,
// #TODO figure out whether we want to set "updated" and "updated by". Could replace with
// meta.GetUpdatedTimestamp() but it currently gets overwritten in prepareObjectForStorage().
Updated: createdTime,
OrgID: orgID,
@@ -114,57 +116,10 @@ func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *fo
// nolint:staticcheck
FullpathUIDs: meta.GetFullPathUIDs(),
}
return f
}
func UnstructuredToLegacyFolderDTO(item unstructured.Unstructured) (*dtos.Folder, error) {
spec := item.Object["spec"].(map[string]any)
uid := item.GetName()
title := spec["title"].(string)
meta, err := utils.MetaAccessor(&item)
if err != nil {
return nil, err
}
id, err := getLegacyID(meta)
if err != nil {
return nil, err
}
created, err := getCreated(meta)
if err != nil {
return nil, err
}
// avoid panic
var createdTime time.Time
if created != nil {
// #TODO Fix this time format. The legacy time format seems to be along the lines of time.Now()
// which includes a part that represents a fraction of a second.
createdTime = created.Local()
}
dto := &dtos.Folder{
UID: uid,
Title: title,
ID: id,
ParentUID: meta.GetFolder(),
// #TODO add back CreatedBy, UpdatedBy once we figure out how to access userService
// to translate user ID into user login. meta.GetCreatedBy() only stores user ID
// Could convert meta.GetCreatedBy() return value to a struct--id and name
CreatedBy: meta.GetCreatedBy(),
UpdatedBy: meta.GetCreatedBy(),
URL: getURL(meta, title),
// #TODO get Created in format "2024-09-12T15:37:41.09466+02:00"
Created: createdTime,
// #TODO figure out whether we want to set "updated" and "updated by". Could replace with
// meta.GetUpdatedTimestamp() but it currently gets overwritten in prepareObjectForStorage().
Updated: createdTime,
// #TODO figure out about adding version, parents, orgID fields
}
return dto, nil
// CreatedBy needs to be returned separately because it's the user UID (string) but
// folder.Folder expects user ID (int64).
return f, meta.GetCreatedBy()
// #TODO figure out about adding version, parents, orgID fields
}
func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) (*v0alpha1.Folder, error) {
@@ -198,6 +153,7 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper)
// #TODO: turns out these get overwritten by Unified Storage (see pkg/storage/unified/apistore/prepare.go)
// We're going to have to align with that. For now we do need the user ID because the folder type stores it
// as the only user identifier
if v.CreatedByUID != "" {
meta.SetCreatedBy(v.UpdatedByUID)
}