Provisioning: Fix dual write of folders (#80140)

* Provisioning: Store folders in folders table

* Solve linting issues

* Remove a comment
This commit is contained in:
Tania
2024-01-10 16:48:28 +01:00
committed by GitHub
parent 6be6724433
commit 744c1032ee
14 changed files with 342 additions and 153 deletions
+2 -1
View File
@@ -5,6 +5,7 @@ import (
alertmodels "github.com/grafana/grafana/pkg/services/alerting/models"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/model"
)
@@ -41,7 +42,7 @@ type DashboardProvisioningService interface {
GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error)
SaveFolderForProvisionedDashboards(context.Context, *SaveDashboardDTO) (*Dashboard, error)
SaveFolderForProvisionedDashboards(context.Context, *folder.CreateFolderCommand) (*folder.Folder, error)
SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *DashboardProvisioning) (*Dashboard, error)
UnprovisionDashboard(ctx context.Context, dashboardID int64) error
}
@@ -1,10 +1,11 @@
// Code generated by mockery v2.28.0. DO NOT EDIT.
// Code generated by mockery v2.38.0. DO NOT EDIT.
package dashboards
import (
context "context"
folder "github.com/grafana/grafana/pkg/services/folder"
mock "github.com/stretchr/testify/mock"
)
@@ -17,6 +18,10 @@ type FakeDashboardProvisioning struct {
func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for DeleteOrphanedProvisionedDashboards")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok {
r0 = rf(ctx, cmd)
@@ -31,6 +36,10 @@ func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx con
func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
ret := _m.Called(ctx, dashboardID, orgID)
if len(ret) == 0 {
panic("no return value specified for DeleteProvisionedDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, dashboardID, orgID)
@@ -45,6 +54,10 @@ func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Cont
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) {
ret := _m.Called(ctx, name)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDashboardData")
}
var r0 []*DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*DashboardProvisioning, error)); ok {
@@ -71,6 +84,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Con
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) {
ret := _m.Called(ctx, dashboardID)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDashboardDataByDashboardID")
}
var r0 *DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok {
@@ -97,6 +114,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ct
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) {
ret := _m.Called(ctx, orgID, dashboardUID)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDashboardDataByDashboardUID")
}
var r0 *DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok {
@@ -120,23 +141,27 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(c
}
// SaveFolderForProvisionedDashboards provides a mock function with given fields: _a0, _a1
func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *SaveDashboardDTO) (*Dashboard, error) {
func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *folder.CreateFolderCommand) (*folder.Folder, error) {
ret := _m.Called(_a0, _a1)
var r0 *Dashboard
if len(ret) == 0 {
panic("no return value specified for SaveFolderForProvisionedDashboards")
}
var r0 *folder.Folder
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) (*Dashboard, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, *folder.CreateFolderCommand) (*folder.Folder, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) *Dashboard); ok {
if rf, ok := ret.Get(0).(func(context.Context, *folder.CreateFolderCommand) *folder.Folder); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*Dashboard)
r0 = ret.Get(0).(*folder.Folder)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, *folder.CreateFolderCommand) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
@@ -149,6 +174,10 @@ func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 cont
func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *DashboardProvisioning) (*Dashboard, error) {
ret := _m.Called(ctx, dto, provisioning)
if len(ret) == 0 {
panic("no return value specified for SaveProvisionedDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, *DashboardProvisioning) (*Dashboard, error)); ok {
@@ -175,6 +204,10 @@ func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Contex
func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, dashboardID int64) error {
ret := _m.Called(ctx, dashboardID)
if len(ret) == 0 {
panic("no return value specified for UnprovisionDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, dashboardID)
@@ -185,13 +218,12 @@ func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, d
return r0
}
type mockConstructorTestingTNewFakeDashboardProvisioning interface {
// NewFakeDashboardProvisioning creates a new instance of FakeDashboardProvisioning. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFakeDashboardProvisioning(t interface {
mock.TestingT
Cleanup(func())
}
// NewFakeDashboardProvisioning creates a new instance of FakeDashboardProvisioning. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakeDashboardProvisioning(t mockConstructorTestingTNewFakeDashboardProvisioning) *FakeDashboardProvisioning {
}) *FakeDashboardProvisioning {
mock := &FakeDashboardProvisioning{}
mock.Mock.Test(t)
@@ -1,11 +1,13 @@
// Code generated by mockery v2.38.0. DO NOT EDIT.
package dashboards
import (
context "context"
identity "github.com/grafana/grafana/pkg/services/auth/identity"
mock "github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/services/auth/identity"
model "github.com/grafana/grafana/pkg/services/search/model"
)
@@ -18,6 +20,10 @@ type FakeDashboardService struct {
func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*SaveDashboardCommand, error) {
ret := _m.Called(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
if len(ret) == 0 {
panic("no return value specified for BuildSaveDashboardCommand")
}
var r0 *SaveDashboardCommand
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool, bool) (*SaveDashboardCommand, error)); ok {
@@ -40,23 +46,27 @@ func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, d
return r0, r1
}
// CountInFolder provides a mock function with given fields: ctx, orgID, folderUID, _a3
func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64, folderUID string, _a3 identity.Requester) (int64, error) {
ret := _m.Called(ctx, orgID, folderUID, _a3)
// CountInFolder provides a mock function with given fields: ctx, orgID, folderUID, user
func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64, folderUID string, user identity.Requester) (int64, error) {
ret := _m.Called(ctx, orgID, folderUID, user)
if len(ret) == 0 {
panic("no return value specified for CountInFolder")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64, string, identity.Requester) (int64, error)); ok {
return rf(ctx, orgID, folderUID, _a3)
return rf(ctx, orgID, folderUID, user)
}
if rf, ok := ret.Get(0).(func(context.Context, int64, string, identity.Requester) int64); ok {
r0 = rf(ctx, orgID, folderUID, _a3)
r0 = rf(ctx, orgID, folderUID, user)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, int64, string, identity.Requester) error); ok {
r1 = rf(ctx, orgID, folderUID, _a3)
r1 = rf(ctx, orgID, folderUID, user)
} else {
r1 = ret.Error(1)
}
@@ -68,6 +78,10 @@ func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64,
func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
ret := _m.Called(ctx, dashboardId, 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)
@@ -82,6 +96,10 @@ func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId
func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for FindDashboards")
}
var r0 []DashboardSearchProjection
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)); ok {
@@ -108,6 +126,10 @@ func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindP
func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardQuery) (*Dashboard, error)); ok {
@@ -134,6 +156,10 @@ func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *GetDash
func (_m *FakeDashboardService) GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardTags")
}
var r0 []*DashboardTagCloudItem
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error)); ok {
@@ -160,6 +186,10 @@ func (_m *FakeDashboardService) GetDashboardTags(ctx context.Context, query *Get
func (_m *FakeDashboardService) GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardUIDByID")
}
var r0 *DashboardRef
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) (*DashboardRef, error)); ok {
@@ -186,6 +216,10 @@ func (_m *FakeDashboardService) GetDashboardUIDByID(ctx context.Context, query *
func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboards")
}
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) ([]*Dashboard, error)); ok {
@@ -208,10 +242,44 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDas
return r0, r1
}
// GetDashboardsSharedWithUser provides a mock function with given fields: ctx, user
func (_m *FakeDashboardService) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) {
ret := _m.Called(ctx, user)
if len(ret) == 0 {
panic("no return value specified for GetDashboardsSharedWithUser")
}
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) ([]*Dashboard, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) []*Dashboard); ok {
r0 = rf(ctx, user)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*Dashboard)
}
}
if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ImportDashboard provides a mock function with given fields: ctx, dto
func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*Dashboard, error) {
ret := _m.Called(ctx, dto)
if len(ret) == 0 {
panic("no return value specified for ImportDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) (*Dashboard, error)); ok {
@@ -238,6 +306,10 @@ func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDa
func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error) {
ret := _m.Called(ctx, dto, allowUiUpdate)
if len(ret) == 0 {
panic("no return value specified for SaveDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool) (*Dashboard, error)); ok {
@@ -264,6 +336,10 @@ func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDash
func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for SearchDashboards")
}
var r0 model.HitList
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) (model.HitList, error)); ok {
@@ -286,38 +362,12 @@ func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *Fin
return r0, r1
}
func (_m *FakeDashboardService) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) {
ret := _m.Called(ctx, user)
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) ([]*Dashboard, error)); ok {
return rf(ctx, user)
}
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) []*Dashboard); ok {
r0 = rf(ctx, user)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*Dashboard)
}
}
if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) error); ok {
r1 = rf(ctx, user)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewFakeDashboardService interface {
// NewFakeDashboardService creates a new instance of FakeDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFakeDashboardService(t interface {
mock.TestingT
Cleanup(func())
}
// NewFakeDashboardService creates a new instance of FakeDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakeDashboardService(t mockConstructorTestingTNewFakeDashboardService) *FakeDashboardService {
}) *FakeDashboardService {
mock := &FakeDashboardService{}
mock.Mock.Test(t)
@@ -349,42 +349,17 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
return dash, nil
}
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*dashboards.Dashboard, error) {
dto.User = accesscontrol.BackgroundUser("dashboard_provisioning", dto.OrgID, org.RoleAdmin, provisionerPermissions)
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, false)
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *folder.CreateFolderCommand) (*folder.Folder, error) {
dto.SignedInUser = accesscontrol.BackgroundUser("dashboard_provisioning", dto.OrgID, org.RoleAdmin, provisionerPermissions)
f, err := dr.folderService.Create(ctx, dto)
if err != nil {
dr.log.Error("failed to create folder for provisioned dashboards", "folder", dto.Title, "org", dto.OrgID, "err", err)
return nil, err
}
dash, err := dr.dashboardStore.SaveDashboard(ctx, *cmd)
if err != nil {
return nil, err
}
dashAlertInfo := alerting.DashAlertInfo{
User: dto.User,
Dash: dash,
OrgID: dto.OrgID,
}
// extract/save legacy alerts only if legacy alerting is enabled
if setting.IsLegacyAlertingEnabled() {
alerts, err := dr.dashAlertExtractor.GetAlerts(ctx, dashAlertInfo)
if err != nil {
return nil, err
}
err = dr.dashboardStore.SaveAlerts(ctx, dash.ID, alerts)
if err != nil {
return nil, err
}
}
if dto.Dashboard.ID == 0 {
dr.setDefaultPermissions(ctx, dto, dash, true)
}
return dash, nil
dr.setDefaultFolderPermissions(ctx, dto, f, true)
return f, nil
}
func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO,
@@ -532,6 +507,35 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
}
}
func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) {
inFolder := f.ParentUID != ""
var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned {
namespaceID, userIDstr := cmd.SignedInUser.GetNamespacedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
dr.log.Error("Could not make user admin", "folder", cmd.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
})
}
}
if !inFolder {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()},
}...)
}
if _, err := dr.folderPermissions.SetPermissions(ctx, cmd.OrgID, f.UID, permissions...); err != nil {
dr.log.Error("Could not set default folder permissions", "folder", f.Title, "error", err)
}
}
func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
return dr.dashboardStore.GetDashboard(ctx, query)
}
+81 -6
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.28.0. DO NOT EDIT.
// Code generated by mockery v2.38.0. DO NOT EDIT.
package dashboards
@@ -20,6 +20,10 @@ type FakeDashboardStore struct {
func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParameters) (*quota.Map, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Count")
}
var r0 *quota.Map
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *quota.ScopeParameters) (*quota.Map, error)); ok {
@@ -46,6 +50,10 @@ func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParamet
func (_m *FakeDashboardStore) CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) {
ret := _m.Called(ctx, request)
if len(ret) == 0 {
panic("no return value specified for CountDashboardsInFolder")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *CountDashboardsInFolderRequest) (int64, error)); ok {
@@ -70,6 +78,10 @@ func (_m *FakeDashboardStore) CountDashboardsInFolder(ctx context.Context, reque
func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for DeleteDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *DeleteDashboardCommand) error); ok {
r0 = rf(ctx, cmd)
@@ -84,6 +96,10 @@ func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDa
func (_m *FakeDashboardStore) DeleteDashboardsInFolder(ctx context.Context, request *DeleteDashboardsInFolderRequest) error {
ret := _m.Called(ctx, request)
if len(ret) == 0 {
panic("no return value specified for DeleteDashboardsInFolder")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *DeleteDashboardsInFolderRequest) error); ok {
r0 = rf(ctx, request)
@@ -98,6 +114,10 @@ func (_m *FakeDashboardStore) DeleteDashboardsInFolder(ctx context.Context, requ
func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for DeleteOrphanedProvisionedDashboards")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok {
r0 = rf(ctx, cmd)
@@ -112,6 +132,10 @@ func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Co
func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for FindDashboards")
}
var r0 []DashboardSearchProjection
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)); ok {
@@ -138,6 +162,10 @@ func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPer
func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardQuery) (*Dashboard, error)); ok {
@@ -164,6 +192,10 @@ func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *GetDashbo
func (_m *FakeDashboardStore) GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardTags")
}
var r0 []*DashboardTagCloudItem
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error)); ok {
@@ -190,6 +222,10 @@ func (_m *FakeDashboardStore) GetDashboardTags(ctx context.Context, query *GetDa
func (_m *FakeDashboardStore) GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardUIDByID")
}
var r0 *DashboardRef
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) (*DashboardRef, error)); ok {
@@ -216,6 +252,10 @@ func (_m *FakeDashboardStore) GetDashboardUIDByID(ctx context.Context, query *Ge
func (_m *FakeDashboardStore) GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboards")
}
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) ([]*Dashboard, error)); ok {
@@ -242,6 +282,10 @@ func (_m *FakeDashboardStore) GetDashboards(ctx context.Context, query *GetDashb
func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query *GetDashboardsByPluginIDQuery) ([]*Dashboard, error) {
ret := _m.Called(ctx, query)
if len(ret) == 0 {
panic("no return value specified for GetDashboardsByPluginID")
}
var r0 []*Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsByPluginIDQuery) ([]*Dashboard, error)); ok {
@@ -268,6 +312,10 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query
func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) {
ret := _m.Called(ctx, name)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDashboardData")
}
var r0 []*DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*DashboardProvisioning, error)); ok {
@@ -294,6 +342,10 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, n
func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) {
ret := _m.Called(ctx, dashboardID)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDataByDashboardID")
}
var r0 *DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok {
@@ -320,6 +372,10 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Contex
func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) {
ret := _m.Called(ctx, orgID, dashboardUID)
if len(ret) == 0 {
panic("no return value specified for GetProvisionedDataByDashboardUID")
}
var r0 *DashboardProvisioning
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok {
@@ -346,6 +402,10 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Conte
func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
ret := _m.Called(ctx, dashID, alerts)
if len(ret) == 0 {
panic("no return value specified for SaveAlerts")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Alert) error); ok {
r0 = rf(ctx, dashID, alerts)
@@ -360,6 +420,10 @@ func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, aler
func (_m *FakeDashboardStore) SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error) {
ret := _m.Called(ctx, cmd)
if len(ret) == 0 {
panic("no return value specified for SaveDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, SaveDashboardCommand) (*Dashboard, error)); ok {
@@ -386,6 +450,10 @@ func (_m *FakeDashboardStore) SaveDashboard(ctx context.Context, cmd SaveDashboa
func (_m *FakeDashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd SaveDashboardCommand, provisioning *DashboardProvisioning) (*Dashboard, error) {
ret := _m.Called(ctx, cmd, provisioning)
if len(ret) == 0 {
panic("no return value specified for SaveProvisionedDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, SaveDashboardCommand, *DashboardProvisioning) (*Dashboard, error)); ok {
@@ -412,6 +480,10 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
if len(ret) == 0 {
panic("no return value specified for UnprovisionDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
@@ -426,6 +498,10 @@ func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64
func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashboard *Dashboard, overwrite bool) (bool, error) {
ret := _m.Called(ctx, dashboard, overwrite)
if len(ret) == 0 {
panic("no return value specified for ValidateDashboardBeforeSave")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *Dashboard, bool) (bool, error)); ok {
@@ -446,13 +522,12 @@ func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(ctx context.Context, d
return r0, r1
}
type mockConstructorTestingTNewFakeDashboardStore interface {
// NewFakeDashboardStore creates a new instance of FakeDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFakeDashboardStore(t interface {
mock.TestingT
Cleanup(func())
}
// NewFakeDashboardStore creates a new instance of FakeDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakeDashboardStore(t mockConstructorTestingTNewFakeDashboardStore) *FakeDashboardStore {
}) *FakeDashboardStore {
mock := &FakeDashboardStore{}
mock.Mock.Test(t)
+4 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/setting"
@@ -177,10 +178,12 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F
return folder.ErrDatabaseError.Errorf("failed to get folder: %w", err)
}
if !exists {
return folder.ErrFolderNotFound.Errorf("folder not found")
// embed dashboards.ErrFolderNotFound
return folder.ErrFolderNotFound.Errorf("%w", dashboards.ErrFolderNotFound)
}
return nil
})
return foldr.WithURL(), err
}
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
alert_models "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/util"
@@ -107,13 +108,12 @@ func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID(
// dashboard folder not found. create one.
if errors.Is(err, dashboards.ErrDashboardNotFound) {
dash := &dashboards.SaveDashboardDTO{}
dash.Dashboard = dashboards.NewDashboardFolder(folderName)
dash.Dashboard.IsFolder = true
dash.Overwrite = true
dash.OrgID = orgID
dash.Dashboard.SetUID(util.GenerateShortUID())
dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, dash)
createCmd := &folder.CreateFolderCommand{
OrgID: orgID,
UID: util.GenerateShortUID(),
Title: folderName,
}
dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, createCmd)
if err != nil {
return "", err
}
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"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/provisioning/utils"
)
@@ -23,7 +24,7 @@ type DashboardProvisioner interface {
}
// DashboardProvisionerFactory creates DashboardProvisioners based on input
type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService, org.Service, utils.DashboardStore) (DashboardProvisioner, error)
type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService, org.Service, utils.DashboardStore, folder.Service) (DashboardProvisioner, error)
// Provisioner is responsible for syncing dashboard from disk to Grafana's database.
type Provisioner struct {
@@ -39,7 +40,7 @@ func (provider *Provisioner) HasDashboardSources() bool {
}
// New returns a new DashboardProvisioner
func New(ctx context.Context, configDirectory string, provisioner dashboards.DashboardProvisioningService, orgService org.Service, dashboardStore utils.DashboardStore) (DashboardProvisioner, error) {
func New(ctx context.Context, configDirectory string, provisioner dashboards.DashboardProvisioningService, orgService org.Service, dashboardStore utils.DashboardStore, folderService folder.Service) (DashboardProvisioner, error) {
logger := log.New("provisioning.dashboard")
cfgReader := &configReader{path: configDirectory, log: logger, orgService: orgService}
configs, err := cfgReader.readConfig(ctx)
@@ -47,7 +48,7 @@ func New(ctx context.Context, configDirectory string, provisioner dashboards.Das
return nil, fmt.Errorf("%v: %w", "Failed to read dashboards config", err)
}
fileReaders, err := getFileReaders(configs, logger, provisioner, dashboardStore)
fileReaders, err := getFileReaders(configs, logger, provisioner, dashboardStore, folderService)
if err != nil {
return nil, fmt.Errorf("%v: %w", "Failed to initialize file readers", err)
}
@@ -66,6 +67,8 @@ func New(ctx context.Context, configDirectory string, provisioner dashboards.Das
// Provision scans the disk for dashboards and updates
// the database with the latest versions of those dashboards.
func (provider *Provisioner) Provision(ctx context.Context) error {
provider.log.Info("starting to provision dashboards")
for _, reader := range provider.fileReaders {
if err := reader.walkDisk(ctx); err != nil {
if os.IsNotExist(err) {
@@ -79,6 +82,7 @@ func (provider *Provisioner) Provision(ctx context.Context) error {
}
provider.duplicateValidator.validate()
provider.log.Info("finished to provision dashboards")
return nil
}
@@ -127,14 +131,24 @@ func (provider *Provisioner) GetAllowUIUpdatesFromConfig(name string) bool {
}
func getFileReaders(
configs []*config, logger log.Logger, service dashboards.DashboardProvisioningService, store utils.DashboardStore,
configs []*config,
logger log.Logger,
service dashboards.DashboardProvisioningService,
store utils.DashboardStore,
folderService folder.Service,
) ([]*FileReader, error) {
var readers []*FileReader
for _, config := range configs {
switch config.Type {
case "file":
fileReader, err := NewDashboardFileReader(config, logger.New("type", config.Type, "name", config.Name), service, store)
fileReader, err := NewDashboardFileReader(
config,
logger.New("type", config.Type, "name", config.Name),
service,
store,
folderService,
)
if err != nil {
return nil, fmt.Errorf("failed to create file reader for config %v: %w", config.Name, err)
}
@@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/provisioning/utils"
"github.com/grafana/grafana/pkg/util"
)
@@ -34,6 +35,7 @@ type FileReader struct {
dashboardProvisioningService dashboards.DashboardProvisioningService
dashboardStore utils.DashboardStore
FoldersFromFilesStructure bool
folderService folder.Service
mux sync.RWMutex
usageTracker *usageTracker
@@ -41,7 +43,8 @@ type FileReader struct {
}
// NewDashboardFileReader returns a new filereader based on `config`
func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.DashboardProvisioningService, dashboardStore utils.DashboardStore) (*FileReader, error) {
func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.DashboardProvisioningService,
dashboardStore utils.DashboardStore, folderService folder.Service) (*FileReader, error) {
var path string
path, ok := cfg.Options["path"].(string)
if !ok {
@@ -64,6 +67,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.Dash
log: log,
dashboardProvisioningService: service,
dashboardStore: dashboardStore,
folderService: folderService,
FoldersFromFilesStructure: foldersFromFilesStructure,
usageTracker: newUsageTracker(),
}, nil
@@ -333,22 +337,23 @@ func (fr *FileReader) getOrCreateFolder(ctx context.Context, cfg *config, servic
// dashboard folder not found. create one.
if errors.Is(err, dashboards.ErrDashboardNotFound) {
dash := &dashboards.SaveDashboardDTO{}
dash.Dashboard = dashboards.NewDashboardFolder(folderName)
dash.Dashboard.IsFolder = true
dash.Overwrite = true
dash.OrgID = cfg.OrgID
// set dashboard folderUid if given
if cfg.FolderUID == accesscontrol.GeneralFolderUID {
return 0, "", dashboards.ErrFolderInvalidUID
}
dash.Dashboard.SetUID(cfg.FolderUID)
dbDash, err := service.SaveFolderForProvisionedDashboards(ctx, dash)
createCmd := &folder.CreateFolderCommand{
OrgID: cfg.OrgID,
UID: cfg.FolderUID,
Title: folderName,
}
f, err := service.SaveFolderForProvisionedDashboards(ctx, createCmd)
if err != nil {
return 0, "", err
}
return dbDash.ID, dbDash.UID, nil
// nolint:staticcheck
return f.ID, f.UID, nil
}
if !result.IsFolder {
@@ -26,7 +26,7 @@ func TestProvisionedSymlinkedFolder(t *testing.T) {
Options: map[string]any{"path": symlinkedFolder},
}
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
if err != nil {
t.Error("expected err to be nil")
}
@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/util"
)
@@ -41,7 +42,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
t.Run("using path parameter", func(t *testing.T) {
cfg := setup()
cfg.Options["path"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
require.NoError(t, err)
require.NotEqual(t, reader.Path, "")
})
@@ -49,7 +50,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
t.Run("using folder as options", func(t *testing.T) {
cfg := setup()
cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
require.NoError(t, err)
require.NotEqual(t, reader.Path, "")
})
@@ -58,7 +59,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
cfg := setup()
cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
require.NoError(t, err)
require.NotEqual(t, reader.Path, "")
})
@@ -71,7 +72,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
}
cfg.Options["folder"] = fullPath
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
require.NoError(t, err)
require.Equal(t, reader.Path, fullPath)
@@ -81,7 +82,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
t.Run("using relative path", func(t *testing.T) {
cfg := setup()
cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil)
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil)
require.NoError(t, err)
resolvedPath := reader.resolvedPath()
@@ -113,10 +114,10 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Folder = "Team A"
fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, 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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -136,7 +137,7 @@ func TestDashboardFileReader(t *testing.T) {
inserted++
})
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -173,7 +174,7 @@ func TestDashboardFileReader(t *testing.T) {
fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -201,7 +202,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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -236,7 +237,7 @@ func TestDashboardFileReader(t *testing.T) {
fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -264,7 +265,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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -279,7 +280,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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -293,10 +294,10 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["foldersFromFilesStructure"] = true
fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2)
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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -313,7 +314,7 @@ func TestDashboardFileReader(t *testing.T) {
Folder: "",
}
_, err := NewDashboardFileReader(cfg, logger, nil, nil)
_, err := NewDashboardFileReader(cfg, logger, nil, nil, nil)
require.NotNil(t, err)
})
@@ -321,7 +322,7 @@ func TestDashboardFileReader(t *testing.T) {
setup()
cfg.Options["path"] = brokenDashboards
_, err := NewDashboardFileReader(cfg, logger, nil, nil)
_, err := NewDashboardFileReader(cfg, logger, nil, nil, nil)
require.NoError(t, err)
})
@@ -331,17 +332,17 @@ func TestDashboardFileReader(t *testing.T) {
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]any{"path": containingID}}
fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return(nil, nil).Times(2)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2)
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)
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -361,7 +362,7 @@ func TestDashboardFileReader(t *testing.T) {
"folder": defaultDashboards,
},
}
r, err := NewDashboardFileReader(cfg, logger, nil, nil)
r, err := NewDashboardFileReader(cfg, logger, nil, nil, nil)
require.NoError(t, err)
_, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
@@ -379,9 +380,9 @@ func TestDashboardFileReader(t *testing.T) {
"folder": defaultDashboards,
},
}
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Once()
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
_, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
@@ -401,7 +402,7 @@ func TestDashboardFileReader(t *testing.T) {
},
}
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
_, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
@@ -456,7 +457,7 @@ func TestDashboardFileReader(t *testing.T) {
cfg.DisableDeletion = true
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
reader.dashboardProvisioningService = fakeService
@@ -471,7 +472,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)
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
)
const (
@@ -34,9 +35,9 @@ func TestDuplicatesValidator(t *testing.T) {
const folderName = "duplicates-validator-folder"
fakeStore := &fakeDashboardStore{}
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(6)
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)
@@ -53,11 +54,11 @@ func TestDuplicatesValidator(t *testing.T) {
Options: map[string]any{"path": dashboardContainingUID},
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore)
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -90,7 +91,7 @@ func TestDuplicatesValidator(t *testing.T) {
const folderName = "duplicates-validator-folder"
fakeStore := &fakeDashboardStore{}
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
_, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
@@ -106,11 +107,11 @@ func TestDuplicatesValidator(t *testing.T) {
Options: map[string]any{"path": dashboardContainingUID},
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore)
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -150,7 +151,7 @@ func TestDuplicatesValidator(t *testing.T) {
})
t.Run("Duplicates validator should restrict write access only for readers with duplicates", func(t *testing.T) {
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(5)
fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(3)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5)
fakeStore := &fakeDashboardStore{}
@@ -167,15 +168,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)
reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore)
reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil)
reader3.dashboardProvisioningService = fakeService
require.NoError(t, err)
@@ -192,7 +193,7 @@ func TestDuplicatesValidator(t *testing.T) {
duplicates := duplicateValidator.getDuplicates()
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
require.NoError(t, err)
_, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg1.Folder)
require.NoError(t, err)
@@ -209,7 +210,7 @@ func TestDuplicatesValidator(t *testing.T) {
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"first"}, titleUsageReaders)
r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore)
r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil)
require.NoError(t, err)
_, folderUID, err = r.getOrCreateFolder(context.Background(), cfg3, fakeService, cfg3.Folder)
require.NoError(t, err)
+3 -1
View File
@@ -75,6 +75,7 @@ func ProvideService(
secretService: secrectService,
log: log.New("provisioning"),
orgService: orgService,
folderService: folderService,
}
return s, nil
}
@@ -145,6 +146,7 @@ type ProvisioningServiceImpl struct {
searchService searchV2.SearchService
quotaService quota.Service
secretService secrets.Service
folderService folder.Service
}
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
@@ -239,7 +241,7 @@ func (ps *ProvisioningServiceImpl) ProvisionNotifications(ctx context.Context) e
func (ps *ProvisioningServiceImpl) ProvisionDashboards(ctx context.Context) error {
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardProvisioningService, ps.orgService, ps.dashboardService)
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardProvisioningService, ps.orgService, ps.dashboardService, ps.folderService)
if err != nil {
return fmt.Errorf("%v: %w", "Failed to create provisioner", err)
}
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
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/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/utils"
@@ -95,7 +96,7 @@ func setup() *serviceTestStruct {
}
serviceTest.service = newProvisioningServiceImpl(
func(context.Context, string, dashboardstore.DashboardProvisioningService, org.Service, utils.DashboardStore) (dashboards.DashboardProvisioner, error) {
func(context.Context, string, dashboardstore.DashboardProvisioningService, org.Service, utils.DashboardStore, folder.Service) (dashboards.DashboardProvisioner, error) {
return serviceTest.mock, nil
},
nil,