mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Folders: Fix legacy search (#100393)
This commit is contained in:
parent
ab74852fc9
commit
df84d928e2
@ -30,6 +30,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
@ -835,7 +836,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
if dashboardService == nil {
|
||||
dashboardService, err = service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, folderStore, features, folderPermissions,
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotaService, nil, nil,
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
dashboardService.(dashboards.PermissionsRegistrationService).RegisterDashboardPermissions(dashboardPermissions)
|
||||
@ -843,7 +844,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
|
||||
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, folderStore, features, folderPermissions,
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotaService, nil, nil,
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -472,7 +473,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
sc.cfg, dashStore, folderStore,
|
||||
features, folderPermissions, ac,
|
||||
folderServiceWithFlagOn, fStore, nil, nil, nil, nil, quotaSrv, nil, nil,
|
||||
folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil,
|
||||
)
|
||||
require.NoError(b, err)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -9,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@ -32,12 +34,12 @@ import (
|
||||
// The DTO returns everything the UI needs in a single request
|
||||
type SearchHandler struct {
|
||||
log log.Logger
|
||||
client resource.ResourceIndexClient
|
||||
client func(context.Context) resource.ResourceIndexClient
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
func NewSearchHandler(client resource.ResourceIndexClient, tracer trace.Tracer, cfg *setting.Cfg, legacyDashboardSearcher resource.ResourceIndexClient) *SearchHandler {
|
||||
searchClient := resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, client, legacyDashboardSearcher)
|
||||
func NewSearchHandler(tracer trace.Tracer, cfg *setting.Cfg, legacyDashboardSearcher resource.ResourceIndexClient) *SearchHandler {
|
||||
searchClient := resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, unified.GetResourceClient, legacyDashboardSearcher)
|
||||
return &SearchHandler{
|
||||
client: searchClient,
|
||||
log: log.New("grafana-apiserver.dashboards.search"),
|
||||
@ -339,7 +341,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
||||
searchRequest.Options.Fields = append(searchRequest.Options.Fields, namesFilter...)
|
||||
}
|
||||
|
||||
result, err := s.client.Search(ctx, searchRequest)
|
||||
result, err := s.client(ctx).Search(ctx, searchRequest)
|
||||
if err != nil {
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
func TestSearchFallback(t *testing.T) {
|
||||
t.Run("should hit legacy search handler on mode 0", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -30,7 +31,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode0},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -49,6 +51,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
|
||||
t.Run("should hit legacy search handler on mode 1", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -56,7 +59,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode1},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -75,6 +79,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
|
||||
t.Run("should hit legacy search handler on mode 2", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -82,7 +87,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode2},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -101,6 +107,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
|
||||
t.Run("should hit unified storage search handler on mode 3", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -108,7 +115,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode3},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -127,6 +135,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
|
||||
t.Run("should hit unified storage search handler on mode 4", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -134,7 +143,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode4},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -153,6 +163,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
|
||||
t.Run("should hit unified storage search handler on mode 5", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
mockUnifiedCtxclient := func(context.Context) resource.ResourceClient { return mockClient }
|
||||
mockLegacyClient := &MockClient{}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@ -160,7 +171,8 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode5},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(mockClient, tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
@ -185,7 +197,7 @@ func TestSearchHandler(t *testing.T) {
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: mockClient,
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
}
|
||||
|
||||
@ -271,6 +283,7 @@ func TestSearchHandler(t *testing.T) {
|
||||
// MockClient implements the ResourceIndexClient interface for testing
|
||||
type MockClient struct {
|
||||
resource.ResourceIndexClient
|
||||
resource.ResourceIndex
|
||||
|
||||
// Capture the last SearchRequest for assertions
|
||||
LastSearchRequest *resource.ResourceSearchRequest
|
||||
@ -330,7 +343,45 @@ func (m *MockClient) Search(ctx context.Context, in *resource.ResourceSearchRequ
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) CountRepositoryObjects(ctx context.Context, in *resource.CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.CountRepositoryObjectsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Watch(ctx context.Context, in *resource.WatchRequest, opts ...grpc.CallOption) (resource.ResourceStore_WatchClient, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Delete(ctx context.Context, in *resource.DeleteRequest, opts ...grpc.CallOption) (*resource.DeleteResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Create(ctx context.Context, in *resource.CreateRequest, opts ...grpc.CallOption) (*resource.CreateResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Update(ctx context.Context, in *resource.UpdateRequest, opts ...grpc.CallOption) (*resource.UpdateResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) Restore(ctx context.Context, in *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) ListRepositoryObjects(ctx context.Context, in *resource.ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.ListRepositoryObjectsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) IsHealthy(ctx context.Context, in *resource.HealthCheckRequest, opts ...grpc.CallOption) (*resource.HealthCheckResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockClient) BatchProcess(ctx context.Context, opts ...grpc.CallOption) (resource.BatchStore_BatchProcessClient, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
features: features,
|
||||
accessControl: accessControl,
|
||||
unified: unified,
|
||||
search: dashboard.NewSearchHandler(unified, tracing, cfg, legacyDashboardSearcher),
|
||||
search: dashboard.NewSearchHandler(tracing, cfg, legacyDashboardSearcher),
|
||||
|
||||
legacy: &dashboard.DashboardStorage{
|
||||
Resource: dashboardv0alpha1.DashboardResourceInfo,
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardsservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -51,7 +52,7 @@ func TestIntegrationAuthorize(t *testing.T) {
|
||||
fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest())
|
||||
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(),
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotatest.New(false, nil), nil, nil)
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil)
|
||||
require.NoError(t, err)
|
||||
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardsservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -63,7 +64,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest())
|
||||
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(),
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotatest.New(false, nil), nil, nil)
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil)
|
||||
require.NoError(t, err)
|
||||
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
|
||||
repo := ProvideService(sql, cfg, features, tagService, tracing.InitializeTracerForTest(), ruleStore, dashSvc)
|
||||
@ -246,7 +247,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest())
|
||||
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(),
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotatest.New(false, nil), nil, nil)
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil)
|
||||
require.NoError(t, err)
|
||||
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
|
||||
cfg.AnnotationMaximumTagsLength = 60
|
||||
|
@ -12,15 +12,16 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"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/storage/unified/resource"
|
||||
k8sUser "k8s.io/apiserver/pkg/authentication/user"
|
||||
k8sRequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
@ -44,18 +45,21 @@ var _ K8sHandler = (*k8sHandler)(nil)
|
||||
type k8sHandler struct {
|
||||
namespacer request.NamespaceMapper
|
||||
gvr schema.GroupVersionResource
|
||||
restConfigProvider apiserver.RestConfigProvider
|
||||
searcher resource.ResourceIndexClient
|
||||
restConfig func(context.Context) *rest.Config
|
||||
searcher func(context.Context) resource.ResourceIndexClient
|
||||
userService user.Service
|
||||
}
|
||||
|
||||
func NewK8sHandler(cfg *setting.Cfg, namespacer request.NamespaceMapper, gvr schema.GroupVersionResource, restConfigProvider apiserver.RestConfigProvider, searcher resource.ResourceIndexClient, dashStore dashboards.Store, userSvc user.Service) K8sHandler {
|
||||
func NewK8sHandler(cfg *setting.Cfg, namespacer request.NamespaceMapper, gvr schema.GroupVersionResource,
|
||||
restConfig func(context.Context) *rest.Config, dashStore dashboards.Store, userSvc user.Service) K8sHandler {
|
||||
legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore)
|
||||
searchClient := resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, searcher, legacySearcher)
|
||||
key := gvr.Resource + "." + gvr.Group // the unified storage key in the config.ini is resource + group
|
||||
searchClient := resource.NewSearchClient(cfg, key, unified.GetResourceClient, legacySearcher)
|
||||
|
||||
return &k8sHandler{
|
||||
namespacer: namespacer,
|
||||
gvr: gvr,
|
||||
restConfigProvider: restConfigProvider,
|
||||
restConfig: restConfig,
|
||||
searcher: searchClient,
|
||||
userService: userSvc,
|
||||
}
|
||||
@ -187,12 +191,12 @@ func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resource.Resou
|
||||
}
|
||||
}
|
||||
|
||||
return h.searcher.Search(ctx, in)
|
||||
return h.searcher(ctx).Search(ctx, in)
|
||||
}
|
||||
|
||||
func (h *k8sHandler) GetStats(ctx context.Context, orgID int64) (*resource.ResourceStatsResponse, error) {
|
||||
// goes directly through grpc, so doesn't need the new context
|
||||
return h.searcher.GetStats(ctx, &resource.ResourceStatsRequest{
|
||||
return h.searcher(ctx).GetStats(ctx, &resource.ResourceStatsRequest{
|
||||
Namespace: h.GetNamespace(orgID),
|
||||
Kinds: []string{
|
||||
h.gvr.Group + "/" + h.gvr.Resource,
|
||||
@ -223,7 +227,7 @@ func (h *k8sHandler) GetUserFromMeta(ctx context.Context, userMeta string) (*use
|
||||
}
|
||||
|
||||
func (h *k8sHandler) getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
|
||||
cfg := h.restConfigProvider.GetRestConfig(ctx)
|
||||
cfg := h.restConfig(ctx)
|
||||
if cfg == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var _ K8sHandler = (*MockK8sHandler)(nil)
|
||||
@ -88,3 +89,11 @@ func (m *MockK8sHandler) GetUserFromMeta(ctx context.Context, userMeta string) (
|
||||
}
|
||||
return args.Get(0).(*user.User), args.Error(1)
|
||||
}
|
||||
|
||||
type MockTestRestConfig struct {
|
||||
cfg *rest.Config
|
||||
}
|
||||
|
||||
func (r MockTestRestConfig) GetRestConfig(ctx context.Context) *rest.Config {
|
||||
return r.cfg
|
||||
}
|
||||
|
@ -93,10 +93,10 @@ func ProvideDashboardServiceImpl(
|
||||
cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore,
|
||||
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||
ac accesscontrol.AccessControl, folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer,
|
||||
restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient,
|
||||
restConfigProvider apiserver.RestConfigProvider, userService user.Service,
|
||||
quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper,
|
||||
) (*DashboardServiceImpl, error) {
|
||||
k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider, unified, dashboardStore, userService)
|
||||
k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService)
|
||||
|
||||
dashSvc := &DashboardServiceImpl{
|
||||
cfg: cfg,
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
accesscontrolmock "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/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@ -904,8 +905,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
|
||||
folderService,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
@ -993,8 +993,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
|
||||
folderService,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
@ -1040,8 +1039,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
|
||||
folderService,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
@ -1106,8 +1104,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string
|
||||
folderService,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
@ -1179,8 +1176,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
|
||||
folderService,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -110,8 +111,7 @@ func TestValidateDashboardExists(t *testing.T) {
|
||||
foldertest.NewFakeService(),
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotatest.New(false, nil),
|
||||
nil,
|
||||
|
@ -52,8 +52,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.Dash
|
||||
cfg,
|
||||
request.GetNamespaceMapper(cfg),
|
||||
v0alpha1.DashboardResourceInfo.GroupVersionResource(),
|
||||
restConfigProvider,
|
||||
unified,
|
||||
restConfigProvider.GetRestConfig,
|
||||
dashboardStore,
|
||||
userService,
|
||||
),
|
||||
|
@ -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)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -25,6 +25,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"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
@ -355,8 +356,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
|
||||
folderSvc,
|
||||
folder.NewFakeStore(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
client.MockTestRestConfig{},
|
||||
nil,
|
||||
quotaService,
|
||||
nil,
|
||||
@ -453,7 +453,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena
|
||||
cfg, dashboardStore, folderStore,
|
||||
features, folderPermissions, ac,
|
||||
folderSvc, fStore,
|
||||
nil, nil, nil, nil, quotaService, nil, nil,
|
||||
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, svcErr)
|
||||
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
|
||||
@ -526,7 +526,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
cfg, dashboardStore, folderStore,
|
||||
features, folderPermissions, ac,
|
||||
folderSvc, fStore,
|
||||
nil, nil, nil, nil, quotaService, nil, nil,
|
||||
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, dashSvcErr)
|
||||
dashService.RegisterDashboardPermissions(dashboardPermissions)
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"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/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -735,7 +736,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
|
||||
cfg, dashboardStore, folderStore,
|
||||
features, acmock.NewMockedPermissionsService(), ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, nil, nil, nil, quotaService, nil, nil,
|
||||
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
service.RegisterDashboardPermissions(dashPermissionService)
|
||||
@ -833,7 +834,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
cfg, dashStore, folderStore,
|
||||
features, acmock.NewMockedPermissionsService(), ac,
|
||||
folderSvc, folder.NewFakeStore(),
|
||||
nil, nil, nil, nil, quotaService, nil, nil,
|
||||
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
dashService.RegisterDashboardPermissions(dashPermissionService)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
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/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -62,7 +63,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa
|
||||
cfg, dashboardStore, fs,
|
||||
features, folderPermissions, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, nil, nil, nil, quotaService, nil, nil,
|
||||
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -325,7 +326,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
dashService, err := service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStoreService, folderStore,
|
||||
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(), nil, nil, nil, nil, quotatest.New(false, nil), nil, nil,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
dashService.RegisterDashboardPermissions(dashPermissionService)
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"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"
|
||||
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
@ -1398,7 +1399,7 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
|
||||
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest())
|
||||
|
||||
dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, nil, nil, nil, quotatest.New(false, nil), nil, nil)
|
||||
dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil)
|
||||
require.NoError(t, err)
|
||||
dashboardService.RegisterDashboardPermissions(&actest.FakePermissionsService{})
|
||||
fakeGuardian := &guardian.FakeDashboardGuardian{
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/authimpl"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -496,7 +497,7 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
|
||||
fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
||||
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest())
|
||||
dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(),
|
||||
ac, folderSvc, fStore, nil, nil, nil, nil, quotaService, nil, nil)
|
||||
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil)
|
||||
require.NoError(t, err)
|
||||
dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService())
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
|
@ -1,20 +1,22 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func NewSearchClient(cfg *setting.Cfg, unifiedStorageConfigKey string, unifiedClient ResourceIndexClient, legacyClient ResourceIndexClient) ResourceIndexClient {
|
||||
func NewSearchClient(cfg *setting.Cfg, unifiedStorageConfigKey string, unifiedClient func(context.Context) ResourceClient, legacyClient ResourceIndexClient) func(context.Context) ResourceIndexClient {
|
||||
config, ok := cfg.UnifiedStorage[unifiedStorageConfigKey]
|
||||
if !ok {
|
||||
return legacyClient
|
||||
return func(ctx context.Context) ResourceIndexClient { return legacyClient }
|
||||
}
|
||||
|
||||
switch config.DualWriterMode {
|
||||
case rest.Mode0, rest.Mode1, rest.Mode2:
|
||||
return legacyClient
|
||||
return func(ctx context.Context) ResourceIndexClient { return legacyClient }
|
||||
default:
|
||||
return unifiedClient
|
||||
return func(ctx context.Context) ResourceIndexClient { return unifiedClient(ctx) }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user