Folders/K8s: Fix createdBy and updatedBy fields in response (#99569)
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package folderimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func (ss *FolderUnifiedStoreImpl) UnstructuredToLegacyFolder(ctx context.Context, item *unstructured.Unstructured) (*folder.Folder, error) {
|
||||
meta, err := utils.MetaAccessor(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, _ := authlib.ParseNamespace(meta.GetNamespace())
|
||||
if info.OrgID < 0 {
|
||||
info.OrgID = 1 // This resolves all test cases that assume org 1
|
||||
}
|
||||
|
||||
title, _, _ := unstructured.NestedString(item.Object, "spec", "title")
|
||||
description, _, _ := unstructured.NestedString(item.Object, "spec", "description")
|
||||
|
||||
uid := meta.GetName()
|
||||
url := ""
|
||||
if uid != folder.RootFolder.UID {
|
||||
slug := slugify.Slugify(title)
|
||||
url = dashboards.GetFolderURL(uid, slug)
|
||||
}
|
||||
|
||||
created := meta.GetCreationTimestamp().Time.UTC()
|
||||
updated, _ := meta.GetUpdatedTimestamp()
|
||||
if updated == nil {
|
||||
updated = &created
|
||||
} else {
|
||||
tmp := updated.UTC()
|
||||
updated = &tmp
|
||||
}
|
||||
|
||||
creator, err := ss.getUserFromMeta(ctx, meta.GetCreatedBy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updater, err := ss.getUserFromMeta(ctx, meta.GetUpdatedBy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if updater.UID == "" {
|
||||
updater = creator
|
||||
}
|
||||
|
||||
return &folder.Folder{
|
||||
UID: uid,
|
||||
Title: title,
|
||||
Description: description,
|
||||
ID: meta.GetDeprecatedInternalID(), // nolint:staticcheck
|
||||
ParentUID: meta.GetFolder(),
|
||||
Version: int(meta.GetGeneration()),
|
||||
Repository: meta.GetRepositoryName(),
|
||||
|
||||
URL: url,
|
||||
Created: created,
|
||||
Updated: *updated,
|
||||
OrgID: info.OrgID,
|
||||
CreatedBy: creator.ID,
|
||||
UpdatedBy: updater.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ss *FolderUnifiedStoreImpl) getUserFromMeta(ctx context.Context, userMeta string) (*user.User, error) {
|
||||
if userMeta == "" || toUID(userMeta) == "" {
|
||||
return &user.User{}, nil
|
||||
}
|
||||
usr, err := ss.getUser(ctx, toUID(userMeta))
|
||||
if err != nil && errors.Is(err, user.ErrUserNotFound) {
|
||||
return &user.User{}, nil
|
||||
}
|
||||
return usr, err
|
||||
}
|
||||
|
||||
func (ss *FolderUnifiedStoreImpl) getUser(ctx context.Context, uid string) (*user.User, error) {
|
||||
userID, err := strconv.ParseInt(uid, 10, 64)
|
||||
if err == nil {
|
||||
return ss.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: userID})
|
||||
}
|
||||
return ss.userService.GetByUID(ctx, &user.GetUserByUIDQuery{UID: uid})
|
||||
}
|
||||
|
||||
func toUID(rawIdentifier string) string {
|
||||
parts := strings.Split(rawIdentifier, ":")
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package folderimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
)
|
||||
|
||||
func TestFolderConversions(t *testing.T) {
|
||||
input := &unstructured.Unstructured{}
|
||||
err := input.UnmarshalJSON([]byte(`{
|
||||
"kind": "Folder",
|
||||
"apiVersion": "folder.grafana.app/v0alpha1",
|
||||
"metadata": {
|
||||
"name": "be79sztagf20wd",
|
||||
"namespace": "default",
|
||||
"uid": "wfi3RARqQREzEKtUJCWurWevwbQ7i9ii0cA7JUIbMtEX",
|
||||
"resourceVersion": "1734509107000",
|
||||
"creationTimestamp": "2022-12-02T02:02:02Z",
|
||||
"generation": 4,
|
||||
"labels": {
|
||||
"grafana.app/deprecatedInternalID": "234"
|
||||
},
|
||||
"annotations": {
|
||||
"grafana.app/folder": "parent-folder-name",
|
||||
"grafana.app/updatedTimestamp": "2022-12-02T07:02:02Z",
|
||||
"grafana.app/repoName": "example-repo",
|
||||
"grafana.app/createdBy": "user:useruid",
|
||||
"grafana.app/updatedBy": "user:useruid"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "test folder",
|
||||
"description": "Something set in the file"
|
||||
}
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
created, err := time.Parse(time.RFC3339, "2022-12-02T02:02:02Z")
|
||||
created = created.UTC()
|
||||
require.NoError(t, err)
|
||||
|
||||
fake := usertest.NewUserServiceFake()
|
||||
fake.ExpectedUser = &user.User{ID: 10, UID: "useruid"}
|
||||
|
||||
fs := ProvideUnifiedStore(nil, fake)
|
||||
|
||||
converted, err := fs.UnstructuredToLegacyFolder(context.Background(), input)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, folder.Folder{
|
||||
ID: 234,
|
||||
OrgID: 1,
|
||||
Version: 4,
|
||||
UID: "be79sztagf20wd",
|
||||
ParentUID: "parent-folder-name",
|
||||
Title: "test folder",
|
||||
Description: "Something set in the file",
|
||||
URL: "/dashboards/f/be79sztagf20wd/test-folder",
|
||||
Repository: "example-repo",
|
||||
Created: created,
|
||||
Updated: created.Add(time.Hour * 5),
|
||||
CreatedBy: 10,
|
||||
UpdatedBy: 10,
|
||||
}, *converted)
|
||||
}
|
||||
@@ -53,7 +53,6 @@ type Service struct {
|
||||
log *slog.Logger
|
||||
dashboardStore dashboards.Store
|
||||
dashboardFolderStore folder.FolderStore
|
||||
userService user.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
accessControl accesscontrol.AccessControl
|
||||
k8sclient folderK8sHandler
|
||||
@@ -88,7 +87,6 @@ func ProvideService(
|
||||
dashboardStore: dashboardStore,
|
||||
dashboardFolderStore: folderStore,
|
||||
store: store,
|
||||
userService: userService,
|
||||
features: features,
|
||||
accessControl: ac,
|
||||
bus: bus,
|
||||
@@ -114,7 +112,7 @@ func ProvideService(
|
||||
recourceClientProvider: unified.GetResourceClient,
|
||||
}
|
||||
|
||||
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
||||
unifiedStore := ProvideUnifiedStore(k8sHandler, userService)
|
||||
|
||||
srv.unifiedStore = unifiedStore
|
||||
srv.k8sclient = k8sHandler
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
)
|
||||
|
||||
@@ -181,7 +182,11 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
recourceClientProvider: f,
|
||||
}
|
||||
|
||||
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
||||
userService := &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{},
|
||||
}
|
||||
|
||||
unifiedStore := ProvideUnifiedStore(k8sHandler, userService)
|
||||
|
||||
ctx := context.Background()
|
||||
usr := &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
|
||||
@@ -20,21 +20,24 @@ import (
|
||||
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type FolderUnifiedStoreImpl struct {
|
||||
log log.Logger
|
||||
k8sclient folderK8sHandler
|
||||
log log.Logger
|
||||
k8sclient folderK8sHandler
|
||||
userService user.Service
|
||||
}
|
||||
|
||||
// sqlStore implements the store interface.
|
||||
var _ folder.Store = (*FolderUnifiedStoreImpl)(nil)
|
||||
|
||||
func ProvideUnifiedStore(k8sHandler *foldk8sHandler) *FolderUnifiedStoreImpl {
|
||||
func ProvideUnifiedStore(k8sHandler *foldk8sHandler, userService user.Service) *FolderUnifiedStoreImpl {
|
||||
return &FolderUnifiedStoreImpl{
|
||||
k8sclient: k8sHandler,
|
||||
log: log.New("folder-store"),
|
||||
k8sclient: k8sHandler,
|
||||
log: log.New("folder-store"),
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ func (ss *FolderUnifiedStoreImpl) Create(ctx context.Context, cmd folder.CreateF
|
||||
return nil, err
|
||||
}
|
||||
|
||||
folder, err := internalfolders.UnstructuredToLegacyFolder(out)
|
||||
folder, err := ss.UnstructuredToLegacyFolder(ctx, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +138,7 @@ func (ss *FolderUnifiedStoreImpl) Update(ctx context.Context, cmd folder.UpdateF
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internalfolders.UnstructuredToLegacyFolder(out)
|
||||
return ss.UnstructuredToLegacyFolder(ctx, out)
|
||||
}
|
||||
|
||||
// If WithFullpath is true it computes also the full path of a folder.
|
||||
@@ -178,7 +181,7 @@ func (ss *FolderUnifiedStoreImpl) Get(ctx context.Context, q folder.GetFolderQue
|
||||
return nil, dashboards.ErrFolderNotFound
|
||||
}
|
||||
|
||||
return internalfolders.UnstructuredToLegacyFolder(out)
|
||||
return ss.UnstructuredToLegacyFolder(ctx, out)
|
||||
}
|
||||
|
||||
func (ss *FolderUnifiedStoreImpl) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
@@ -206,7 +209,7 @@ func (ss *FolderUnifiedStoreImpl) GetParents(ctx context.Context, q folder.GetPa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
folder, err := internalfolders.UnstructuredToLegacyFolder(out)
|
||||
folder, err := ss.UnstructuredToLegacyFolder(ctx, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -245,9 +248,9 @@ func (ss *FolderUnifiedStoreImpl) GetChildren(ctx context.Context, q folder.GetC
|
||||
hits := make([]*folder.Folder, 0)
|
||||
for _, item := range out.Items {
|
||||
// convert item to legacy folder format
|
||||
f, err := internalfolders.UnstructuredToLegacyFolder(&item)
|
||||
f, err := ss.UnstructuredToLegacyFolder(ctx, &item)
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("unable covert unstructured item to legacy folder %w", err)
|
||||
return nil, fmt.Errorf("unable to convert unstructured item to legacy folder %w", err)
|
||||
}
|
||||
|
||||
// it we are at root level, skip subfolder
|
||||
@@ -345,9 +348,9 @@ func (ss *FolderUnifiedStoreImpl) GetFolders(ctx context.Context, q folder.GetFo
|
||||
m := map[string]*folder.Folder{}
|
||||
for _, item := range out.Items {
|
||||
// convert item to legacy folder format
|
||||
f, err := internalfolders.UnstructuredToLegacyFolder(&item)
|
||||
f, err := ss.UnstructuredToLegacyFolder(ctx, &item)
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("unable covert unstructured item to legacy folder %w", err)
|
||||
return nil, fmt.Errorf("unable to convert unstructured item to legacy folder %w", err)
|
||||
}
|
||||
|
||||
m[f.UID] = f
|
||||
@@ -405,9 +408,9 @@ func (ss *FolderUnifiedStoreImpl) GetDescendants(ctx context.Context, orgID int6
|
||||
nodes := map[string]*folder.Folder{}
|
||||
for _, item := range out.Items {
|
||||
// convert item to legacy folder format
|
||||
f, err := internalfolders.UnstructuredToLegacyFolder(&item)
|
||||
f, err := ss.UnstructuredToLegacyFolder(ctx, &item)
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("unable covert unstructured item to legacy folder %w", err)
|
||||
return nil, fmt.Errorf("unable to convert unstructured item to legacy folder %w", err)
|
||||
}
|
||||
|
||||
nodes[f.UID] = f
|
||||
|
||||
Reference in New Issue
Block a user