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