mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
k8s: Dashboards: allow querying of unistore (#97995)
This commit is contained in:
parent
d8ddbcda51
commit
b3985a4d37
@ -478,7 +478,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
|
||||
hs.log.Error("Failed to delete public dashboard")
|
||||
}
|
||||
|
||||
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.SignedInUser.GetOrgID())
|
||||
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, dash.UID, c.SignedInUser.GetOrgID())
|
||||
if err != nil {
|
||||
var dashboardErr dashboards.DashboardErr
|
||||
if ok := errors.As(err, &dashboardErr); ok {
|
||||
|
@ -263,7 +263,7 @@ func TestHTTPServer_DeleteDashboardByUID_AccessControl(t *testing.T) {
|
||||
|
||||
dashSvc := dashboards.NewFakeDashboardService(t)
|
||||
dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dash, nil).Maybe()
|
||||
dashSvc.On("DeleteDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
||||
dashSvc.On("DeleteDashboard", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
||||
hs.DashboardService = dashSvc
|
||||
|
||||
hs.Cfg = setting.NewCfg()
|
||||
@ -838,14 +838,14 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
if dashboardService == nil {
|
||||
dashboardService, err = service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions,
|
||||
ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), nil,
|
||||
ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions,
|
||||
ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), nil,
|
||||
ac, folderSvc, fStore, nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -478,7 +478,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
|
||||
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
|
||||
sc.cfg, dashStore, folderStore,
|
||||
features, folderPermissions, dashboardPermissions, ac,
|
||||
folderServiceWithFlagOn, fStore, nil, zanzana.NewNoopClient(), nil,
|
||||
folderServiceWithFlagOn, fStore, nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(b, err)
|
||||
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
//go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go
|
||||
type DashboardService interface {
|
||||
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, validateProvisionedDashboard bool) (*SaveDashboardCommand, error)
|
||||
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
|
||||
DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error
|
||||
FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
|
||||
// GetDashboard fetches a dashboard.
|
||||
// To fetch a dashboard under root by title should set the folder UID to point to an empty string
|
||||
|
@ -102,17 +102,17 @@ func (_m *FakeDashboardService) CountInFolders(ctx context.Context, orgID int64,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteDashboard provides a mock function with given fields: ctx, dashboardId, orgId
|
||||
func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
|
||||
ret := _m.Called(ctx, dashboardId, orgId)
|
||||
// DeleteDashboard provides a mock function with given fields: ctx, dashboardId, dashboardUID, orgId
|
||||
func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
|
||||
ret := _m.Called(ctx, dashboardId, dashboardUID, orgId)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
|
||||
r0 = rf(ctx, dashboardId, orgId)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string, int64) error); ok {
|
||||
r0 = rf(ctx, dashboardId, dashboardUID, orgId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/authlib/claims"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel"
|
||||
@ -38,6 +40,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
k8sUser "k8s.io/apiserver/pkg/authentication/user"
|
||||
@ -67,6 +70,7 @@ type DashboardServiceImpl struct {
|
||||
dashboardStore dashboards.Store
|
||||
folderStore folder.FolderStore
|
||||
folderService folder.Service
|
||||
userService user.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
folderPermissions accesscontrol.FolderPermissionsService
|
||||
dashboardPermissions accesscontrol.DashboardPermissionsService
|
||||
@ -79,6 +83,7 @@ type DashboardServiceImpl struct {
|
||||
// interface to allow for testing
|
||||
type dashboardK8sHandler interface {
|
||||
getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
|
||||
getNamespace(orgID int64) string
|
||||
}
|
||||
|
||||
var _ dashboardK8sHandler = (*dashk8sHandler)(nil)
|
||||
@ -95,15 +100,10 @@ func ProvideDashboardServiceImpl(
|
||||
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||
dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl,
|
||||
folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, zclient zanzana.Client,
|
||||
restConfigProvider apiserver.RestConfigProvider,
|
||||
restConfigProvider apiserver.RestConfigProvider, userService user.Service,
|
||||
) (*DashboardServiceImpl, error) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: v0alpha1.GROUP,
|
||||
Version: v0alpha1.VERSION,
|
||||
Resource: v0alpha1.DashboardResourceInfo.GetName(),
|
||||
}
|
||||
k8sHandler := &dashk8sHandler{
|
||||
gvr: gvr,
|
||||
gvr: v0alpha1.DashboardResourceInfo.GroupVersionResource(),
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
restConfigProvider: restConfigProvider,
|
||||
}
|
||||
@ -119,6 +119,7 @@ func ProvideDashboardServiceImpl(
|
||||
zclient: zclient,
|
||||
folderStore: folderStore,
|
||||
folderService: folderSvc,
|
||||
userService: userService,
|
||||
k8sclient: k8sHandler,
|
||||
metrics: newDashboardsMetrics(r),
|
||||
}
|
||||
@ -142,6 +143,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) {
|
||||
// TODO: make this go through the k8s cli too under the feature toggle. First get dashboard through unistore & then get provisioning data
|
||||
return dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID)
|
||||
}
|
||||
|
||||
@ -284,6 +286,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
// TODO: once we can search in unistore by id, go through k8s cli too
|
||||
return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd)
|
||||
}
|
||||
|
||||
@ -358,6 +361,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
|
||||
}
|
||||
|
||||
// dashboard
|
||||
// TODO: make this go through the k8s cli too under the feature toggle. First save dashboard & then save provisioning data
|
||||
dash, err := dr.dashboardStore.SaveProvisionedDashboard(ctx, *cmd, provisioning)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -402,9 +406,9 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dash, err := dr.dashboardStore.SaveDashboard(ctx, *cmd)
|
||||
dash, err := dr.saveDashboard(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("saving dashboard failed: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// new dashboard created
|
||||
@ -414,7 +418,20 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
|
||||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboards.SaveDashboardCommand) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID)
|
||||
}
|
||||
|
||||
return dr.dashboardStore.SaveDashboard(ctx, *cmd)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true})
|
||||
}
|
||||
|
||||
return dr.dashboardStore.GetSoftDeletedDashboard(ctx, orgID, uid)
|
||||
}
|
||||
|
||||
@ -457,6 +474,8 @@ func (dr *DashboardServiceImpl) RestoreDashboard(ctx context.Context, dashboard
|
||||
return folder.ErrInternal.Errorf("failed to fetch parent folder from store: %w", err)
|
||||
}
|
||||
|
||||
// TODO: once restore in k8s is finalized, add functionality here under the feature toggle
|
||||
|
||||
return dr.dashboardStore.RestoreDashboard(ctx, dashboard.OrgID, dashboard.UID, restoringFolder)
|
||||
}
|
||||
|
||||
@ -473,13 +492,18 @@ func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID i
|
||||
return dashboards.ErrDashboardCannotDeleteProvisionedDashboard
|
||||
}
|
||||
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
// deletes in unistore are soft deletes, so we can just delete in the same way
|
||||
return dr.deleteDashboardThroughK8s(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgID, UID: dashboardUID})
|
||||
}
|
||||
|
||||
return dr.dashboardStore.SoftDeleteDashboard(ctx, orgID, dashboardUID)
|
||||
}
|
||||
|
||||
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
|
||||
// operations by the user where we want to make sure user does not delete provisioned dashboard.
|
||||
func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
|
||||
return dr.deleteDashboard(ctx, dashboardId, orgId, true)
|
||||
func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
|
||||
return dr.deleteDashboard(ctx, dashboardId, dashboardUID, orgId, true)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardByPublicUid(ctx context.Context, dashboardPublicUid string) (*dashboards.Dashboard, error) {
|
||||
@ -488,10 +512,10 @@ func (dr *DashboardServiceImpl) GetDashboardByPublicUid(ctx context.Context, das
|
||||
|
||||
// DeleteProvisionedDashboard removes dashboard from the DB even if it is provisioned.
|
||||
func (dr *DashboardServiceImpl) DeleteProvisionedDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
|
||||
return dr.deleteDashboard(ctx, dashboardId, orgId, false)
|
||||
return dr.deleteDashboard(ctx, dashboardId, "", orgId, false)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
|
||||
func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64, validateProvisionedDashboard bool) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.deleteDashboard")
|
||||
defer span.End()
|
||||
|
||||
@ -505,7 +529,14 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
|
||||
return dashboards.ErrDashboardCannotDeleteProvisionedDashboard
|
||||
}
|
||||
}
|
||||
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId}
|
||||
|
||||
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID}
|
||||
|
||||
// TODO: once we can do this search by IDs in unistore, remove this constraint
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) && cmd.UID != "" {
|
||||
return dr.deleteDashboardThroughK8s(ctx, cmd)
|
||||
}
|
||||
|
||||
return dr.dashboardStore.DeleteDashboard(ctx, cmd)
|
||||
}
|
||||
|
||||
@ -526,7 +557,7 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dash, err := dr.dashboardStore.SaveDashboard(ctx, *cmd)
|
||||
dash, err := dr.saveDashboard(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -539,10 +570,12 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
|
||||
// UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
|
||||
// and provisioned dashboards are left behind but not deleted.
|
||||
func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
|
||||
// TODO: once we can search by dashboard ID in unistore, go through k8s cli too
|
||||
return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) {
|
||||
// TODO: once we can do this search in unistore, go through k8s cli too
|
||||
return dr.dashboardStore.GetDashboardsByPluginID(ctx, query)
|
||||
}
|
||||
|
||||
@ -626,92 +659,20 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
func (dk8s *dashk8sHandler) getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
|
||||
dyn, err := dynamic.NewForConfig(dk8s.restConfigProvider.GetRestConfig(ctx))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return dyn.Resource(dk8s.gvr).Namespace(dk8s.namespacer(orgID)), true
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) 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 (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
|
||||
// TODO: once getting dashboards by ID in unified storage is supported, we can remove the restraint of the uid being provided
|
||||
// TODO: once we can do this search by ID in unistore, remove this constraint
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) && query.UID != "" {
|
||||
// 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 := dr.getK8sContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
client, ok := dr.k8sclient.getClient(newCtx, query.OrgID)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if including deleted dashboards, use the /latest subresource
|
||||
subresource := ""
|
||||
if query.IncludeDeleted {
|
||||
subresource = "latest"
|
||||
}
|
||||
|
||||
out, err := client.Get(newCtx, query.UID, v1.GetOptions{}, subresource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if out == nil {
|
||||
return nil, dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
return UnstructuredToLegacyDashboard(out, query.OrgID)
|
||||
return dr.getDashboardThroughK8s(ctx, query)
|
||||
}
|
||||
|
||||
return dr.dashboardStore.GetDashboard(ctx, query)
|
||||
}
|
||||
|
||||
// TODO: once getting dashboards by ID in unified storage is supported, need to do the same as the above function
|
||||
// TODO: once we can do this search by ID in unistore, go through k8s cli too
|
||||
func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
|
||||
return dr.dashboardStore.GetDashboardUIDByID(ctx, query)
|
||||
}
|
||||
|
||||
// TODO: add support to get dashboards in unified storage.
|
||||
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
|
||||
return dr.dashboardStore.GetDashboards(ctx, query)
|
||||
}
|
||||
@ -808,7 +769,7 @@ func (dr *DashboardServiceImpl) getUserSharedDashboardUIDs(ctx context.Context,
|
||||
return userDashboardUIDs, nil
|
||||
}
|
||||
|
||||
// TODO: add support to find dashboards in unified storage too.
|
||||
// TODO: once we can do this search by this in unistore, go through k8s cli too
|
||||
func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.FindDashboards")
|
||||
defer span.End()
|
||||
@ -833,6 +794,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
|
||||
return dr.dashboardStore.FindDashboards(ctx, query)
|
||||
}
|
||||
|
||||
// TODO: once we can do this search in unistore, go through k8s cli too
|
||||
func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (model.HitList, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.service.SearchDashboards")
|
||||
defer span.End()
|
||||
@ -854,6 +816,14 @@ func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *das
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dr.listDashboardThroughK8s(ctx, requester.GetOrgID())
|
||||
}
|
||||
|
||||
return dr.dashboardStore.GetAllDashboards(ctx)
|
||||
}
|
||||
|
||||
@ -915,6 +885,7 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
|
||||
// TODO: use k8s client to get dashboards first, and then filter tags and join
|
||||
return dr.dashboardStore.GetDashboardTags(ctx, query)
|
||||
}
|
||||
|
||||
@ -945,7 +916,7 @@ func (dr *DashboardServiceImpl) CleanUpDeletedDashboards(ctx context.Context) (i
|
||||
return 0, err
|
||||
}
|
||||
for _, dashboard := range deletedDashboards {
|
||||
err = dr.DeleteDashboard(ctx, dashboard.ID, dashboard.OrgID)
|
||||
err = dr.DeleteDashboard(ctx, dashboard.ID, dashboard.UID, dashboard.OrgID)
|
||||
if err != nil {
|
||||
dr.log.Warn("Failed to cleanup deleted dashboard", "dashboardUid", dashboard.UID, "error", err)
|
||||
break
|
||||
@ -956,35 +927,247 @@ func (dr *DashboardServiceImpl) CleanUpDeletedDashboards(ctx context.Context) (i
|
||||
return deletedDashboardsCount, nil
|
||||
}
|
||||
|
||||
func UnstructuredToLegacyDashboard(item *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Dashboard k8s functions
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
func (dk8s *dashk8sHandler) getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
|
||||
dyn, err := dynamic.NewForConfig(dk8s.restConfigProvider.GetRestConfig(ctx))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return dyn.Resource(dk8s.gvr).Namespace(dk8s.getNamespace(orgID)), true
|
||||
}
|
||||
|
||||
func (dk8s *dashk8sHandler) getNamespace(orgID int64) string {
|
||||
return dk8s.namespacer(orgID)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) 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 (dr *DashboardServiceImpl) getDashboardThroughK8s(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, 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 := dr.getK8sContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
client, ok := dr.k8sclient.getClient(newCtx, query.OrgID)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if including deleted dashboards, use the /latest subresource
|
||||
subresource := ""
|
||||
if query.IncludeDeleted {
|
||||
subresource = "latest"
|
||||
}
|
||||
|
||||
out, err := client.Get(newCtx, query.UID, v1.GetOptions{}, subresource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if out == nil {
|
||||
return nil, dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
return dr.UnstructuredToLegacyDashboard(ctx, out, query.OrgID)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd *dashboards.SaveDashboardCommand, orgID int64) (*dashboards.Dashboard, 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 := dr.getK8sContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
client, ok := dr.k8sclient.getClient(newCtx, orgID)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
obj, err := LegacySaveCommandToUnstructured(cmd, dr.k8sclient.getNamespace(orgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out *unstructured.Unstructured
|
||||
current, err := client.Get(newCtx, obj.GetName(), v1.GetOptions{})
|
||||
if current == nil || err != nil {
|
||||
out, err = client.Create(newCtx, &obj, v1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
out, err = client.Update(newCtx, &obj, v1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
finalDash, err := dr.UnstructuredToLegacyDashboard(ctx, out, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return finalDash, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) deleteDashboardThroughK8s(ctx context.Context, cmd *dashboards.DeleteDashboardCommand) 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 := dr.getK8sContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
client, ok := dr.k8sclient.getClient(newCtx, cmd.OrgID)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not get k8s client")
|
||||
}
|
||||
|
||||
err = client.Delete(newCtx, cmd.UID, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) listDashboardThroughK8s(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, 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 := dr.getK8sContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
client, ok := dr.k8sclient.getClient(newCtx, orgID)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: once we can do this search in unistore, update this
|
||||
out, err := client.List(newCtx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if out == nil {
|
||||
return nil, dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dashboards := make([]*dashboards.Dashboard, 0)
|
||||
for _, item := range out.Items {
|
||||
dash, err := dr.UnstructuredToLegacyDashboard(ctx, &item, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboards = append(dashboards, dash)
|
||||
}
|
||||
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
||||
spec, ok := item.Object["spec"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("error parsing dashboard from k8s response")
|
||||
}
|
||||
|
||||
out := dashboards.Dashboard{
|
||||
OrgID: orgID,
|
||||
Data: simplejson.NewFromAny(spec),
|
||||
}
|
||||
obj, err := utils.MetaAccessor(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uid := obj.GetName()
|
||||
spec["uid"] = uid
|
||||
|
||||
out.UID = obj.GetName()
|
||||
out.Slug = obj.GetSlug()
|
||||
out.FolderUID = obj.GetFolder()
|
||||
dashVersion := 0
|
||||
if version, ok := spec["version"].(int64); ok {
|
||||
dashVersion = int(version)
|
||||
}
|
||||
|
||||
out := dashboards.Dashboard{
|
||||
OrgID: orgID,
|
||||
UID: uid,
|
||||
Slug: obj.GetSlug(),
|
||||
FolderUID: obj.GetFolder(),
|
||||
Version: dashVersion,
|
||||
Data: simplejson.NewFromAny(spec),
|
||||
}
|
||||
|
||||
out.Created = obj.GetCreationTimestamp().Time
|
||||
updated, err := obj.GetUpdatedTimestamp()
|
||||
if err == nil && updated != nil {
|
||||
out.Updated = *updated
|
||||
} else {
|
||||
// by default, set updated to created
|
||||
out.Updated = out.Created
|
||||
}
|
||||
|
||||
deleted := obj.GetDeletionTimestamp()
|
||||
if deleted != nil {
|
||||
out.Deleted = obj.GetDeletionTimestamp().Time
|
||||
}
|
||||
|
||||
createdBy := obj.GetCreatedBy()
|
||||
if createdBy != "" && toUID(createdBy) != "" {
|
||||
creator, err := dr.userService.GetByUID(ctx, &user.GetUserByUIDQuery{UID: toUID(createdBy)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.CreatedBy = creator.ID
|
||||
}
|
||||
|
||||
updatedBy := obj.GetUpdatedBy()
|
||||
if updatedBy != "" && toUID(updatedBy) != "" {
|
||||
updator, err := dr.userService.GetByUID(ctx, &user.GetUserByUIDQuery{UID: toUID(updatedBy)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.UpdatedBy = updator.ID
|
||||
}
|
||||
|
||||
if id, ok := spec["id"].(int64); ok {
|
||||
out.ID = id
|
||||
}
|
||||
@ -993,10 +1176,6 @@ func UnstructuredToLegacyDashboard(item *unstructured.Unstructured, orgID int64)
|
||||
out.GnetID = gnetID
|
||||
}
|
||||
|
||||
if version, ok := spec["version"].(int64); ok {
|
||||
out.Version = int(version)
|
||||
}
|
||||
|
||||
if pluginID, ok := spec["plugin_id"].(string); ok {
|
||||
out.PluginID = pluginID
|
||||
}
|
||||
@ -1019,3 +1198,48 @@ func UnstructuredToLegacyDashboard(item *unstructured.Unstructured, orgID int64)
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, namespace string) (unstructured.Unstructured, error) {
|
||||
uid := cmd.GetDashboardModel().UID
|
||||
if uid == "" {
|
||||
uid = uuid.NewString()
|
||||
}
|
||||
|
||||
finalObj := unstructured.Unstructured{
|
||||
Object: map[string]interface{}{},
|
||||
}
|
||||
|
||||
obj := map[string]interface{}{}
|
||||
body, err := cmd.Dashboard.ToDB()
|
||||
if err != nil {
|
||||
return finalObj, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &obj)
|
||||
if err != nil {
|
||||
return finalObj, err
|
||||
}
|
||||
|
||||
// update the version
|
||||
version, ok := obj["version"].(float64)
|
||||
if !ok || version == 0 {
|
||||
obj["version"] = 1
|
||||
} else if !cmd.Overwrite {
|
||||
obj["version"] = version + 1
|
||||
}
|
||||
|
||||
finalObj.Object["spec"] = obj
|
||||
finalObj.SetName(uid)
|
||||
finalObj.SetNamespace(namespace)
|
||||
finalObj.SetGroupVersionKind(v0alpha1.DashboardResourceInfo.GroupVersionKind())
|
||||
|
||||
return finalObj, nil
|
||||
}
|
||||
|
||||
func toUID(rawIdentifier string) string {
|
||||
parts := strings.Split(rawIdentifier, ":")
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
@ -886,6 +886,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
guardian.InitAccessControlGuardian(cfg, ac, dashboardService)
|
||||
@ -954,6 +955,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||
@ -981,6 +983,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = service.SaveDashboard(context.Background(), &dto, false)
|
||||
@ -1027,6 +1030,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||
@ -1080,6 +1084,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
res, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -19,6 +21,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@ -179,7 +182,7 @@ func TestDashboardService(t *testing.T) {
|
||||
|
||||
t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) {
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once()
|
||||
err := service.DeleteDashboard(context.Background(), 1, 1)
|
||||
err := service.DeleteDashboard(context.Background(), 1, "", 1)
|
||||
require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard)
|
||||
})
|
||||
})
|
||||
@ -196,7 +199,7 @@ func TestDashboardService(t *testing.T) {
|
||||
args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1}
|
||||
fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once()
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(nil, nil).Once()
|
||||
err := service.DeleteDashboard(context.Background(), 1, 1)
|
||||
err := service.DeleteDashboard(context.Background(), 1, "", 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
@ -238,6 +241,10 @@ func (m *mockDashK8sCli) getClient(ctx context.Context, orgID int64) (dynamic.Re
|
||||
return args.Get(0).(dynamic.ResourceInterface), args.Bool(1)
|
||||
}
|
||||
|
||||
func (m *mockDashK8sCli) getNamespace(orgID int64) string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
type mockResourceInterface struct {
|
||||
mock.Mock
|
||||
dynamic.ResourceInterface
|
||||
@ -251,6 +258,48 @@ func (m *mockResourceInterface) Get(ctx context.Context, name string, options me
|
||||
return args.Get(0).(*unstructured.Unstructured), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockResourceInterface) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
||||
args := m.Called(ctx, opts)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*unstructured.UnstructuredList), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockResourceInterface) Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
|
||||
args := m.Called(ctx, obj, options, subresources)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*unstructured.Unstructured), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockResourceInterface) Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
|
||||
args := m.Called(ctx, obj, options, subresources)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*unstructured.Unstructured), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockResourceInterface) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error {
|
||||
args := m.Called(ctx, name, options, subresources)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *mockDashK8sCli, *mockResourceInterface) {
|
||||
k8sClientMock := new(mockDashK8sCli)
|
||||
k8sResourceMock := new(mockResourceInterface)
|
||||
service.k8sclient = k8sClientMock
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
|
||||
ctx := context.Background()
|
||||
userCtx := &user.SignedInUser{UserID: 1, OrgID: 1}
|
||||
ctx = identity.WithRequester(ctx, userCtx)
|
||||
|
||||
return ctx, k8sClientMock, k8sResourceMock
|
||||
}
|
||||
|
||||
func TestGetDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
@ -258,14 +307,13 @@ func TestGetDashboard(t *testing.T) {
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
query := &dashboards.GetDashboardQuery{
|
||||
UID: "test-uid",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
query := &dashboards.GetDashboardQuery{
|
||||
UID: "test-uid",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
fakeStore.On("GetDashboard", mock.Anything, query).Return(&dashboards.Dashboard{}, nil).Once()
|
||||
dashboard, err := service.GetDashboard(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
@ -274,36 +322,26 @@ func TestGetDashboard(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
k8sClientMock := new(mockDashK8sCli)
|
||||
k8sResourceMock := new(mockResourceInterface)
|
||||
service.k8sclient = k8sClientMock
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
query := &dashboards.GetDashboardQuery{
|
||||
UID: "test-uid",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"title": "testing slugify",
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}
|
||||
|
||||
dashboardExpected := dashboards.Dashboard{
|
||||
UID: "uid", // uid is the name of the k8s object
|
||||
Title: "testing slugify",
|
||||
Slug: "testing-slugify", // slug is taken from title
|
||||
OrgID: 1, // orgID is populated from the query
|
||||
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify"}),
|
||||
UID: "uid", // uid is the name of the k8s object
|
||||
Title: "testing slugify",
|
||||
Slug: "testing-slugify", // slug is taken from title
|
||||
OrgID: 1, // orgID is populated from the query
|
||||
Version: 1,
|
||||
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
userCtx := &user.SignedInUser{UserID: 1}
|
||||
ctx = identity.WithRequester(ctx, userCtx)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil).Once()
|
||||
|
||||
@ -316,18 +354,7 @@ func TestGetDashboard(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should return error when Kubernetes client fails", func(t *testing.T) {
|
||||
k8sClientMock := new(mockDashK8sCli)
|
||||
k8sResourceMock := new(mockResourceInterface)
|
||||
service.k8sclient = k8sClientMock
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
query := &dashboards.GetDashboardQuery{
|
||||
UID: "test-uid",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
userCtx := &user.SignedInUser{UserID: 1}
|
||||
ctx = identity.WithRequester(ctx, userCtx)
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything).Return(nil, assert.AnError).Once()
|
||||
|
||||
@ -338,21 +365,9 @@ func TestGetDashboard(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should return dashboard not found if Kubernetes client returns nil", func(t *testing.T) {
|
||||
k8sClientMock := new(mockDashK8sCli)
|
||||
k8sResourceMock := new(mockResourceInterface)
|
||||
service.k8sclient = k8sClientMock
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
query := &dashboards.GetDashboardQuery{
|
||||
UID: "test-uid",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
userCtx := &user.SignedInUser{UserID: 1}
|
||||
ctx = identity.WithRequester(ctx, userCtx)
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything).Return(nil, nil).Once()
|
||||
|
||||
dashboard, err := service.GetDashboard(ctx, query)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, dashboards.ErrDashboardNotFound, err)
|
||||
@ -360,3 +375,249 @@ func TestGetDashboard(t *testing.T) {
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAllDashboards(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetAllDashboards", mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once()
|
||||
dashboard, err := service.GetAllDashboards(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
|
||||
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}
|
||||
|
||||
dashboardExpected := dashboards.Dashboard{
|
||||
UID: "uid", // uid is the name of the k8s object
|
||||
Title: "testing slugify",
|
||||
Slug: "testing-slugify", // slug is taken from title
|
||||
OrgID: 1, // orgID is populated from the query
|
||||
Version: 1, // default to version 1
|
||||
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}),
|
||||
}
|
||||
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
k8sResourceMock.On("List", mock.Anything, mock.Anything).Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{dashboardUnstructured}}, nil).Once()
|
||||
|
||||
dashes, err := service.GetAllDashboards(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashes)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
// make sure the conversion is working
|
||||
require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSaveDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
defer func() { guardian.New = origNewDashboardGuardian }()
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
||||
|
||||
query := &dashboards.SaveDashboardDTO{
|
||||
OrgID: 1,
|
||||
User: &user.SignedInUser{UserID: 1},
|
||||
Dashboard: &dashboards.Dashboard{
|
||||
UID: "uid", // uid is the name of the k8s object
|
||||
Title: "testing slugify",
|
||||
Slug: "testing-slugify", // slug is taken from title
|
||||
OrgID: 1, // orgID is populated from the query
|
||||
Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid"}),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything, mock.AnythingOfType("bool")).Return(true, nil)
|
||||
fakeStore.On("SaveDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
|
||||
dashboard, err := service.SaveDashboard(context.Background(), query, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
},
|
||||
"spec": map[string]any{
|
||||
"test": "test",
|
||||
"version": int64(1),
|
||||
"title": "testing slugify",
|
||||
},
|
||||
}}
|
||||
|
||||
t.Run("Should use Kubernetes create if feature flags are enabled and dashboard doesn't exist", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, query.Dashboard.UID, mock.Anything, mock.Anything).Return(nil, nil)
|
||||
k8sResourceMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
|
||||
|
||||
dashboard, err := service.SaveDashboard(ctx, query, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes update if feature flags are enabled and dashboard exists", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true)
|
||||
k8sResourceMock.On("Get", mock.Anything, query.Dashboard.UID, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
|
||||
k8sResourceMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil)
|
||||
|
||||
dashboard, err := service.SaveDashboard(ctx, query, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dashboard)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteDashboard(t *testing.T) {
|
||||
fakeStore := dashboards.FakeDashboardStore{}
|
||||
defer fakeStore.AssertExpectations(t)
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
}
|
||||
|
||||
t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) {
|
||||
service.features = featuremgmt.WithFeatures()
|
||||
fakeStore.On("DeleteDashboard", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil).Once()
|
||||
err := service.DeleteDashboard(context.Background(), 1, "uid", 1)
|
||||
require.NoError(t, err)
|
||||
fakeStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service)
|
||||
k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once()
|
||||
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil).Once()
|
||||
k8sResourceMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
|
||||
err := service.DeleteDashboard(ctx, 1, "uid", 1)
|
||||
require.NoError(t, err)
|
||||
k8sClientMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnstructuredToLegacyDashboard(t *testing.T) {
|
||||
fake := usertest.NewUserServiceFake()
|
||||
fake.ExpectedUser = &user.User{ID: 10, UID: "useruid"}
|
||||
dr := &DashboardServiceImpl{
|
||||
userService: fake,
|
||||
}
|
||||
t.Run("successfully converts unstructured to legacy dashboard", func(t *testing.T) {
|
||||
uid := "36b7c825-79cc-435e-acf6-c78bd96a4510"
|
||||
orgID := int64(123)
|
||||
title := "Test Dashboard"
|
||||
now := metav1.Now()
|
||||
item := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"title": title,
|
||||
"version": int64(1),
|
||||
"id": int64(1),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
obj, err := utils.MetaAccessor(item)
|
||||
require.NoError(t, err)
|
||||
obj.SetCreationTimestamp(now)
|
||||
obj.SetName(uid)
|
||||
obj.SetCreatedBy("user:useruid")
|
||||
obj.SetUpdatedBy("user:useruid")
|
||||
|
||||
result, err := dr.UnstructuredToLegacyDashboard(context.Background(), item, orgID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, uid, result.UID)
|
||||
assert.Equal(t, title, result.Title)
|
||||
assert.Equal(t, orgID, result.OrgID)
|
||||
assert.Equal(t, "test-dashboard", result.Slug) // should slugify the title
|
||||
assert.Equal(t, false, result.HasACL)
|
||||
assert.Equal(t, false, result.IsFolder)
|
||||
assert.Equal(t, int64(1), result.ID)
|
||||
assert.Equal(t, now.Time.Format(time.RFC3339), result.Created.Format(time.RFC3339))
|
||||
assert.Equal(t, int64(10), result.CreatedBy)
|
||||
assert.Equal(t, now.Time.Format(time.RFC3339), result.Updated.Format(time.RFC3339)) // updated should default to created
|
||||
assert.Equal(t, int64(10), result.UpdatedBy)
|
||||
})
|
||||
|
||||
t.Run("returns error if spec is missing", func(t *testing.T) {
|
||||
item := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{},
|
||||
}
|
||||
_, err := (&DashboardServiceImpl{}).UnstructuredToLegacyDashboard(context.Background(), item, int64(123))
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "error parsing dashboard from k8s response", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestLegacySaveCommandToUnstructured(t *testing.T) {
|
||||
namespace := "test-namespace"
|
||||
t.Run("successfully converts save command to unstructured", func(t *testing.T) {
|
||||
cmd := &dashboards.SaveDashboardCommand{
|
||||
Dashboard: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "test-uid"}),
|
||||
}
|
||||
|
||||
result, err := LegacySaveCommandToUnstructured(cmd, namespace)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, "test-uid", result.GetName())
|
||||
assert.Equal(t, "test-namespace", result.GetNamespace())
|
||||
spec := result.Object["spec"].(map[string]any)
|
||||
assert.Equal(t, spec["version"], 1)
|
||||
})
|
||||
|
||||
t.Run("should increase version when called", func(t *testing.T) {
|
||||
cmd := &dashboards.SaveDashboardCommand{
|
||||
Dashboard: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "test-uid", "version": int64(1)}),
|
||||
}
|
||||
result, err := LegacySaveCommandToUnstructured(cmd, namespace)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
spec := result.Object["spec"].(map[string]any)
|
||||
assert.Equal(t, spec["version"], float64(2))
|
||||
})
|
||||
}
|
||||
|
||||
func TestToUID(t *testing.T) {
|
||||
t.Run("parses valid UID", func(t *testing.T) {
|
||||
rawIdentifier := "user:uid-value"
|
||||
result := toUID(rawIdentifier)
|
||||
assert.Equal(t, "uid-value", result)
|
||||
})
|
||||
|
||||
t.Run("returns empty string for invalid identifier", func(t *testing.T) {
|
||||
rawIdentifier := "invalid-uid"
|
||||
result := toUID(rawIdentifier)
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ func TestIntegrationDashboardServiceZanzana(t *testing.T) {
|
||||
nil,
|
||||
zclient,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -101,7 +101,7 @@ func TestValidateDashboardExists(t *testing.T) {
|
||||
feats := featuremgmt.WithFeatures()
|
||||
dashboardStore, err := dashdb.ProvideDashboardStore(sqlStore, cfg, feats, tagimpl.ProvideService(sqlStore), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), feats, nil, nil, acmock.New(), foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient(), nil)
|
||||
dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), feats, nil, nil, acmock.New(), foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient(), nil, nil)
|
||||
require.NoError(t, err)
|
||||
s := ProvideService(dsStore, secretsService, dashSvc)
|
||||
ctx := context.Background()
|
||||
|
@ -490,7 +490,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
CanEditValue: true,
|
||||
})
|
||||
|
||||
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, zanzana.NewNoopClient(), nil)
|
||||
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, zanzana.NewNoopClient(), nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac, b)
|
||||
@ -573,7 +573,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
})
|
||||
|
||||
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff,
|
||||
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, zanzana.NewNoopClient(), nil)
|
||||
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, zanzana.NewNoopClient(), nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b)
|
||||
@ -719,7 +719,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
tc.service.dashboardStore = dashStore
|
||||
tc.service.store = nestedFolderStore
|
||||
|
||||
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service, tc.service.store, nil, zanzana.NewNoopClient(), nil)
|
||||
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service, tc.service.store, nil, zanzana.NewNoopClient(), nil, nil)
|
||||
require.NoError(t, err)
|
||||
alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac, b)
|
||||
require.NoError(t, err)
|
||||
@ -1506,6 +1506,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -312,6 +312,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
|
||||
nil,
|
||||
zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
||||
@ -400,6 +401,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, zanzana.NewNoopClient(),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, svcErr)
|
||||
guardian.InitAccessControlGuardian(cfg, ac, dashboardService)
|
||||
@ -461,7 +463,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
cfg, dashboardStore, folderStore,
|
||||
features, folderPermissions, dashboardPermissions, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, zanzana.NewNoopClient(), nil,
|
||||
nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, dashSvcErr)
|
||||
guardian.InitAccessControlGuardian(cfg, ac, dashService)
|
||||
|
@ -736,7 +736,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
|
||||
cfg, dashboardStore, folderStore,
|
||||
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, zanzana.NewNoopClient(), nil,
|
||||
nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
||||
@ -833,7 +833,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
cfg, dashStore, folderStore,
|
||||
features, acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, zanzana.NewNoopClient(), nil,
|
||||
nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
guardian.InitAccessControlGuardian(cfg, ac, dashService)
|
||||
|
@ -62,7 +62,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa
|
||||
cfg, dashboardStore, fs,
|
||||
features, folderPermissions, dashboardPermissions, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(),
|
||||
nil, zanzana.NewNoopClient(), nil,
|
||||
nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
|
@ -95,7 +95,7 @@ func (du *DashboardUpdater) syncPluginDashboards(ctx context.Context, plugin plu
|
||||
if dash.Removed {
|
||||
du.logger.Info("Deleting plugin dashboard", "pluginId", plugin.ID, "dashboard", dash.Slug)
|
||||
|
||||
if err := du.dashboardService.DeleteDashboard(ctx, dash.DashboardId, orgID); err != nil {
|
||||
if err := du.dashboardService.DeleteDashboard(ctx, dash.DashboardId, dash.UID, orgID); err != nil {
|
||||
du.logger.Error("Failed to auto update app dashboard", "pluginId", plugin.ID, "error", err)
|
||||
return
|
||||
}
|
||||
@ -150,7 +150,7 @@ func (du *DashboardUpdater) handlePluginStateChanged(ctx context.Context, event
|
||||
|
||||
for _, dash := range queryResult {
|
||||
du.logger.Info("Deleting plugin dashboard", "pluginId", event.PluginId, "dashboard", dash.Slug)
|
||||
if err := du.dashboardService.DeleteDashboard(ctx, dash.ID, dash.OrgID); err != nil {
|
||||
if err := du.dashboardService.DeleteDashboard(ctx, dash.ID, dash.UID, dash.OrgID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -443,18 +443,21 @@ func (s *pluginsSettingsServiceMock) DecryptedValues(_ *pluginsettings.DTO) map[
|
||||
type dashboardServiceMock struct {
|
||||
dashboards.DashboardService
|
||||
deleteDashboardArgs []struct {
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
dashboardUID string
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dashboardServiceMock) DeleteDashboard(_ context.Context, dashboardId int64, orgId int64) error {
|
||||
func (s *dashboardServiceMock) DeleteDashboard(_ context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
|
||||
s.deleteDashboardArgs = append(s.deleteDashboardArgs, struct {
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
dashboardUID string
|
||||
}{
|
||||
orgId: orgId,
|
||||
dashboardId: dashboardId,
|
||||
orgId: orgId,
|
||||
dashboardId: dashboardId,
|
||||
dashboardUID: dashboardUID,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@ -532,8 +535,9 @@ func scenario(t *testing.T, desc string, input scenarioInput, f func(ctx *scenar
|
||||
|
||||
sCtx.dashboardService = &dashboardServiceMock{
|
||||
deleteDashboardArgs: []struct {
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
orgId int64
|
||||
dashboardId int64
|
||||
dashboardUID string
|
||||
}{},
|
||||
}
|
||||
|
||||
|
@ -326,7 +326,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
dashService, err := service.ProvideDashboardServiceImpl(
|
||||
cfg, dashboardStoreService, folderStore,
|
||||
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), dashPermissionService, ac,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient(), nil,
|
||||
foldertest.NewFakeService(), folder.NewFakeStore(), nil, zanzana.NewNoopClient(), nil, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user