K8s: Move to one feature toggle for folders and dashboards (#100911)
This commit is contained in:
@@ -168,9 +168,8 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
|
||||
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
|
||||
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards |
|
||||
| `kubernetesCliDashboards` | Use the k8s client to retrieve dashboards internally |
|
||||
| `kubernetesRestore` | Allow restoring objects in k8s |
|
||||
| `kubernetesFoldersServiceV2` | Use the Folders Service V2, and route Folder Service requests to k8s |
|
||||
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s |
|
||||
| `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) |
|
||||
| `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query |
|
||||
| `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service |
|
||||
|
||||
@@ -108,9 +108,8 @@ export interface FeatureToggles {
|
||||
kubernetesPlaylists?: boolean;
|
||||
kubernetesSnapshots?: boolean;
|
||||
kubernetesDashboards?: boolean;
|
||||
kubernetesCliDashboards?: boolean;
|
||||
kubernetesRestore?: boolean;
|
||||
kubernetesFoldersServiceV2?: boolean;
|
||||
kubernetesClientDashboardsFolders?: boolean;
|
||||
datasourceQueryTypes?: boolean;
|
||||
queryService?: boolean;
|
||||
queryServiceRewrite?: boolean;
|
||||
|
||||
@@ -192,7 +192,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
|
||||
}
|
||||
metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetDashboard).Inc()
|
||||
// lookup folder title & url
|
||||
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
queryResult, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
UID: &dash.FolderUID,
|
||||
|
||||
@@ -625,7 +625,7 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
|
||||
|
||||
featuresArr := []any{featuremgmt.FlagNestedFolders}
|
||||
if tc.unifiedStorageEnabled {
|
||||
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesFoldersServiceV2)
|
||||
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesClientDashboardsFolders)
|
||||
}
|
||||
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
|
||||
@@ -66,7 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg,
|
||||
unified resource.ResourceClient,
|
||||
) *FolderAPIBuilder {
|
||||
if !featuremgmt.AnyEnabled(features,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagProvisioning) {
|
||||
return nil // skip registration unless opting into Kubernetes folders or unless we want to customize registration when testing
|
||||
|
||||
@@ -110,7 +110,7 @@ func ProvideFolderPermissions(
|
||||
// if the folder is not found, this may be on the create path,
|
||||
// where the write path to legacy will then go through the read
|
||||
// path and try to read from both legacy & unified before it exists on both
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
_, err = folderService.GetLegacy(ctx, &folder.GetFolderQuery{
|
||||
UID: &resourceID,
|
||||
OrgID: orgID,
|
||||
|
||||
@@ -152,7 +152,7 @@ func (dr *DashboardServiceImpl) getPermissionsService(isFolder bool) accesscontr
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
u := "a.Map{}
|
||||
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
|
||||
if err != nil {
|
||||
@@ -188,7 +188,7 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
resp, err := dr.k8sclient.GetStats(ctx, orgID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -226,7 +226,7 @@ func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -267,7 +267,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context,
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// if dashboard id is 0, it is a new dashboard
|
||||
if dashboardID == 0 {
|
||||
return nil, nil
|
||||
@@ -301,7 +301,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
if dashboardUID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
|
||||
}
|
||||
|
||||
// Validate folder
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
|
||||
OrgID: dash.OrgID,
|
||||
UID: &dash.FolderUID,
|
||||
@@ -555,7 +555,7 @@ func (dr *DashboardServiceImpl) ValidateDashboardBeforeSave(ctx context.Context,
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// check each org for orphaned provisioned dashboards
|
||||
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
|
||||
if err != nil {
|
||||
@@ -673,7 +673,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
|
||||
}
|
||||
|
||||
var dash *dashboards.Dashboard
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// save the dashboard but then do NOT return
|
||||
// we want to save the provisioning data to the dashboard_provisioning table still
|
||||
// to ensure we can safely rollback to mode2 if needed
|
||||
@@ -748,7 +748,7 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboards.SaveDashboardCommand) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID)
|
||||
}
|
||||
|
||||
@@ -756,7 +756,7 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true})
|
||||
}
|
||||
|
||||
@@ -815,7 +815,7 @@ func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID i
|
||||
return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore)
|
||||
}
|
||||
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// 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}, true)
|
||||
}
|
||||
@@ -836,7 +836,7 @@ func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId
|
||||
|
||||
// DeleteAllDashboards will delete all dashboards within a given org.
|
||||
func (dr *DashboardServiceImpl) DeleteAllDashboards(ctx context.Context, orgId int64) error {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.deleteAllDashboardThroughK8s(ctx, orgId)
|
||||
}
|
||||
|
||||
@@ -859,7 +859,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
|
||||
|
||||
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID}
|
||||
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
err := dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -924,7 +924,7 @@ 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 {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -957,7 +957,7 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: query.OrgID,
|
||||
ProvisionedRepo: dashboard.PluginIDRepoName,
|
||||
@@ -1055,7 +1055,7 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.getDashboardThroughK8s(ctx, query)
|
||||
}
|
||||
|
||||
@@ -1063,7 +1063,7 @@ func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboa
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1089,7 +1089,7 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
if query.OrgID == 0 {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
@@ -1237,7 +1237,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
|
||||
}(time.Now())
|
||||
}
|
||||
|
||||
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
if query.OrgId == 0 {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
@@ -1319,7 +1319,7 @@ 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) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1331,7 +1331,7 @@ func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashbo
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return dr.listDashboardsThroughK8s(ctx, orgID)
|
||||
}
|
||||
|
||||
@@ -1405,7 +1405,7 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
|
||||
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resource.ResourceSearchRequest{
|
||||
Facet: map[string]*resource.ResourceSearchRequest_Facet{
|
||||
"tags": {
|
||||
@@ -1437,7 +1437,7 @@ func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *das
|
||||
}
|
||||
|
||||
func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: orgID,
|
||||
FolderUIDs: folderUIDs,
|
||||
|
||||
@@ -265,7 +265,7 @@ func TestDashboardService(t *testing.T) {
|
||||
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) {
|
||||
mockCli := new(client.MockK8sHandler)
|
||||
service.k8sclient = mockCli
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
|
||||
|
||||
ctx := context.Background()
|
||||
userCtx := &user.SignedInUser{UserID: 1, OrgID: 1}
|
||||
@@ -1096,7 +1096,13 @@ func TestSaveProvisionedDashboard(t *testing.T) {
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
log: log.NewNopLogger(),
|
||||
folderService: &foldertest.FakeService{
|
||||
ExpectedFolder: &folder.Folder{
|
||||
ID: 0,
|
||||
UID: "general",
|
||||
},
|
||||
},
|
||||
log: log.NewNopLogger(),
|
||||
}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
@@ -1160,6 +1166,9 @@ func TestSaveDashboard(t *testing.T) {
|
||||
service := &DashboardServiceImpl{
|
||||
cfg: setting.NewCfg(),
|
||||
dashboardStore: &fakeStore,
|
||||
folderService: &foldertest.FakeService{
|
||||
ExpectedFolder: &folder.Folder{},
|
||||
},
|
||||
}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
|
||||
@@ -86,7 +86,7 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
|
||||
query.DashboardID = id
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,7 +157,7 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
versions, err := s.listHistoryThroughK8s(
|
||||
ctx,
|
||||
query.OrgID,
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestDashboardVersionService(t *testing.T) {
|
||||
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
|
||||
mockCli := new(client.MockK8sHandler)
|
||||
dashboardVersionService.k8sclient = mockCli
|
||||
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
|
||||
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
|
||||
|
||||
mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil)
|
||||
@@ -232,7 +232,7 @@ func TestListDashboardVersions(t *testing.T) {
|
||||
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
|
||||
mockCli := new(client.MockK8sHandler)
|
||||
dashboardVersionService.k8sclient = mockCli
|
||||
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
|
||||
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
|
||||
|
||||
dashboardService.On("GetDashboardUIDByID", mock.Anything,
|
||||
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
|
||||
|
||||
@@ -680,12 +680,6 @@ var (
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "kubernetesCliDashboards",
|
||||
Description: "Use the k8s client to retrieve dashboards internally",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "kubernetesRestore",
|
||||
Description: "Allow restoring objects in k8s",
|
||||
@@ -693,10 +687,10 @@ var (
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "kubernetesFoldersServiceV2",
|
||||
Description: "Use the Folders Service V2, and route Folder Service requests to k8s",
|
||||
Name: "kubernetesClientDashboardsFolders",
|
||||
Description: "Route the folder and dashboard service requests to k8s",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaSearchAndStorageSquad,
|
||||
Owner: grafanaAppPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "datasourceQueryTypes",
|
||||
|
||||
@@ -360,7 +360,6 @@ appPlatformAccessTokens,2024-09-05T16:18:44Z,2024-10-14T10:47:18Z,d5ebaa0ef92ede
|
||||
appSidecar,2024-09-09T12:45:05Z,,5e2ac24890906e5070323d87730dd78a4f885963,Andrej Ocenas
|
||||
vizActions,2024-09-09T14:11:55Z,,af48d3db1eb2d8681843f5997e50fea5e5ea3096,Adela Almasan
|
||||
groupAttributeSync,2024-09-09T15:29:43Z,,6ded6a8872204a818b3795dc733cc5fe5db066a0,Aaron Godin
|
||||
kubernetesFolders,2024-09-10T09:22:08Z,,b12a29a1dac8b9aec4a99be08e1665939cb27dc5,Arati R.
|
||||
alertingFilterV2,2024-09-11T11:29:26Z,,90ee52e8d9c14237f8a57b622c0def7512e657cd,Gilles De Mey
|
||||
improvedExternalSessionHandling,2024-09-17T10:54:39Z,,41cd0f51800d4849345fc0980ca4173967fc8e9e,Misi
|
||||
datasourceAPIServers,2024-09-19T08:28:27Z,,f21a5987a22bcdb596d6a258d2960e4151348b63,Ryan McKinley
|
||||
@@ -407,7 +406,6 @@ feedbackButton,2024-12-02T17:08:15Z,,8a1b89a5ebb847f6b29e92dcea796f00c30431d6,Mi
|
||||
elasticsearchCrossClusterSearch,2024-12-12T22:20:04Z,,b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f,Isabella Siu
|
||||
unifiedHistory,2024-12-13T10:41:18Z,,aac62c89dae1092836a91d1b6ae6bd7127fe676a,Laura Fernández
|
||||
lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5aece7,Sven Grossmann
|
||||
kubernetesCliDashboards,2024-12-13T22:55:43Z,,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen
|
||||
useV2DashboardsAPI,2024-12-17T21:17:09Z,,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop
|
||||
investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho
|
||||
unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood
|
||||
@@ -421,9 +419,9 @@ improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d282
|
||||
teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck
|
||||
ABTestFeatureToggleA,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
|
||||
ABTestFeatureToggleB,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
|
||||
kubernetesFoldersServiceV2,2025-01-13T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,maicon
|
||||
queryLibraryDashboards,2025-01-14T11:01:15Z,,740cd22fe51a3543c182857f31fc97fd42263306,Ashley Harrison
|
||||
elasticsearchImprovedParsing,2025-01-15T17:05:54Z,,bab55a4cb84f2ba57838f96a492ab9aa7f307957,Adam Yeats
|
||||
grafanaAdvisor,2025-01-20T10:08:00Z,,c1364d6be6f552203ba786f17a89664304b89247,Andres Martinez Gotor
|
||||
datasourceConnectionsTab,2025-01-21T17:39:48Z,,97d8f68b705f9949493079d1833abfe80e7b48f3,Syerikjan Kh
|
||||
unifiedStorageSearchPermissionFiltering,2025-01-22T11:38:37Z,,dd483fc17fa4a2931848e3574cfc31ea6f6530d9,owensmallwood
|
||||
kubernetesClientDashboardsFolders,2025-02-18T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,Stephanie Hingtgen
|
||||
|
||||
|
@@ -89,9 +89,8 @@ formatString,GA,@grafana/dataviz-squad,false,false,true
|
||||
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
|
||||
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
|
||||
kubernetesCliDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,false
|
||||
kubernetesRestore,experimental,@grafana/grafana-app-platform-squad,false,false,false
|
||||
kubernetesFoldersServiceV2,experimental,@grafana/search-and-storage,false,false,false
|
||||
kubernetesClientDashboardsFolders,experimental,@grafana/grafana-app-platform-squad,false,false,false
|
||||
datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
|
||||
|
@@ -367,17 +367,13 @@ const (
|
||||
// Use the kubernetes API in the frontend for dashboards
|
||||
FlagKubernetesDashboards = "kubernetesDashboards"
|
||||
|
||||
// FlagKubernetesCliDashboards
|
||||
// Use the k8s client to retrieve dashboards internally
|
||||
FlagKubernetesCliDashboards = "kubernetesCliDashboards"
|
||||
|
||||
// FlagKubernetesRestore
|
||||
// Allow restoring objects in k8s
|
||||
FlagKubernetesRestore = "kubernetesRestore"
|
||||
|
||||
// FlagKubernetesFoldersServiceV2
|
||||
// Use the Folders Service V2, and route Folder Service requests to k8s
|
||||
FlagKubernetesFoldersServiceV2 = "kubernetesFoldersServiceV2"
|
||||
// FlagKubernetesClientDashboardsFolders
|
||||
// Route the folder and dashboard service requests to k8s
|
||||
FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders"
|
||||
|
||||
// FlagDatasourceQueryTypes
|
||||
// Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)
|
||||
|
||||
@@ -2227,12 +2227,15 @@
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kubernetesCliDashboards",
|
||||
"resourceVersion": "1733520389522",
|
||||
"creationTimestamp": "2024-12-13T22:55:43Z"
|
||||
"name": "kubernetesClientDashboardsFolders",
|
||||
"resourceVersion": "1739913583714",
|
||||
"creationTimestamp": "2025-02-18T21:15:35Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-02-18 21:19:43.714638 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use the k8s client to retrieve dashboards internally",
|
||||
"description": "Route the folder and dashboard service requests to k8s",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/grafana-app-platform-squad"
|
||||
}
|
||||
@@ -2264,34 +2267,6 @@
|
||||
"hideFromAdminPage": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kubernetesFolders",
|
||||
"resourceVersion": "1725863636605",
|
||||
"creationTimestamp": "2024-09-10T09:22:08Z",
|
||||
"deletionTimestamp": "2025-01-22T20:49:15Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2024-09-09 06:33:56.605329 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use the kubernetes API in the frontend for folders, and route /api/folders requests to k8s",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/search-and-storage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kubernetesFoldersServiceV2",
|
||||
"resourceVersion": "1735336477446",
|
||||
"creationTimestamp": "2025-01-13T21:15:35Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use the Folders Service V2, and route Folder Service requests to k8s",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/search-and-storage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "kubernetesPlaylists",
|
||||
|
||||
@@ -111,7 +111,7 @@ func ProvideService(
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
|
||||
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
k8sHandler := client.NewK8sHandler(
|
||||
cfg,
|
||||
request.GetNamespaceMapper(cfg),
|
||||
@@ -129,7 +129,7 @@ func ProvideService(
|
||||
srv.k8sclient = k8sHandler
|
||||
}
|
||||
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
dashHandler := client.NewK8sHandler(
|
||||
cfg,
|
||||
request.GetNamespaceMapper(cfg),
|
||||
@@ -193,7 +193,7 @@ func (s *Service) DBMigration(db db.DB) {
|
||||
}
|
||||
|
||||
func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
// TODO:
|
||||
// - implement filtering by alerting folders and k6 folders (see the dashboards store `FindDashboards` method for reference)
|
||||
// - implement fallback on search client in unistore to go to legacy store (will need to read from dashboard store)
|
||||
@@ -204,7 +204,7 @@ func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery
|
||||
}
|
||||
|
||||
func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.getFoldersFromApiServer(ctx, q)
|
||||
}
|
||||
return s.GetFoldersLegacy(ctx, q)
|
||||
@@ -263,7 +263,7 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.getFromApiServer(ctx, q)
|
||||
}
|
||||
return s.GetLegacy(ctx, q)
|
||||
@@ -404,7 +404,7 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, user identi
|
||||
}
|
||||
|
||||
func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.getChildrenFromApiServer(ctx, q)
|
||||
}
|
||||
return s.GetChildrenLegacy(ctx, q)
|
||||
@@ -674,7 +674,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo
|
||||
}
|
||||
|
||||
func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.getParentsFromApiServer(ctx, q)
|
||||
}
|
||||
return s.GetParentsLegacy(ctx, q)
|
||||
@@ -707,7 +707,7 @@ func (s *Service) getFolderByTitle(ctx context.Context, orgID int64, title strin
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.createOnApiServer(ctx, cmd)
|
||||
}
|
||||
return s.CreateLegacy(ctx, cmd)
|
||||
@@ -826,7 +826,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.updateOnApiServer(ctx, cmd)
|
||||
}
|
||||
return s.UpdateLegacy(ctx, cmd)
|
||||
@@ -965,7 +965,7 @@ func prepareForUpdate(dashFolder *dashboards.Dashboard, orgId int64, userId int6
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.deleteFromApiServer(ctx, cmd)
|
||||
}
|
||||
return s.DeleteLegacy(ctx, cmd)
|
||||
@@ -1088,7 +1088,7 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
|
||||
}
|
||||
|
||||
func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.moveOnApiServer(ctx, cmd)
|
||||
}
|
||||
return s.MoveLegacy(ctx, cmd)
|
||||
@@ -1315,7 +1315,7 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
|
||||
}
|
||||
|
||||
func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
return s.getDescendantCountsFromApiServer(ctx, q)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"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/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||
@@ -659,71 +658,43 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
|
||||
return folder.ErrFolderNotEmpty.Errorf("folder contains %d alert rules", alertRulesInFolder)
|
||||
}
|
||||
|
||||
// if dashboard restore is on we don't delete public dashboards, the hard delete will take care of it later
|
||||
if !s.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards
|
||||
var dashboardUIDs []string
|
||||
// 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) {
|
||||
request := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Labels: []*resource.Requirement{},
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_FOLDER,
|
||||
Operator: string(selection.In),
|
||||
Values: folders,
|
||||
},
|
||||
},
|
||||
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards -
|
||||
// we cannot use the dashboard service directly due to circular dependencies, so use the search client to get the dashboards
|
||||
request := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Labels: []*resource.Requirement{},
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_FOLDER,
|
||||
Operator: string(selection.In),
|
||||
Values: folders,
|
||||
},
|
||||
Limit: 100000}
|
||||
},
|
||||
},
|
||||
Limit: 100000}
|
||||
|
||||
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
|
||||
hits, err := dashboardsearch.ParseResults(res, 0)
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
dashboardUIDs = make([]string, len(hits.Hits))
|
||||
for i, dashboard := range hits.Hits {
|
||||
dashboardUIDs[i] = dashboard.Name
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dashes, err := s.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: cmd.SignedInUser,
|
||||
FolderUIDs: folders,
|
||||
OrgId: cmd.OrgID,
|
||||
Type: searchstore.TypeDashboard,
|
||||
})
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
dashboardUIDs = make([]string, len(dashes))
|
||||
for i, dashboard := range dashes {
|
||||
dashboardUIDs[i] = dashboard.UID
|
||||
err = s.dashboardStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{
|
||||
UID: dashboard.UID,
|
||||
OrgID: cmd.OrgID,
|
||||
})
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete all public dashboards in the folders
|
||||
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
|
||||
hits, err := dashboardsearch.ParseResults(res, 0)
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
dashboardUIDs := make([]string, len(hits.Hits))
|
||||
for i, dashboard := range hits.Hits {
|
||||
dashboardUIDs[i] = dashboard.Name
|
||||
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
|
||||
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
|
||||
}
|
||||
}
|
||||
// Delete all public dashboards in the folders
|
||||
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = s.unifiedStore.Delete(ctx, folders, cmd.OrgID)
|
||||
|
||||
@@ -179,7 +179,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
}
|
||||
|
||||
featuresArr := []any{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2}
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders}
|
||||
features := featuremgmt.WithFeatures(featuresArr...)
|
||||
|
||||
dashboardStore := dashboards.NewFakeDashboardStore(t)
|
||||
@@ -206,6 +206,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
|
||||
publicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
|
||||
|
||||
fakeK8sClient := new(client.MockK8sHandler)
|
||||
folderService := &Service{
|
||||
log: slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"),
|
||||
unifiedStore: unifiedStore,
|
||||
@@ -216,7 +217,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
k8sclient: k8sCli,
|
||||
dashboardStore: dashboardStore,
|
||||
dashboardK8sClient: fakeK8sClient,
|
||||
publicDashboardService: publicDashboardService,
|
||||
}
|
||||
|
||||
@@ -348,7 +349,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).Once()
|
||||
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
|
||||
publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
|
||||
@@ -362,6 +363,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
|
||||
t.Run("When deleting folder by uid, expectedForceDeleteRules as false, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
|
||||
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
|
||||
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
|
||||
|
||||
expectedForceDeleteRules := false
|
||||
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
|
||||
@@ -375,6 +377,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
||||
|
||||
t.Run("When deleting folder by uid, expectedForceDeleteRules as true, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
|
||||
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
|
||||
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
|
||||
|
||||
expectedForceDeleteRules := true
|
||||
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
|
||||
@@ -526,7 +529,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
|
||||
}
|
||||
service := Service{
|
||||
k8sclient: fakeK8sClient,
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
|
||||
unifiedStore: folderStore,
|
||||
}
|
||||
user := &user.SignedInUser{OrgID: 1}
|
||||
@@ -779,7 +782,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
dashboardStore: dashboardStore,
|
||||
publicDashboardService: publicDashboardFakeService,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
|
||||
}
|
||||
user := &user.SignedInUser{OrgID: 1}
|
||||
ctx := identity.WithRequester(context.Background(), user)
|
||||
@@ -798,52 +801,22 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
require.NoError(t, service.RegisterService(alertingStore))
|
||||
|
||||
t.Run("Should delete folder", func(t *testing.T) {
|
||||
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{}).Return(nil).Once()
|
||||
dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid",
|
||||
UID: "uid1",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Should delete dashboards and public dashboards within the folder", func(t *testing.T) {
|
||||
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
|
||||
{
|
||||
UID: "test",
|
||||
OrgID: 1,
|
||||
},
|
||||
{
|
||||
UID: "test2",
|
||||
OrgID: 1,
|
||||
},
|
||||
}, nil).Once()
|
||||
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
|
||||
UID: "test",
|
||||
OrgID: 1,
|
||||
}).Return(nil).Once()
|
||||
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
|
||||
UID: "test2",
|
||||
OrgID: 1,
|
||||
}).Return(nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dashboardStore.AssertExpectations(t)
|
||||
dashboardK8sclient.AssertExpectations(t)
|
||||
publicDashboardFakeService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
// enable k8s ff for dashboards, retest
|
||||
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) {
|
||||
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()
|
||||
t.Run("Should delete folders, dashboards, and public dashboards within the folder", func(t *testing.T) {
|
||||
fakeFolderStore.ExpectedFolders = []*folder.Folder{{UID: "uid2", ID: 2}}
|
||||
dashboardK8sclient.On("Delete", mock.Anything, "test", int64(1), mock.Anything).Return(nil).Once()
|
||||
dashboardK8sclient.On("Delete", mock.Anything, "test2", int64(1), mock.Anything).Return(nil).Once()
|
||||
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Labels: []*resource.Requirement{},
|
||||
@@ -851,7 +824,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_FOLDER,
|
||||
Operator: string(selection.In),
|
||||
Values: []string{"uid1"},
|
||||
Values: []string{"uid", "uid2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -870,11 +843,21 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid1",
|
||||
Resource: "folder",
|
||||
Name: "test",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("folder1"),
|
||||
[]byte("uid"),
|
||||
[]byte(""),
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "test2",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("uid2"),
|
||||
[]byte(""),
|
||||
},
|
||||
},
|
||||
@@ -882,13 +865,14 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
},
|
||||
TotalHits: 1,
|
||||
}, nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid1",
|
||||
UID: "uid",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dashboardStore.AssertExpectations(t)
|
||||
publicDashboardFakeService.AssertExpectations(t)
|
||||
dashboardK8sclient.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
|
||||
sb.Write(`(SELECT COUNT(DISTINCT (` + dialect.Quote("rule_group") + `)) FROM ` + dialect.Quote("alert_rule") + `) AS rule_groups,`)
|
||||
}
|
||||
// currently not supported when dashboards are in unified storage
|
||||
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
|
||||
sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanStr(false))
|
||||
sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanStr(false))
|
||||
}
|
||||
|
||||
@@ -61,9 +61,14 @@ target = all
|
||||
protocol = https
|
||||
|
||||
[feature_toggles]
|
||||
; store folders in k8s
|
||||
kubernetesFolders = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
kubernetesClientDashboardsFolders = true
|
||||
|
||||
[unified_storage.folders.folder.grafana.app]
|
||||
dualWriterMode = 4
|
||||
|
||||
[unified_storage.dashboards.dashboard.grafana.app]
|
||||
dualWriterMode = 4
|
||||
|
||||
[grafana-apiserver]
|
||||
; use unified storage for k8s apiserver
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
@@ -139,7 +139,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
}))
|
||||
})
|
||||
@@ -155,7 +155,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
@@ -172,7 +172,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
@@ -189,7 +189,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
@@ -206,7 +206,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
featuremgmt.FlagNestedFolders,
|
||||
},
|
||||
}))
|
||||
@@ -577,7 +577,7 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -679,7 +679,7 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -856,7 +856,7 @@ func TestFoldersCreateAPIEndpointK8S(t *testing.T) {
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1026,7 +1026,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ func TestIntegrationOpenAPIs(t *testing.T) {
|
||||
h := NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2, // Will be default on by G12
|
||||
featuremgmt.FlagQueryService, // Query Library
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders, // Will be default on by G12
|
||||
featuremgmt.FlagQueryService, // Query Library
|
||||
featuremgmt.FlagProvisioning,
|
||||
featuremgmt.FlagInvestigationsBackend,
|
||||
},
|
||||
|
||||
@@ -35,8 +35,7 @@ func TestIntegrationProvisioning(t *testing.T) {
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagProvisioning,
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagKubernetesCliDashboards,
|
||||
featuremgmt.FlagKubernetesFoldersServiceV2,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
||||
"dashboards.dashboard.grafana.app": {
|
||||
|
||||
@@ -373,7 +373,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
|
||||
public onRestore = async (version: DecoratedRevisionModel): Promise<boolean> => {
|
||||
let versionRsp;
|
||||
if (config.featureToggles.kubernetesCliDashboards) {
|
||||
if (config.featureToggles.kubernetesClientDashboardsFolders) {
|
||||
// the id here is the resource version in k8s, use this instead to get the specific version
|
||||
versionRsp = await historySrv.restoreDashboard(version.uid, version.id);
|
||||
} else {
|
||||
|
||||
@@ -136,7 +136,7 @@ export class VersionsEditView extends SceneObjectBase<VersionsEditViewState> imp
|
||||
return;
|
||||
}
|
||||
let lhs, rhs;
|
||||
if (config.featureToggles.kubernetesCliDashboards) {
|
||||
if (config.featureToggles.kubernetesClientDashboardsFolders) {
|
||||
// the id here is the resource version in k8s, use this instead to get the specific version
|
||||
lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id);
|
||||
rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id);
|
||||
|
||||
@@ -94,7 +94,7 @@ export class VersionsSettings extends PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
let lhs, rhs;
|
||||
if (config.featureToggles.kubernetesCliDashboards) {
|
||||
if (config.featureToggles.kubernetesClientDashboardsFolders) {
|
||||
// the id here is the resource version in k8s, use this instead to get the specific version
|
||||
lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.id);
|
||||
rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.id);
|
||||
|
||||
@@ -19,7 +19,8 @@ const restoreDashboard = async (version: number, dashboard: DashboardModel) => {
|
||||
export const useDashboardRestore = (id: number, version: number) => {
|
||||
const dashboard = useSelector((state) => state.dashboard.getModel());
|
||||
const [state, onRestoreDashboard] = useAsyncFn(
|
||||
async () => await restoreDashboard(config.featureToggles.kubernetesCliDashboards ? id : version, dashboard!),
|
||||
async () =>
|
||||
await restoreDashboard(config.featureToggles.kubernetesClientDashboardsFolders ? id : version, dashboard!),
|
||||
[]
|
||||
);
|
||||
const notifyApp = useAppNotification();
|
||||
|
||||
Reference in New Issue
Block a user