diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index ae71f06eaf1a9..b226689986184 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -71,6 +71,8 @@ var serviceIdentityPermissions = getWildcardPermissions( "dashboards:write", "dashboards:create", "datasources:read", + "alert.provisioning:write", + "alert.provisioning.secrets:read", ) func IsServiceIdentity(ctx context.Context) bool { diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index ac221ba86e22d..17aa5aa16a9f5 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -8,6 +8,7 @@ import ( claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/storage/unified/resource" "google.golang.org/grpc" ) @@ -48,6 +49,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour Title: req.Query, Limit: req.Limit, // FolderUIDs: req.FolderUIDs, + Type: searchstore.TypeDashboard, SignedInUser: user, } diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index b48c4a6fdd1af..fbd3a209ff573 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -14,7 +14,10 @@ import ( "google.golang.org/grpc" ) -/* Temporarily disabled search fallback while we add functionality +/* +Search Fallback was returning both Folders and Dashboards which resulted +in issues with rendering the Folder UI. Also, filters are not implemented +yet. For those reasons, we will be disabling Search Fallback for now func TestSearchFallback(t *testing.T) { t.Run("should hit legacy search handler on mode 0", func(t *testing.T) { mockClient := &MockClient{} @@ -171,7 +174,8 @@ func TestSearchFallback(t *testing.T) { t.Fatalf("expected Search NOT to be called, but it was") } }) -}*/ +} +*/ func TestSearchHandlerFields(t *testing.T) { // Create a mock client diff --git a/pkg/server/server.go b/pkg/server/server.go index ca1263d9e4db3..a56b6c9f18498 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -131,7 +131,7 @@ func (s *Server) Init() error { return err } - return s.provisioningService.RunInitProvisioners(s.context) + return nil } // Run initializes and starts services. This will block until all services have diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index 5135793a796ec..ae1f579efd0bf 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -240,9 +240,9 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64 Key: folderkey, Fields: []*resource.Requirement{ { - Key: resource.SEARCH_FIELD_TITLE, + Key: resource.SEARCH_FIELD_TITLE_SORT, Operator: string(selection.In), - Values: []string{title}, + Values: []string{strings.ToLower(title)}, }, }, Labels: []*resource.Requirement{}, diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index 977300ed7fdf1..a19c83f209e30 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -567,7 +567,7 @@ func (r resourceClientMock) Search(ctx context.Context, in *resource.ResourceSea } if len(in.Options.Fields) > 0 && - in.Options.Fields[0].Key == resource.SEARCH_FIELD_TITLE && + in.Options.Fields[0].Key == resource.SEARCH_FIELD_TITLE_SORT && in.Options.Fields[0].Operator == "in" && len(in.Options.Fields[0].Values) > 0 && in.Options.Fields[0].Values[0] == "foo" { diff --git a/pkg/services/provisioning/alerting/rules_provisioner.go b/pkg/services/provisioning/alerting/rules_provisioner.go index 4baefedfebf3c..f41e48a94da5e 100644 --- a/pkg/services/provisioning/alerting/rules_provisioner.go +++ b/pkg/services/provisioning/alerting/rules_provisioner.go @@ -45,7 +45,8 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context, files []*AlertingFile) error { for _, file := range files { for _, group := range file.Groups { - u := provisionerUser(group.OrgID) + ctx, u := identity.WithServiceIdentitiy(ctx, group.OrgID) + folderUID, err := prov.getOrCreateFolderFullpath(ctx, group.FolderFullpath, group.OrgID) if err != nil { prov.logger.Error("failed to get or create folder", "folder", group.FolderFullpath, "org", group.OrgID, "err", err) @@ -120,11 +121,13 @@ func (prov *defaultAlertRuleProvisioner) getOrCreateFolderFullpath( func (prov *defaultAlertRuleProvisioner) getOrCreateFolderByTitle( ctx context.Context, folderName string, orgID int64, parentUID *string) (string, error) { + ctx, user := identity.WithServiceIdentitiy(ctx, orgID) + cmd := &folder.GetFolderQuery{ Title: &folderName, ParentUID: parentUID, OrgID: orgID, - SignedInUser: provisionerUser(orgID), + SignedInUser: user, } cmdResult, err := prov.folderService.Get(ctx, cmd) diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index e36bedda79e50..b7c54ddcdb09a 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -147,6 +148,8 @@ func (fr *FileReader) isDatabaseAccessRestricted() bool { // storeDashboardsInFolder saves dashboards from the filesystem on disk to the folder from config func (fr *FileReader) storeDashboardsInFolder(ctx context.Context, filesFoundOnDisk map[string]os.FileInfo, dashboardRefs map[string]*dashboards.DashboardProvisioning, usageTracker *usageTracker) error { + ctx, _ = identity.WithServiceIdentitiy(ctx, fr.Cfg.OrgID) + folderID, folderUID, err := fr.getOrCreateFolder(ctx, fr.Cfg, fr.dashboardProvisioningService, fr.Cfg.Folder) if err != nil && !errors.Is(err, ErrFolderNameMissing) { return fmt.Errorf("%w with name %q: %w", ErrGetOrCreateFolder, fr.Cfg.Folder, err) @@ -177,6 +180,7 @@ func (fr *FileReader) storeDashboardsInFoldersFromFileStructure(ctx context.Cont folderName = filepath.Base(dashboardsFolder) } + ctx, _ = identity.WithServiceIdentitiy(ctx, fr.Cfg.OrgID) folderID, folderUID, err := fr.getOrCreateFolder(ctx, fr.Cfg, fr.dashboardProvisioningService, folderName) if err != nil && !errors.Is(err, ErrFolderNameMissing) { return fmt.Errorf("%w with name %q from file system structure: %w", ErrGetOrCreateFolder, folderName, err) @@ -342,38 +346,42 @@ func (fr *FileReader) getOrCreateFolder(ctx context.Context, cfg *config, servic return 0, "", ErrFolderNameMissing } - // TODO use folder service instead + user, err := identity.GetRequester(ctx) + if err != nil { + return 0, "", err + } + metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Provisioning).Inc() - cmd := &dashboards.GetDashboardQuery{ - FolderID: util.Pointer(int64(0)), // nolint:staticcheck - OrgID: cfg.OrgID, + cmd := &folder.GetFolderQuery{ + OrgID: cfg.OrgID, + SignedInUser: user, } if cfg.FolderUID != "" { - cmd.UID = cfg.FolderUID + cmd.UID = &cfg.FolderUID } else { // provisioning depends on unique names //nolint:staticcheck cmd.Title = &folderName } - result, err := fr.dashboardStore.GetDashboard(ctx, cmd) - - if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { + result, err := fr.folderService.Get(ctx, cmd) + if err != nil && !errors.Is(err, dashboards.ErrFolderNotFound) { return 0, "", err } - // dashboard folder not found. create one. - if errors.Is(err, dashboards.ErrDashboardNotFound) { - // set dashboard folderUid if given - if cfg.FolderUID == accesscontrol.GeneralFolderUID { - return 0, "", dashboards.ErrFolderInvalidUID - } + // do not allow the creation of folder with uid "general" + if result != nil && result.UID == accesscontrol.GeneralFolderUID { + return 0, "", dashboards.ErrFolderInvalidUID + } + // dashboard folder not found. create one. + if errors.Is(err, dashboards.ErrFolderNotFound) { createCmd := &folder.CreateFolderCommand{ - OrgID: cfg.OrgID, - UID: cfg.FolderUID, - Title: folderName, + OrgID: cfg.OrgID, + UID: cfg.FolderUID, + Title: folderName, + SignedInUser: user, } f, err := service.SaveFolderForProvisionedDashboards(ctx, createCmd) @@ -385,10 +393,7 @@ func (fr *FileReader) getOrCreateFolder(ctx context.Context, cfg *config, servic return f.ID, f.UID, nil } - if !result.IsFolder { - return 0, "", fmt.Errorf("got invalid response. expected folder, found dashboard") - } - + //nolint:staticcheck return result.ID, result.UID, nil } diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go index ac2741c86cc95..52e9ac19e42bf 100644 --- a/pkg/services/provisioning/dashboards/file_reader_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_test.go @@ -12,9 +12,20 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/dashboards/database" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/folder/folderimpl" + "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" + "github.com/grafana/grafana/pkg/services/tag/tagimpl" + "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util" ) @@ -28,6 +39,10 @@ const ( configName = "default" ) +func TestMain(m *testing.M) { + testsuite.Run(m) +} + func TestCreatingNewDashboardFileReader(t *testing.T) { setup := func() *config { return &config{ @@ -107,6 +122,17 @@ func TestDashboardFileReader(t *testing.T) { } } + sql, cfgT := db.InitTestDBWithCfg(t) + features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders) + fStore := folderimpl.ProvideStore(sql) + tagService := tagimpl.ProvideService(sql) + dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService) + require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sql) + folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()), + dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), + supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest()) + t.Run("Reading dashboards from disk", func(t *testing.T) { t.Run("Can read default dashboard", func(t *testing.T) { setup() @@ -116,8 +142,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once() fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{ID: 1}, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 2}, nil).Times(2) - - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -137,7 +162,7 @@ func TestDashboardFileReader(t *testing.T) { inserted++ }) - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -174,7 +199,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -202,7 +227,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -237,7 +262,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -265,7 +290,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -280,7 +305,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -297,7 +322,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(2) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(3) - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -314,7 +339,7 @@ func TestDashboardFileReader(t *testing.T) { Folder: "", } - _, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) + _, err := NewDashboardFileReader(cfg, logger, nil, nil, folderSvc) require.NotNil(t, err) }) @@ -322,7 +347,7 @@ func TestDashboardFileReader(t *testing.T) { setup() cfg.Options["path"] = brokenDashboards - _, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) + _, err := NewDashboardFileReader(cfg, logger, nil, nil, folderSvc) require.NoError(t, err) }) @@ -335,14 +360,14 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(2) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2) - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, folderSvc) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) err = reader1.walkDisk(context.Background()) require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, folderSvc) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -362,7 +387,7 @@ func TestDashboardFileReader(t *testing.T) { "folder": defaultDashboards, }, } - r, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, nil, folderSvc) require.NoError(t, err) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) @@ -382,10 +407,12 @@ func TestDashboardFileReader(t *testing.T) { } fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{ID: 1}, nil).Once() - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) - _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) + ctx := context.Background() + ctx, _ = identity.WithServiceIdentitiy(ctx, 1) + _, _, err = r.getOrCreateFolder(ctx, cfg, fakeService, cfg.Folder) require.NoError(t, err) }) @@ -402,10 +429,12 @@ func TestDashboardFileReader(t *testing.T) { }, } - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) - _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) + ctx := context.Background() + ctx, _ = identity.WithServiceIdentitiy(ctx, 1) + _, _, err = r.getOrCreateFolder(ctx, cfg, fakeService, cfg.Folder) require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID) }) @@ -457,7 +486,7 @@ func TestDashboardFileReader(t *testing.T) { cfg.DisableDeletion = true - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) reader.dashboardProvisioningService = fakeService @@ -472,7 +501,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() fakeService.On("DeleteProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) reader.dashboardProvisioningService = fakeService require.NoError(t, err) diff --git a/pkg/services/provisioning/dashboards/validator_test.go b/pkg/services/provisioning/dashboards/validator_test.go index de8d438d02f14..788eb7e017a10 100644 --- a/pkg/services/provisioning/dashboards/validator_test.go +++ b/pkg/services/provisioning/dashboards/validator_test.go @@ -8,9 +8,19 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/dashboards/database" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/folder/folderimpl" + "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" + "github.com/grafana/grafana/pkg/services/tag/tagimpl" ) const ( @@ -31,16 +41,30 @@ func TestDuplicatesValidator(t *testing.T) { } logger := log.New("test.logger") + sql, cfgT := db.InitTestDBWithCfg(t) + features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders) + fStore := folderimpl.ProvideStore(sql) + tagService := tagimpl.ProvideService(sql) + dashStore, err := database.ProvideDashboardStore(sql, cfgT, features, tagService) + require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sql) + folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()), + dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), + supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest()) + t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) { const folderName = "duplicates-validator-folder" + ctx := context.Background() + ctx, _ = identity.WithServiceIdentitiy(ctx, 1) + fakeStore := &fakeDashboardStore{} - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(6) fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(4) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5) - _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName) + _, folderUID, err := r.getOrCreateFolder(ctx, cfg, fakeService, folderName) require.NoError(t, err) identity := dashboardIdentity{folderUID: folderUID, title: "Grafana"} @@ -54,11 +78,11 @@ func TestDuplicatesValidator(t *testing.T) { Options: map[string]any{"path": dashboardContainingUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, folderSvc) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, folderSvc) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -90,10 +114,13 @@ func TestDuplicatesValidator(t *testing.T) { t.Run("Duplicates validator should not collect info about duplicate UIDs and titles within folders for different orgs", func(t *testing.T) { const folderName = "duplicates-validator-folder" + ctx := context.Background() + ctx, _ = identity.WithServiceIdentitiy(ctx, 1) + fakeStore := &fakeDashboardStore{} - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) - _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName) + _, folderUID, err := r.getOrCreateFolder(ctx, cfg, fakeService, folderName) require.NoError(t, err) identity := dashboardIdentity{folderUID: folderUID, title: "Grafana"} @@ -107,11 +134,11 @@ func TestDuplicatesValidator(t *testing.T) { Options: map[string]any{"path": dashboardContainingUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, folderSvc) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, folderSvc) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -168,15 +195,15 @@ func TestDuplicatesValidator(t *testing.T) { Name: "third", Type: "file", OrgID: 2, Folder: "duplicates-validator-folder", Options: map[string]any{"path": twoDashboardsWithUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, folderSvc) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, folderSvc) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) - reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil) + reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore, folderSvc) reader3.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -193,9 +220,12 @@ func TestDuplicatesValidator(t *testing.T) { duplicates := duplicateValidator.getDuplicates() - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) + ctx := context.Background() + ctx, _ = identity.WithServiceIdentitiy(ctx, 1) + + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, folderSvc) require.NoError(t, err) - _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg1.Folder) + _, folderUID, err := r.getOrCreateFolder(ctx, cfg, fakeService, cfg1.Folder) require.NoError(t, err) identity := dashboardIdentity{folderUID: folderUID, title: "Grafana"} @@ -210,9 +240,9 @@ func TestDuplicatesValidator(t *testing.T) { sort.Strings(titleUsageReaders) require.Equal(t, []string{"first"}, titleUsageReaders) - r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil) + r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore, folderSvc) require.NoError(t, err) - _, folderUID, err = r.getOrCreateFolder(context.Background(), cfg3, fakeService, cfg3.Folder) + _, folderUID, err = r.getOrCreateFolder(ctx, cfg3, fakeService, cfg3.Folder) require.NoError(t, err) identity = dashboardIdentity{folderUID: folderUID, title: "Grafana"} diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 451b3a098e37d..fcc8a50c2f4d5 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -164,6 +164,7 @@ type ProvisioningServiceImpl struct { folderService folder.Service resourcePermissions accesscontrol.ReceiverPermissionsService tracer tracing.Tracer + onceInitProvisioners sync.Once } func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error { @@ -189,7 +190,19 @@ func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) erro } func (ps *ProvisioningServiceImpl) Run(ctx context.Context) error { - err := ps.ProvisionDashboards(ctx) + var err error + + // run Init Provisioners only once + ps.onceInitProvisioners.Do(func() { + err = ps.RunInitProvisioners(ctx) + }) + + if err != nil { + // error already logged + return err + } + + err = ps.ProvisionDashboards(ctx) if err != nil { ps.log.Error("Failed to provision dashboard", "error", err) // Consider the allow list of errors for which running the provisioning service should not diff --git a/pkg/services/provisioning/provisioning_test.go b/pkg/services/provisioning/provisioning_test.go index de84250512ada..beee1881a7007 100644 --- a/pkg/services/provisioning/provisioning_test.go +++ b/pkg/services/provisioning/provisioning_test.go @@ -13,7 +13,11 @@ import ( dashboardstore "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" + prov_alerting "github.com/grafana/grafana/pkg/services/provisioning/alerting" "github.com/grafana/grafana/pkg/services/provisioning/dashboards" + "github.com/grafana/grafana/pkg/services/provisioning/datasources" "github.com/grafana/grafana/pkg/services/provisioning/utils" "github.com/grafana/grafana/pkg/services/searchV2" ) @@ -159,10 +163,17 @@ func setup(t *testing.T) *serviceTestStruct { serviceTest.dashboardProvisionerInstantiations++ return serviceTest.mock, nil }, - nil, - nil, + func(context.Context, string, datasources.BaseDataSourceService, datasources.CorrelationsStore, org.Service) error { + return nil + }, + func(context.Context, string, pluginstore.Store, pluginsettings.Service, org.Service) error { + return nil + }, searchStub, ) + service.provisionAlerting = func(context.Context, prov_alerting.ProvisionerConfig) error { + return nil + } serviceTest.service = service require.NoError(t, err) diff --git a/pkg/storage/unified/resource/search_client.go b/pkg/storage/unified/resource/search_client.go index 2ecbd5ee2bb15..a9c9c95ef9d1a 100644 --- a/pkg/storage/unified/resource/search_client.go +++ b/pkg/storage/unified/resource/search_client.go @@ -4,17 +4,21 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// Search Fallback was returning both Folders and Dashboards which resulted +// in issues with rendering the Folder UI. Also, filters are not implemented +// yet. For those reasons, we will be disabling Search Fallback for now func NewSearchClient(cfg *setting.Cfg, unifiedStorageConfigKey string, unifiedClient ResourceIndexClient, legacyClient ResourceIndexClient) ResourceIndexClient { - /*config, ok := cfg.UnifiedStorage[unifiedStorageConfigKey] - if !ok { - return legacyClient - } + // config, ok := cfg.UnifiedStorage[unifiedStorageConfigKey] + // if !ok { + // return legacyClient + // } + + // switch config.DualWriterMode { + // case rest.Mode0, rest.Mode1, rest.Mode2: + // return legacyClient + // default: + // return unifiedClient + // } - switch config.DualWriterMode { - case rest.Mode0, rest.Mode1, rest.Mode2: - return legacyClient - default: - return unifiedClient - }*/ return unifiedClient }