K8s: Folders: Fix legacy search (#100393)

This commit is contained in:
Stephanie Hingtgen
2025-02-11 12:14:25 -07:00
committed by GitHub
parent ab74852fc9
commit df84d928e2
25 changed files with 416 additions and 661 deletions
+24 -9
View File
@@ -19,6 +19,7 @@ import (
"github.com/grafana/dskit/concurrency"
"github.com/grafana/grafana/pkg/apimachinery/identity"
dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
@@ -27,6 +28,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
@@ -42,7 +44,6 @@ import (
"github.com/grafana/grafana/pkg/services/supportbundles"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified"
"github.com/grafana/grafana/pkg/util"
)
@@ -57,7 +58,8 @@ type Service struct {
dashboardFolderStore folder.FolderStore
features featuremgmt.FeatureToggles
accessControl accesscontrol.AccessControl
k8sclient folderK8sHandler
k8sclient client.K8sHandler
dashboardK8sClient client.K8sHandler
publicDashboardService publicdashboards.ServiceWrapper
// bus is currently used to publish event in case of folder full path change.
// For example when a folder is moved to another folder or when a folder is renamed.
@@ -106,13 +108,14 @@ func ProvideService(
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
k8sHandler := &foldk8sHandler{
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
namespacer: request.GetNamespaceMapper(cfg),
cfg: cfg,
restConfigProvider: apiserver.GetRestConfig,
recourceClientProvider: unified.GetResourceClient,
}
k8sHandler := client.NewK8sHandler(
cfg,
request.GetNamespaceMapper(cfg),
v0alpha1.FolderResourceInfo.GroupVersionResource(),
apiserver.GetRestConfig,
dashboardStore,
userService,
)
unifiedStore := ProvideUnifiedStore(k8sHandler, userService)
@@ -120,6 +123,18 @@ func ProvideService(
srv.k8sclient = k8sHandler
}
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
dashHandler := client.NewK8sHandler(
cfg,
request.GetNamespaceMapper(cfg),
dashboardalpha1.DashboardResourceInfo.GroupVersionResource(),
apiserver.GetRestConfig,
dashboardStore,
userService,
)
srv.dashboardK8sClient = dashHandler
}
return srv
}
@@ -27,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/dashboards/database"
@@ -494,7 +495,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
})
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, nil, nil, nil, quotaService, nil, publicDashboardFakeService)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService)
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
@@ -580,7 +581,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff,
folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, nil, nil, nil, quotaService, nil, publicDashboardFakeService)
folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService)
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b)
@@ -723,7 +724,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
tc.service.store = nestedFolderStore
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, nil, nil, nil, quotaService, nil, publicDashboardFakeService)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService)
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
@@ -1510,8 +1511,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
serviceWithFlagOn,
nestedFolderStore,
nil,
nil,
nil,
client.MockTestRestConfig{},
nil,
quotaService,
nil,
@@ -10,20 +10,15 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/slices"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/client-go/dynamic"
clientrest "k8s.io/client-go/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
dashboardv0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
@@ -33,31 +28,12 @@ import (
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// interface to allow for testing
type folderK8sHandler interface {
getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
getNamespace(orgID int64) string
getSearcher(ctx context.Context) resource.ResourceClient
}
var _ folderK8sHandler = (*foldk8sHandler)(nil)
type foldk8sHandler struct {
cfg *setting.Cfg
namespacer request.NamespaceMapper
gvr schema.GroupVersionResource
restConfigProvider func(ctx context.Context) *clientrest.Config
recourceClientProvider func(ctx context.Context) resource.ResourceClient
}
func (s *Service) getFoldersFromApiServer(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
if q.SignedInUser == nil {
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
@@ -189,7 +165,7 @@ func (s *Service) searchFoldersFromApiServer(ctx context.Context, query folder.S
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Namespace: s.k8sclient.getNamespace(query.OrgID),
Namespace: s.k8sclient.GetNamespace(query.OrgID),
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
},
@@ -228,9 +204,7 @@ func (s *Service) searchFoldersFromApiServer(ctx context.Context, query folder.S
request.Limit = query.Limit
}
client := s.k8sclient.getSearcher(ctx)
res, err := client.Search(ctx, request)
res, err := s.k8sclient.Search(ctx, query.OrgID, request)
if err != nil {
return nil, err
}
@@ -279,7 +253,7 @@ func (s *Service) getFolderByIDFromApiServer(ctx context.Context, id int64, orgI
}
folderkey := &resource.ResourceKey{
Namespace: s.k8sclient.getNamespace(orgID),
Namespace: s.k8sclient.GetNamespace(orgID),
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
}
@@ -298,9 +272,7 @@ func (s *Service) getFolderByIDFromApiServer(ctx context.Context, id int64, orgI
},
Limit: 100000}
client := s.k8sclient.getSearcher(ctx)
res, err := client.Search(ctx, request)
res, err := s.k8sclient.Search(ctx, orgID, request)
if err != nil {
return nil, err
}
@@ -334,7 +306,7 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64
}
folderkey := &resource.ResourceKey{
Namespace: s.k8sclient.getNamespace(orgID),
Namespace: s.k8sclient.GetNamespace(orgID),
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
}
@@ -362,9 +334,7 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64
request.Options.Fields = append(request.Options.Fields, req...)
}
client := s.k8sclient.getSearcher(ctx)
res, err := client.Search(ctx, request)
res, err := s.k8sclient.Search(ctx, orgID, request)
if err != nil {
return nil, err
}
@@ -696,14 +666,8 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
// we cannot use the dashboard service directly due to circular dependencies,
// so either use the search client if the feature is enabled or use the dashboard store
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
dashboardKey := &resource.ResourceKey{
Namespace: s.k8sclient.getNamespace(cmd.OrgID),
Group: dashboardv0.DashboardResourceInfo.GroupVersionResource().Group,
Resource: dashboardv0.DashboardResourceInfo.GroupVersionResource().Resource,
}
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: dashboardKey,
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
@@ -715,8 +679,7 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
},
Limit: 100000}
client := s.k8sclient.getSearcher(ctx)
res, err := client.Search(ctx, request)
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
@@ -726,13 +689,9 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs = make([]string, len(hits.Hits))
k8sDeleteClient, created := s.k8sclient.getDashboardClient(ctx, cmd.OrgID)
if !created {
return folder.ErrInternal.Errorf("failed to create client to get dashboards")
}
for i, dashboard := range hits.Hits {
dashboardUIDs[i] = dashboard.Name
err = k8sDeleteClient.Delete(ctx, dashboard.Name, metav1.DeleteOptions{})
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
@@ -989,43 +948,3 @@ func (s *Service) getDescendantCountsFromApiServer(ctx context.Context, q *folde
}
return countsMap, nil
}
// -----------------------------------------------------------------------------------------
// Folder k8s functions
// -----------------------------------------------------------------------------------------
func (fk8s *foldk8sHandler) getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
cfg := fk8s.restConfigProvider(ctx)
if cfg == nil {
return nil, false
}
dyn, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, false
}
return dyn.Resource(fk8s.gvr).Namespace(fk8s.getNamespace(orgID)), true
}
func (fk8s *foldk8sHandler) getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
cfg := fk8s.restConfigProvider(ctx)
if cfg == nil {
return nil, false
}
dyn, err := dynamic.NewForConfig(cfg)
if err != nil {
return nil, false
}
return dyn.Resource(dashboardv0.DashboardResourceInfo.GroupVersionResource()).Namespace(fk8s.getNamespace(orgID)), true
}
func (fk8s *foldk8sHandler) getNamespace(orgID int64) string {
return fk8s.namespacer(orgID)
}
func (fk8s *foldk8sHandler) getSearcher(ctx context.Context) resource.ResourceClient {
return fk8s.recourceClientProvider(ctx)
}
@@ -12,9 +12,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/apimachinery/pkg/selection"
clientrest "k8s.io/client-go/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@@ -28,8 +26,10 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
@@ -173,23 +173,17 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
Host: folderApiServerMock.URL,
}
f := func(ctx context.Context) resource.ResourceClient {
return resourceClientMock{}
}
k8sHandler := &foldk8sHandler{
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
namespacer: request.GetNamespaceMapper(cfg),
cfg: cfg,
restConfigProvider: restCfgProvider.GetRestConfig,
recourceClientProvider: f,
}
userService := &usertest.FakeUserService{
ExpectedUser: &user.User{},
}
unifiedStore := ProvideUnifiedStore(k8sHandler, userService)
featuresArr := []any{
featuremgmt.FlagKubernetesFoldersServiceV2}
features := featuremgmt.WithFeatures(featuresArr...)
dashboardStore := dashboards.NewFakeDashboardStore(t)
k8sCli := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), v0alpha1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService)
unifiedStore := ProvideUnifiedStore(k8sCli, userService)
ctx := context.Background()
usr := &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{
@@ -209,10 +203,6 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
}
featuresArr := []any{
featuremgmt.FlagKubernetesFoldersServiceV2}
features := featuremgmt.WithFeatures(featuresArr...)
dashboardStore := dashboards.NewFakeDashboardStore(t)
publicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
folderService := &Service{
@@ -224,7 +214,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
registry: make(map[string]folder.RegistryService),
metrics: newFoldersMetrics(nil),
tracer: tracing.InitializeTracerForTest(),
k8sclient: k8sHandler,
k8sclient: k8sCli,
dashboardStore: dashboardStore,
publicDashboardService: publicDashboardService,
}
@@ -341,7 +331,6 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
NewTitle: &title,
SignedInUser: usr,
}
reqResult, err := folderService.Update(ctx, req)
require.NoError(t, err)
require.Equal(t, title, reqResult.Title)
@@ -358,7 +347,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error - ForceDeleteRules false", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil)
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -428,6 +417,13 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When get folder by ID and uid is an empty string should return folder by id", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
IsFolder: true,
ID: fooFolder.ID, // nolint:staticcheck
UID: fooFolder.UID,
},
}, nil).Once()
id := int64(123)
emptyString := ""
query := &folder.GetFolderQuery{
@@ -443,6 +439,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When get folder by non existing ID should return not found error", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
id := int64(111111)
query := &folder.GetFolderQuery{
ID: &id,
@@ -456,6 +453,13 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When get folder by Title should return folder", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
IsFolder: true,
ID: fooFolder.ID, // nolint:staticcheck
UID: fooFolder.UID,
},
}, nil).Once()
title := "foo"
query := &folder.GetFolderQuery{
Title: &title,
@@ -469,6 +473,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When get folder by non existing Title should return not found error", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
title := "does not exists"
query := &folder.GetFolderQuery{
Title: &title,
@@ -506,287 +511,81 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
}
type resourceClientMock struct{}
func (r resourceClientMock) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) {
return nil, nil
}
func (r resourceClientMock) Create(ctx context.Context, in *resource.CreateRequest, opts ...grpc.CallOption) (*resource.CreateResponse, error) {
return nil, nil
}
func (r resourceClientMock) Update(ctx context.Context, in *resource.UpdateRequest, opts ...grpc.CallOption) (*resource.UpdateResponse, error) {
return nil, nil
}
func (r resourceClientMock) Delete(ctx context.Context, in *resource.DeleteRequest, opts ...grpc.CallOption) (*resource.DeleteResponse, error) {
return nil, nil
}
func (r resourceClientMock) Restore(ctx context.Context, in *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) {
return nil, nil
}
func (r resourceClientMock) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
return nil, nil
}
func (r resourceClientMock) Watch(ctx context.Context, in *resource.WatchRequest, opts ...grpc.CallOption) (resource.ResourceStore_WatchClient, error) {
return nil, nil
}
func (r resourceClientMock) BatchProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BatchStore_BatchProcessClient, error) {
return nil, nil
}
func (r resourceClientMock) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
if len(in.Options.Labels) > 0 &&
in.Options.Labels[0].Key == utils.LabelKeyDeprecatedInternalID &&
in.Options.Labels[0].Operator == "in" &&
len(in.Options.Labels[0].Values) > 0 &&
in.Options.Labels[0].Values[0] == "123" {
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "_id",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "foo",
Resource: "folders",
},
Cells: [][]byte{
[]byte("123"),
[]byte("folder1"),
[]byte(""),
},
},
},
},
TotalHits: 1,
}, nil
}
if len(in.Options.Fields) > 0 &&
in.Options.Fields[0].Key == resource.SEARCH_FIELD_TITLE_PHRASE &&
in.Options.Fields[0].Operator == "in" &&
len(in.Options.Fields[0].Values) > 0 &&
in.Options.Fields[0].Values[0] == "foo" {
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "_id",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "foo",
Resource: "folders",
},
Cells: [][]byte{
[]byte("123"),
[]byte("folder1"),
[]byte(""),
},
},
},
},
TotalHits: 1,
}, nil
}
if in.Query == "*test*" {
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "_id",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid",
Resource: "folders",
},
Cells: [][]byte{
[]byte("123"),
[]byte("testing-123"),
[]byte("parent-uid"),
},
},
},
},
TotalHits: 1,
}, nil
}
if len(in.Options.Fields) > 0 &&
in.Options.Fields[0].Key == resource.SEARCH_FIELD_NAME &&
in.Options.Fields[0].Operator == "in" &&
len(in.Options.Fields[0].Values) > 0 {
rows := []*resource.ResourceTableRow{}
for i, row := range in.Options.Fields[0].Values {
rows = append(rows, &resource.ResourceTableRow{
Key: &resource.ResourceKey{
Name: row,
Resource: "folders",
},
Cells: [][]byte{
[]byte(fmt.Sprintf("%d", i)), // set legacy id as the row id
[]byte(fmt.Sprintf("folder%d", i)), // set title as folder + row id
[]byte(""),
},
})
}
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "_id",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: rows,
},
TotalHits: int64(len(rows)),
}, nil
}
if len(in.Options.Fields) > 0 &&
in.Options.Fields[0].Key == resource.SEARCH_FIELD_FOLDER &&
in.Options.Fields[0].Operator == "in" &&
len(in.Options.Fields[0].Values) > 0 {
rows := []*resource.ResourceTableRow{}
for i, row := range in.Options.Fields[0].Values {
rows = append(rows, &resource.ResourceTableRow{
Key: &resource.ResourceKey{
Name: row,
Resource: "folders",
},
Cells: [][]byte{
[]byte(fmt.Sprintf("%d", i)), // set legacy id as the row id
[]byte(fmt.Sprintf("folder%d", i)), // set title as folder + row id
[]byte(""),
},
})
}
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "_id",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: rows,
},
TotalHits: int64(len(rows)),
}, nil
}
// not found
return &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
}, nil
}
func (r resourceClientMock) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
return nil, nil
}
func (r resourceClientMock) CountRepositoryObjects(ctx context.Context, in *resource.CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.CountRepositoryObjectsResponse, error) {
return nil, nil
}
func (r resourceClientMock) ListRepositoryObjects(ctx context.Context, in *resource.ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.ListRepositoryObjectsResponse, error) {
return nil, nil
}
func (r resourceClientMock) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
return nil, nil
}
func (r resourceClientMock) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) {
return nil, nil
}
func (r resourceClientMock) IsHealthy(ctx context.Context, in *resource.HealthCheckRequest, opts ...grpc.CallOption) (*resource.HealthCheckResponse, error) {
return nil, nil
}
type mockFoldersK8sCli struct {
mock.Mock
searcher resourceClientMock
}
func (m *mockFoldersK8sCli) getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
args := m.Called(ctx, orgID)
return args.Get(0).(dynamic.ResourceInterface), args.Bool(1)
}
func (m *mockFoldersK8sCli) getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
args := m.Called(ctx, orgID)
return args.Get(0).(dynamic.ResourceInterface), args.Bool(1)
}
func (m *mockFoldersK8sCli) getNamespace(orgID int64) string {
if orgID == 1 {
return "default"
}
return fmt.Sprintf("orgs-%d", orgID)
}
func (m *mockFoldersK8sCli) getSearcher(ctx context.Context) resource.ResourceClient {
return m.searcher
}
func TestSearchFoldersFromApiServer(t *testing.T) {
fakeK8sClient := new(mockFoldersK8sCli)
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
fakeK8sClient := new(client.MockK8sHandler)
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
})
folderStore := folder.NewFakeStore()
folderStore.ExpectedFolder = &folder.Folder{
UID: "parent-uid",
ID: 2,
Title: "parent title",
}
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
unifiedStore: folderStore,
}
fakeK8sClient.On("getSearcher", mock.Anything).Return(fakeK8sClient)
user := &user.SignedInUser{OrgID: 1}
ctx := identity.WithRequester(context.Background(), user)
fakeK8sClient.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
t.Run("Should search by uids if provided", func(t *testing.T) {
t.Run("Should call search with uids, if provided", func(t *testing.T) {
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Namespace: "default",
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_NAME,
Operator: string(selection.In),
Values: []string{"uid1", "uid2"}, // should only search by uid since it is provided
},
},
Labels: []*resource.Requirement{},
},
Limit: 100000}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid1",
Resource: "folder",
},
Cells: [][]byte{
[]byte("folder0"),
[]byte(""),
},
},
{
Key: &resource.ResourceKey{
Name: "uid2",
Resource: "folder",
},
Cells: [][]byte{
[]byte("folder1"),
[]byte(""),
},
},
},
},
TotalHits: 2,
}, nil).Once()
query := folder.SearchFoldersQuery{
UIDs: []string{"uid1", "uid2"},
IDs: []int64{1, 2}, // will ignore these because uid is passed in
@@ -821,16 +620,60 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
},
}
require.Equal(t, expectedResult, result)
fakeK8sClient.AssertExpectations(t)
})
t.Run("Search by ID if uids are not provided", func(t *testing.T) {
t.Run("Should call search by ID if uids are not provided", func(t *testing.T) {
query := folder.SearchFoldersQuery{
IDs: []int64{123},
SignedInUser: user,
}
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Namespace: "default",
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{
{
Key: utils.LabelKeyDeprecatedInternalID,
Operator: string(selection.In),
Values: []string{"123"},
},
},
},
Limit: 100000}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "foo",
Resource: "folder",
},
Cells: [][]byte{
[]byte("folder1"),
[]byte(""),
},
},
},
},
TotalHits: 1,
}, nil).Once()
result, err := service.searchFoldersFromApiServer(ctx, query)
require.NoError(t, err)
expectedResult := model.HitList{
{
UID: "foo",
@@ -844,6 +687,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
},
}
require.Equal(t, expectedResult, result)
fakeK8sClient.AssertExpectations(t)
})
t.Run("Search by title, wildcard should be added to search request (won't match in search mock if not)", func(t *testing.T) {
@@ -855,10 +699,45 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
Title: "parent title",
}
service.unifiedStore = fakeFolderStore
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanSaveValue: true,
CanViewValue: true,
})
fakeK8sClient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Namespace: "default",
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
},
Fields: []*resource.Requirement{},
Labels: []*resource.Requirement{},
},
Query: "*test*",
Fields: dashboardsearch.IncludeFields,
Limit: 100000}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid",
Resource: "folder",
},
Cells: [][]byte{
[]byte("testing-123"),
[]byte("parent-uid"),
},
},
},
},
TotalHits: 1,
}, nil).Once()
query := folder.SearchFoldersQuery{
Title: "test",
@@ -881,33 +760,26 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
},
}
require.Equal(t, expectedResult, result)
fakeK8sClient.AssertExpectations(t)
})
}
type mockDashboardCli struct {
mock.Mock
dynamic.ResourceInterface
}
func (c *mockDashboardCli) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error {
args := c.Called(ctx, name, options)
return args.Error(0)
}
func TestDeleteFoldersFromApiServer(t *testing.T) {
fakeK8sClient := new(mockFoldersK8sCli)
fakeK8sClient := new(client.MockK8sHandler)
fakeK8sClient.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
dashboardK8sclient := new(client.MockK8sHandler)
fakeFolderStore := folder.NewFakeStore()
dashboardStore := dashboards.NewFakeDashboardStore(t)
publicDashboardFakeService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
service := Service{
k8sclient: fakeK8sClient,
dashboardK8sClient: dashboardK8sclient,
unifiedStore: fakeFolderStore,
dashboardStore: dashboardStore,
publicDashboardService: publicDashboardFakeService,
registry: make(map[string]folder.RegistryService),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
}
fakeK8sClient.On("getSearcher", mock.Anything).Return(fakeK8sClient)
user := &user.SignedInUser{OrgID: 1}
ctx := identity.WithRequester(context.Background(), user)
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
@@ -969,19 +841,53 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2, featuremgmt.FlagKubernetesCliDashboards)
t.Run("Should delete dashboards and public dashboards within the folder through k8s if the ff is enabled", func(t *testing.T) {
dashboardK8sCli := mockDashboardCli{}
dashboardK8sCli.On("Delete", mock.Anything, "uid1", mock.Anything, mock.Anything).Return(nil).Once()
fakeK8sClient.On("getDashboardClient", mock.Anything, mock.Anything).Return(&dashboardK8sCli, true)
fakeK8sClient.On("getSearcher", mock.Anything).Return(fakeK8sClient)
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"uid1"}).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "uid1", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: []string{"uid1"},
},
},
},
Limit: 100000}).Return(&resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
{
Name: "title",
Type: resource.ResourceTableColumnDefinition_STRING,
},
{
Name: "folder",
Type: resource.ResourceTableColumnDefinition_STRING,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid1",
Resource: "folder",
},
Cells: [][]byte{
[]byte("folder1"),
[]byte(""),
},
},
},
},
TotalHits: 1,
}, nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid1",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
dashboardStore.AssertExpectations(t)
publicDashboardFakeService.AssertExpectations(t)
dashboardK8sCli.AssertExpectations(t)
dashboardK8sclient.AssertExpectations(t)
})
}
+13 -170
View File
@@ -4,20 +4,17 @@ import (
"context"
"fmt"
"strings"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sUser "k8s.io/apiserver/pkg/authentication/user"
k8sRequest "k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/log"
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/user"
@@ -26,14 +23,14 @@ import (
type FolderUnifiedStoreImpl struct {
log log.Logger
k8sclient folderK8sHandler
k8sclient client.K8sHandler
userService user.Service
}
// sqlStore implements the store interface.
var _ folder.Store = (*FolderUnifiedStoreImpl)(nil)
func ProvideUnifiedStore(k8sHandler *foldk8sHandler, userService user.Service) *FolderUnifiedStoreImpl {
func ProvideUnifiedStore(k8sHandler client.K8sHandler, userService user.Service) *FolderUnifiedStoreImpl {
return &FolderUnifiedStoreImpl{
k8sclient: k8sHandler,
log: log.New("folder-store"),
@@ -42,23 +39,11 @@ func ProvideUnifiedStore(k8sHandler *foldk8sHandler, userService user.Service) *
}
func (ss *FolderUnifiedStoreImpl) Create(ctx context.Context, cmd folder.CreateFolderCommand) (*folder.Folder, error) {
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, cmd.OrgID)
if !ok {
return nil, nil
}
obj, err := internalfolders.LegacyCreateCommandToUnstructured(&cmd)
if err != nil {
return nil, err
}
out, err := client.Create(newCtx, obj, v1.CreateOptions{})
out, err := ss.k8sclient.Create(ctx, obj, cmd.OrgID)
if err != nil {
return nil, err
}
@@ -72,20 +57,8 @@ func (ss *FolderUnifiedStoreImpl) Create(ctx context.Context, cmd folder.CreateF
}
func (ss *FolderUnifiedStoreImpl) Delete(ctx context.Context, UIDs []string, orgID int64) error {
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, orgID)
if !ok {
return nil
}
for _, uid := range UIDs {
err = client.Delete(newCtx, uid, v1.DeleteOptions{})
err := ss.k8sclient.Delete(ctx, uid, orgID, v1.DeleteOptions{})
if err != nil {
return err
}
@@ -95,19 +68,7 @@ func (ss *FolderUnifiedStoreImpl) Delete(ctx context.Context, UIDs []string, org
}
func (ss *FolderUnifiedStoreImpl) Update(ctx context.Context, cmd folder.UpdateFolderCommand) (*folder.Folder, error) {
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, cmd.OrgID)
if !ok {
return nil, nil
}
obj, err := client.Get(ctx, cmd.UID, v1.GetOptions{})
obj, err := ss.k8sclient.Get(ctx, cmd.UID, cmd.OrgID, v1.GetOptions{})
if err != nil {
return nil, err
}
@@ -133,7 +94,7 @@ func (ss *FolderUnifiedStoreImpl) Update(ctx context.Context, cmd folder.UpdateF
meta.SetFolder(*cmd.NewParentUID)
}
out, err := client.Update(ctx, updated, v1.UpdateOptions{})
out, err := ss.k8sclient.Update(ctx, updated, cmd.OrgID)
if err != nil {
return nil, err
}
@@ -160,21 +121,7 @@ func (ss *FolderUnifiedStoreImpl) Update(ctx context.Context, cmd folder.UpdateF
//
// The full path of C is "A/B\/C".
func (ss *FolderUnifiedStoreImpl) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.Folder, error) {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, q.OrgID)
if !ok {
return nil, nil
}
out, err := client.Get(newCtx, *q.UID, v1.GetOptions{})
out, err := ss.k8sclient.Get(ctx, *q.UID, q.OrgID, v1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
} else if err != nil || out == nil {
@@ -185,26 +132,12 @@ func (ss *FolderUnifiedStoreImpl) Get(ctx context.Context, q folder.GetFolderQue
}
func (ss *FolderUnifiedStoreImpl) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, q.OrgID)
if !ok {
return nil, nil
}
hits := []*folder.Folder{}
parentUid := q.UID
for parentUid != "" {
out, err := client.Get(newCtx, parentUid, v1.GetOptions{})
out, err := ss.k8sclient.Get(ctx, parentUid, q.OrgID, v1.GetOptions{})
if err != nil {
return nil, err
}
@@ -226,21 +159,7 @@ func (ss *FolderUnifiedStoreImpl) GetParents(ctx context.Context, q folder.GetPa
}
func (ss *FolderUnifiedStoreImpl) GetChildren(ctx context.Context, q folder.GetChildrenQuery) ([]*folder.Folder, error) {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, q.OrgID)
if !ok {
return nil, nil
}
out, err := client.List(newCtx, v1.ListOptions{})
out, err := ss.k8sclient.List(ctx, q.OrgID, v1.ListOptions{})
if err != nil {
return nil, err
}
@@ -328,19 +247,7 @@ func (ss *FolderUnifiedStoreImpl) GetHeight(ctx context.Context, foldrUID string
// The full path UIDs of B is "uid1/uid2".
// The full path UIDs of A is "uid1".
func (ss *FolderUnifiedStoreImpl) GetFolders(ctx context.Context, q folder.GetFoldersFromStoreQuery) ([]*folder.Folder, error) {
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, q.OrgID)
if !ok {
return nil, nil
}
out, err := client.List(newCtx, v1.ListOptions{})
out, err := ss.k8sclient.List(ctx, q.OrgID, v1.ListOptions{})
if err != nil {
return nil, err
}
@@ -394,21 +301,7 @@ func (ss *FolderUnifiedStoreImpl) GetFolders(ctx context.Context, q folder.GetFo
}
func (ss *FolderUnifiedStoreImpl) GetDescendants(ctx context.Context, orgID int64, ancestor_uid string) ([]*folder.Folder, error) {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, orgID)
if !ok {
return nil, nil
}
out, err := client.List(newCtx, v1.ListOptions{})
out, err := ss.k8sclient.List(ctx, orgID, v1.ListOptions{})
if err != nil {
return nil, err
}
@@ -458,21 +351,7 @@ func getDescendants(nodes map[string]*folder.Folder, tree map[string]map[string]
}
func (ss *FolderUnifiedStoreImpl) CountFolderContent(ctx context.Context, orgID int64, ancestor_uid string) (folder.DescendantCounts, error) {
// create a new context - prevents issues when the request stems from the k8s api itself
// otherwise the context goes through the handlers twice and causes issues
newCtx, cancel, err := ss.getK8sContext(ctx)
if err != nil {
return nil, err
} else if cancel != nil {
defer cancel()
}
client, ok := ss.k8sclient.getClient(newCtx, orgID)
if !ok {
return nil, nil
}
counts, err := client.Get(newCtx, ancestor_uid, v1.GetOptions{}, "counts")
counts, err := ss.k8sclient.Get(ctx, ancestor_uid, orgID, v1.GetOptions{}, "counts")
if err != nil {
return nil, err
}
@@ -502,42 +381,6 @@ func toFolderLegacyCounts(u *unstructured.Unstructured) (*folder.DescendantCount
return &out, nil
}
func (ss *FolderUnifiedStoreImpl) getK8sContext(ctx context.Context) (context.Context, context.CancelFunc, error) {
requester, requesterErr := identity.GetRequester(ctx)
if requesterErr != nil {
return nil, nil, requesterErr
}
user, exists := k8sRequest.UserFrom(ctx)
if !exists {
// add in k8s user if not there yet
var ok bool
user, ok = requester.(k8sUser.Info)
if !ok {
return nil, nil, fmt.Errorf("could not convert user to k8s user")
}
}
newCtx := k8sRequest.WithUser(context.Background(), user)
newCtx = log.WithContextualAttributes(newCtx, log.FromContext(ctx))
// TODO: after GLSA token workflow is removed, make this return early
// and move the else below to be unconditional
if requesterErr == nil {
newCtxWithRequester := identity.WithRequester(newCtx, requester)
newCtx = newCtxWithRequester
}
// inherit the deadline from the original context, if it exists
deadline, ok := ctx.Deadline()
if ok {
var newCancel context.CancelFunc
newCtx, newCancel = context.WithTimeout(newCtx, time.Until(deadline))
return newCtx, newCancel, nil
}
return newCtx, nil, nil
}
func computeFullPath(parents []*folder.Folder) (string, string) {
fullpath := make([]string, len(parents))
fullpathUIDs := make([]string, len(parents))