Authz: add support to use folder api to fetch folder tree (#100038)

* Add FolderStore interface

* Authz: add implementation to use folders api and use it inproc with loopback config

* Add tracing and add rest.Config for talking with folder api using access tokens

* Restructure test to get rid of circular dependencies in tests

* use correct group version kind

---------

Co-authored-by: gamab <gabriel.mabille@grafana.com>
This commit is contained in:
Karl Persson
2025-02-13 11:59:59 +01:00
committed by GitHub
co-authored by gamab
parent ae9837b793
commit 1b1954de28
14 changed files with 367 additions and 172 deletions
@@ -0,0 +1,158 @@
package store
import (
"context"
"fmt"
"github.com/grafana/authlib/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/pager"
"github.com/grafana/grafana/pkg/apimachinery/utils"
folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type FolderStore interface {
ListFolders(ctx context.Context, ns types.NamespaceInfo) ([]Folder, error)
}
type Folder struct {
UID string
ParentUID *string
}
var _ FolderStore = (*SQLFolderStore)(nil)
func NewSQLFolderStore(sql legacysql.LegacyDatabaseProvider, tracer tracing.Tracer) *SQLFolderStore {
return &SQLFolderStore{sql, tracer}
}
type SQLFolderStore struct {
sql legacysql.LegacyDatabaseProvider
tracer tracing.Tracer
}
var sqlFolders = mustTemplate("folder_query.sql")
type listFoldersQuery struct {
sqltemplate.SQLTemplate
Query *FolderQuery
FolderTable string
}
type FolderQuery struct {
OrgID int64
}
func (r listFoldersQuery) Validate() error {
return nil
}
func newListFolders(sql *legacysql.LegacyDatabaseHelper, query *FolderQuery) listFoldersQuery {
return listFoldersQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
Query: query,
FolderTable: sql.Table("folder"),
}
}
func (s *SQLFolderStore) ListFolders(ctx context.Context, ns types.NamespaceInfo) ([]Folder, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.database.ListFolders")
defer span.End()
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
query := newListFolders(sql, &FolderQuery{OrgID: ns.OrgID})
q, err := sqltemplate.Execute(sqlFolders, query)
if err != nil {
return nil, err
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, query.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
var folders []Folder
for rows.Next() {
var folder Folder
if err := rows.Scan(&folder.UID, &folder.ParentUID); err != nil {
return nil, err
}
folders = append(folders, folder)
}
return folders, nil
}
var _ FolderStore = (*APIFolderStore)(nil)
func NewAPIFolderStore(tracer tracing.Tracer, configProvider func(ctx context.Context) *rest.Config) *APIFolderStore {
return &APIFolderStore{tracer, configProvider}
}
type APIFolderStore struct {
tracer tracing.Tracer
configProvider func(ctx context.Context) *rest.Config
}
func (s *APIFolderStore) ListFolders(ctx context.Context, ns types.NamespaceInfo) ([]Folder, error) {
ctx, span := s.tracer.Start(ctx, "authz.apistore.ListFolders")
defer span.End()
client, err := s.client(ctx, ns.Value)
if err != nil {
return nil, fmt.Errorf("create resource client: %w", err)
}
p := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
return client.List(ctx, opts)
})
const defaultPageSize = 500
folders := make([]Folder, 0, defaultPageSize)
err = p.EachListItem(ctx, metav1.ListOptions{Limit: defaultPageSize}, func(obj runtime.Object) error {
object, err := utils.MetaAccessor(obj)
if err != nil {
return err
}
folder := Folder{UID: object.GetName()}
parent := object.GetFolder()
if parent != "" {
folder.ParentUID = &parent
}
folders = append(folders, folder)
return nil
})
if err != nil {
return nil, fmt.Errorf("fetching folders: %w", err)
}
return folders, nil
}
func (s *APIFolderStore) client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) {
client, err := dynamic.NewForConfig(s.configProvider(ctx))
if err != nil {
return nil, err
}
return client.Resource(folderv0alpha1.FolderResourceInfo.GroupVersionResource()).Namespace(namespace), nil
}
-18
View File
@@ -19,21 +19,3 @@ type UserIdentifierQuery struct {
UserID int64
UserUID string
}
type FolderQuery struct {
OrgID int64
}
type DashboardQuery struct {
OrgID int64
}
type Folder struct {
UID string
ParentUID *string
}
type Dashboard struct {
UID string
ParentUID *string
}
-1
View File
@@ -16,7 +16,6 @@ var (
sqlQueryBasicRoles = mustTemplate("basic_role_query.sql")
sqlUserIdentifiers = mustTemplate("user_identifier_query.sql")
sqlFolders = mustTemplate("folder_query.sql")
)
func mustTemplate(filename string) *template.Template {
-39
View File
@@ -15,7 +15,6 @@ import (
type Store interface {
GetUserIdentifiers(ctx context.Context, query UserIdentifierQuery) (*UserIdentifiers, error)
GetBasicRoles(ctx context.Context, ns claims.NamespaceInfo, query BasicRoleQuery) (*BasicRole, error)
GetFolders(ctx context.Context, ns claims.NamespaceInfo) ([]Folder, error)
}
type StoreImpl struct {
@@ -104,41 +103,3 @@ func (s *StoreImpl) GetBasicRoles(ctx context.Context, ns claims.NamespaceInfo,
return &role, nil
}
func (s *StoreImpl) GetFolders(ctx context.Context, ns claims.NamespaceInfo) ([]Folder, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.database.GetFolders")
defer span.End()
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
query := FolderQuery{OrgID: ns.OrgID}
req := newGetFolders(sql, &query)
q, err := sqltemplate.Execute(sqlFolders, req)
if err != nil {
return nil, err
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
var folders []Folder
for rows.Next() {
var folder Folder
if err := rows.Scan(&folder.UID, &folder.ParentUID); err != nil {
return nil, err
}
folders = append(folders, folder)
}
return folders, nil
}