Provisioning: Show file path of provisioning file in save/delete dialogs (#16706)

* Add file path to metadata and show it in dialogs

* Make path relative to config directory

* Fix tests

* Add test for the relative path

* Refactor to use path relative to provisioner path

* Change return types

* Rename attribute

* Small fixes from review
This commit is contained in:
Andrej Ocenas
2019-04-30 13:32:18 +02:00
committed by GitHub
parent 76ab0aa059
commit eb82a75668
23 changed files with 285 additions and 134 deletions
+18 -8
View File
@@ -24,6 +24,7 @@ type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardId int64) error
DeleteProvisionedDashboard(dashboardId int64, orgId int64) error
}
@@ -37,7 +38,9 @@ var NewService = func() DashboardService {
// NewProvisioningService factory for creating a new dashboard provisioning service
var NewProvisioningService = func() DashboardProvisioningService {
return &dashboardServiceImpl{}
return &dashboardServiceImpl{
log: log.New("dashboard-provisioning-service"),
}
}
type SaveDashboardDTO struct {
@@ -65,6 +68,16 @@ func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*mod
return cmd.Result, nil
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashboardId}
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}
return cmd.Result, nil
}
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard
@@ -123,14 +136,12 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
}
if validateProvisionedDashboard {
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dash.Id}
err := bus.Dispatch(isDashboardProvisioned)
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dash.Id)
if err != nil {
return nil, err
}
if isDashboardProvisioned.Result {
if provisionedData != nil {
return nil, models.ErrDashboardCannotSaveProvisionedDashboard
}
}
@@ -258,13 +269,12 @@ func (dr *dashboardServiceImpl) DeleteProvisionedDashboard(dashboardId int64, or
func (dr *dashboardServiceImpl) deleteDashboard(dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
if validateProvisionedDashboard {
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dashboardId}
err := bus.Dispatch(isDashboardProvisioned)
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardId(dashboardId)
if err != nil {
return errutil.Wrap("failed to check if dashboard is provisioned", err)
}
if isDashboardProvisioned.Result {
if provisionedData != nil {
return models.ErrDashboardCannotDeleteProvisionedDashboard
}
}
@@ -55,8 +55,8 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
@@ -85,9 +85,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
@@ -109,8 +109,8 @@ func TestDashboardService(t *testing.T) {
})
Convey("Should return validation error if alert data is invalid", func() {
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
@@ -129,9 +129,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should not return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
@@ -166,9 +166,9 @@ func TestDashboardService(t *testing.T) {
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = true
cmd.Result = &models.DashboardProvisioning{}
return nil
})
@@ -241,8 +241,12 @@ type Result struct {
}
func setupDeleteHandlers(provisioned bool) *Result {
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = provisioned
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
if provisioned {
cmd.Result = &models.DashboardProvisioning{}
} else {
cmd.Result = nil
}
return nil
})
@@ -112,8 +112,9 @@ func TestFolderService(t *testing.T) {
provisioningValidated := false
bus.AddHandler("test", func(query *models.IsDashboardProvisionedQuery) error {
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
query.Result = nil
return nil
})
@@ -7,18 +7,11 @@ import (
"github.com/pkg/errors"
)
type DashboardProvisioner interface {
Provision() error
PollChanges(ctx context.Context)
}
type DashboardProvisionerImpl struct {
log log.Logger
fileReaders []*fileReader
}
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
func NewDashboardProvisionerImpl(configDirectory string) (*DashboardProvisionerImpl, error) {
logger := log.New("provisioning.dashboard")
cfgReader := &configReader{path: configDirectory, log: logger}
@@ -61,6 +54,17 @@ func (provider *DashboardProvisionerImpl) PollChanges(ctx context.Context) {
}
}
// GetProvisionerResolvedPath returns resolved path for the specified provisioner name. Can be used to generate
// relative path to provisioning file from it's external_id.
func (provider *DashboardProvisionerImpl) GetProvisionerResolvedPath(name string) string {
for _, reader := range provider.fileReaders {
if reader.Cfg.Name == name {
return reader.resolvedPath()
}
}
return ""
}
func getFileReaders(configs []*DashboardsAsConfig, logger log.Logger) ([]*fileReader, error) {
var readers []*fileReader
@@ -3,14 +3,16 @@ package dashboards
import "context"
type Calls struct {
Provision []interface{}
PollChanges []interface{}
Provision []interface{}
PollChanges []interface{}
GetProvisionerResolvedPath []interface{}
}
type DashboardProvisionerMock struct {
Calls *Calls
ProvisionFunc func() error
PollChangesFunc func(ctx context.Context)
Calls *Calls
ProvisionFunc func() error
PollChangesFunc func(ctx context.Context)
GetProvisionerResolvedPathFunc func(name string) string
}
func NewDashboardProvisionerMock() *DashboardProvisionerMock {
@@ -34,3 +36,12 @@ func (dpm *DashboardProvisionerMock) PollChanges(ctx context.Context) {
dpm.PollChangesFunc(ctx)
}
}
func (dpm *DashboardProvisionerMock) GetProvisionerResolvedPath(name string) string {
dpm.Calls.PollChanges = append(dpm.Calls.GetProvisionerResolvedPath, name)
if dpm.GetProvisionerResolvedPathFunc != nil {
return dpm.GetProvisionerResolvedPathFunc(name)
} else {
return ""
}
}
@@ -70,7 +70,7 @@ func (fr *fileReader) pollChanges(ctx context.Context) {
// to the database.
func (fr *fileReader) startWalkingDisk() error {
fr.log.Debug("Start walking disk", "path", fr.Path)
resolvedPath := fr.resolvePath(fr.Path)
resolvedPath := fr.resolvedPath()
if _, err := os.Stat(resolvedPath); err != nil {
if os.IsNotExist(err) {
return err
@@ -329,24 +329,23 @@ func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time,
}, nil
}
func (fr *fileReader) resolvePath(path string) string {
if _, err := os.Stat(path); os.IsNotExist(err) {
func (fr *fileReader) resolvedPath() string {
if _, err := os.Stat(fr.Path); os.IsNotExist(err) {
fr.log.Error("Cannot read directory", "error", err)
}
copy := path
path, err := filepath.Abs(path)
path, err := filepath.Abs(fr.Path)
if err != nil {
fr.log.Error("Could not create absolute path", "path", copy, "error", err)
fr.log.Error("Could not create absolute path", "path", fr.Path, "error", err)
}
path, err = filepath.EvalSymlinks(path)
if err != nil {
fr.log.Error("Failed to read content of symlinked path", "path", copy, "error", err)
fr.log.Error("Failed to read content of symlinked path", "path", fr.Path, "error", err)
}
if path == "" {
path = copy
path = fr.Path
fr.log.Info("falling back to original path due to EvalSymlink/Abs failure")
}
return path
@@ -33,7 +33,7 @@ func TestProvsionedSymlinkedFolder(t *testing.T) {
t.Errorf("expected err to be nil")
}
resolvedPath := reader.resolvePath(reader.Path)
resolvedPath := reader.resolvedPath()
if resolvedPath != want {
t.Errorf("got %s want %s", resolvedPath, want)
}
@@ -70,7 +70,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
So(err, ShouldBeNil)
resolvedPath := reader.resolvePath(reader.Path)
resolvedPath := reader.resolvedPath()
So(filepath.IsAbs(resolvedPath), ShouldBeTrue)
})
})
@@ -435,6 +435,10 @@ func (s *fakeDashboardProvisioningService) DeleteProvisionedDashboard(dashboardI
return nil
}
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardDataByDashboardId(dashboardId int64) (*models.DashboardProvisioning, error) {
return nil, nil
}
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
for _, d := range fakeService.getDashboard {
if d.Slug == cmd.Slug {
+16 -10
View File
@@ -15,9 +15,17 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
type DashboardProvisioner interface {
Provision() error
PollChanges(ctx context.Context)
GetProvisionerResolvedPath(name string) string
}
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
func init() {
registry.RegisterService(NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
func(path string) (DashboardProvisioner, error) {
return dashboards.NewDashboardProvisionerImpl(path)
},
notifiers.Provision,
@@ -25,14 +33,8 @@ func init() {
))
}
type ProvisioningService interface {
ProvisionDatasources() error
ProvisionNotifications() error
ProvisionDashboards() error
}
func NewProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
newDashboardProvisioner DashboardProvisionerFactory,
provisionNotifiers func(string) error,
provisionDatasources func(string) error,
) *provisioningServiceImpl {
@@ -48,8 +50,8 @@ type provisioningServiceImpl struct {
Cfg *setting.Cfg `inject:""`
log log.Logger
pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory
dashboardProvisioner dashboards.DashboardProvisioner
newDashboardProvisioner DashboardProvisionerFactory
dashboardProvisioner DashboardProvisioner
provisionNotifiers func(string) error
provisionDatasources func(string) error
mutex sync.Mutex
@@ -131,6 +133,10 @@ func (ps *provisioningServiceImpl) ProvisionDashboards() error {
return nil
}
func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string {
return ps.dashboardProvisioner.GetProvisionerResolvedPath(name)
}
func (ps *provisioningServiceImpl) cancelPolling() {
if ps.pollingCtxCancel != nil {
ps.log.Debug("Stop polling for dashboard changes")
@@ -0,0 +1,58 @@
package provisioning
type Calls struct {
ProvisionDatasources []interface{}
ProvisionNotifications []interface{}
ProvisionDashboards []interface{}
GetDashboardProvisionerResolvedPath []interface{}
}
type ProvisioningServiceMock struct {
Calls *Calls
ProvisionDatasourcesFunc func() error
ProvisionNotificationsFunc func() error
ProvisionDashboardsFunc func() error
GetDashboardProvisionerResolvedPathFunc func(name string) string
}
func NewProvisioningServiceMock() *ProvisioningServiceMock {
return &ProvisioningServiceMock{
Calls: &Calls{},
}
}
func (mock *ProvisioningServiceMock) ProvisionDatasources() error {
mock.Calls.ProvisionDatasources = append(mock.Calls.ProvisionDatasources, nil)
if mock.ProvisionDatasourcesFunc != nil {
return mock.ProvisionDatasourcesFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) ProvisionNotifications() error {
mock.Calls.ProvisionNotifications = append(mock.Calls.ProvisionNotifications, nil)
if mock.ProvisionNotificationsFunc != nil {
return mock.ProvisionNotificationsFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) ProvisionDashboards() error {
mock.Calls.ProvisionDashboards = append(mock.Calls.ProvisionDashboards, nil)
if mock.ProvisionDashboardsFunc != nil {
return mock.ProvisionDashboardsFunc()
} else {
return nil
}
}
func (mock *ProvisioningServiceMock) GetDashboardProvisionerResolvedPath(name string) string {
mock.Calls.GetDashboardProvisionerResolvedPath = append(mock.Calls.GetDashboardProvisionerResolvedPath, name)
if mock.GetDashboardProvisionerResolvedPathFunc != nil {
return mock.GetDashboardProvisionerResolvedPathFunc(name)
} else {
return ""
}
}
@@ -92,7 +92,7 @@ func setup() *serviceTestStruct {
}
serviceTest.service = NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
func(path string) (DashboardProvisioner, error) {
return serviceTest.mock, nil
},
nil,
@@ -19,16 +19,16 @@ type DashboardExtras struct {
Value string
}
func GetProvisionedDataByDashboardId(cmd *models.IsDashboardProvisionedQuery) error {
func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
result := &models.DashboardProvisioning{}
exist, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
if err != nil {
return err
}
cmd.Result = exist
if exist {
cmd.Result = result
}
return nil
}
@@ -65,20 +65,20 @@ func TestDashboardProvisioningTest(t *testing.T) {
})
Convey("Can query for one provisioned dashboard", func() {
query := &models.IsDashboardProvisionedQuery{DashboardId: cmd.Result.Id}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeTrue)
So(query.Result, ShouldNotBeNil)
})
Convey("Can query for none provisioned dashboard", func() {
query := &models.IsDashboardProvisionedQuery{DashboardId: 3000}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: 3000}
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
Convey("Deleting folder should delete provision meta data", func() {
@@ -89,11 +89,11 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(DeleteDashboard(deleteCmd), ShouldBeNil)
query := &models.IsDashboardProvisionedQuery{DashboardId: cmd.Result.Id}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
Convey("UnprovisionDashboard should delete provisioning metadata", func() {
@@ -103,11 +103,11 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(UnprovisionDashboard(unprovisionCmd), ShouldBeNil)
query := &models.IsDashboardProvisionedQuery{DashboardId: dashId}
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashId}
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse)
So(query.Result, ShouldBeNil)
})
})
})
@@ -27,8 +27,8 @@ func TestIntegratedDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
cmd.Result = false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})