K8s/Folder: Show parent folder information (#97938)

This commit is contained in:
Ryan McKinley 2024-12-17 17:19:40 +03:00 committed by GitHub
parent 00b51c0422
commit eb38a63559
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 33 deletions

View File

@ -45,14 +45,20 @@ type FolderInfoList struct {
// FolderInfo briefly describes a folder -- unlike a folder resource,
// this is a partial record of the folder metadata used for navigating parents and children
type FolderInfo struct {
// UID is the unique identifier for a folder (and the k8s name)
UID string `json:"uid"`
// Name is the k8s name (eg, the unique identifier) for a folder
Name string `json:"name"`
// Title is the display value
Title string `json:"title"`
// The folder description
Description string `json:"description,omitempty"`
// The parent folder UID
Parent string `json:"parent,omitempty"`
// This folder does not resolve
Detached bool `json:"detached,omitempty"`
}
// Access control information for the current user

View File

@ -170,9 +170,9 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co
Description: "FolderInfo briefly describes a folder -- unlike a folder resource, this is a partial record of the folder metadata used for navigating parents and children",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"uid": {
"name": {
SchemaProps: spec.SchemaProps{
Description: "UID is the unique identifier for a folder (and the k8s name)",
Description: "Name is the k8s name (eg, the unique identifier) for a folder",
Default: "",
Type: []string{"string"},
Format: "",
@ -186,6 +186,13 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co
Format: "",
},
},
"description": {
SchemaProps: spec.SchemaProps{
Description: "The folder description",
Type: []string{"string"},
Format: "",
},
},
"parent": {
SchemaProps: spec.SchemaProps{
Description: "The parent folder UID",
@ -193,8 +200,15 @@ func schema_pkg_apis_folder_v0alpha1_FolderInfo(ref common.ReferenceCallback) co
Format: "",
},
},
"detached": {
SchemaProps: spec.SchemaProps{
Description: "This folder does not resolve",
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"uid", "title"},
Required: []string{"name", "title"},
},
},
}

View File

@ -126,11 +126,6 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API
storage := map[string]rest.Storage{}
storage[resourceInfo.StoragePath()] = legacyStore
storage[resourceInfo.StoragePath("parents")] = &subParentsREST{b.folderSvc}
storage[resourceInfo.StoragePath("access")] = &subAccessREST{b.folderSvc}
storage[resourceInfo.StoragePath("counts")] = &subCountREST{searcher: b.searcher}
// enable dual writer
if optsGetter != nil && dualWriteBuilder != nil {
store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter)
if err != nil {
@ -141,6 +136,11 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API
return err
}
}
storage[resourceInfo.StoragePath("parents")] = &subParentsREST{
getter: storage[resourceInfo.StoragePath()].(rest.Getter), // Get the parents
}
storage[resourceInfo.StoragePath("counts")] = &subCountREST{searcher: b.searcher}
storage[resourceInfo.StoragePath("access")] = &subAccessREST{b.folderSvc}
apiGroupInfo.VersionedResourcesStorageMap[v0alpha1.VERSION] = storage
b.storage = storage[resourceInfo.StoragePath()].(grafanarest.Storage)

View File

@ -2,18 +2,20 @@ package folders
import (
"context"
"fmt"
"net/http"
"slices"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/folder"
)
type subParentsREST struct {
service folder.Service
getter rest.Getter
}
var _ = rest.Connecter(&subParentsREST{})
@ -43,32 +45,58 @@ func (r *subParentsREST) NewConnectOptions() (runtime.Object, bool, string) {
}
func (r *subParentsREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
obj, err := r.getter.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, err
}
folder, ok := obj.(*v0alpha1.Folder)
if !ok {
return nil, fmt.Errorf("expecting folder, found: %T", folder)
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
responder.Error(err)
return
}
parents, err := r.service.GetParents(ctx, folder.GetParentsQuery{
UID: name,
OrgID: ns.OrgID,
})
if err != nil {
responder.Error(err)
return
}
info := &v0alpha1.FolderInfoList{
Items: make([]v0alpha1.FolderInfo, 0),
Items: []v0alpha1.FolderInfo{},
}
for _, parent := range parents {
for folder != nil {
parent := ""
meta, _ := utils.MetaAccessor(folder)
if meta != nil {
parent = meta.GetFolder()
}
info.Items = append(info.Items, v0alpha1.FolderInfo{
UID: parent.UID,
Title: parent.Title,
Parent: parent.ParentUID,
Name: folder.Name,
Title: folder.Spec.Title,
Description: folder.Spec.Description,
Parent: parent,
})
if parent == "" {
break
}
obj, err = r.getter.Get(ctx, parent, &metav1.GetOptions{})
if err != nil {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: err.Error(),
})
break
}
folder, ok = obj.(*v0alpha1.Folder)
if !ok {
info.Items = append(info.Items, v0alpha1.FolderInfo{
Name: parent,
Detached: true,
Description: fmt.Sprintf("expected folder, found: %T", obj),
})
break
}
}
// Start from the root
slices.Reverse(info.Items)
responder.Object(http.StatusOK, info)
}), nil
}