mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #10865 from grafana/provisioning
Support deleting provisioned dashboards when file is removed
This commit is contained in:
commit
5af2d09fb3
@ -3,4 +3,4 @@
|
|||||||
# folder: ''
|
# folder: ''
|
||||||
# type: file
|
# type: file
|
||||||
# options:
|
# options:
|
||||||
# folder: /var/lib/grafana/dashboards
|
# path: /var/lib/grafana/dashboards
|
||||||
|
@ -232,7 +232,7 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dashItem := &dashboards.SaveDashboardItem{
|
dashItem := &dashboards.SaveDashboardDTO{
|
||||||
Dashboard: dash,
|
Dashboard: dash,
|
||||||
Message: cmd.Message,
|
Message: cmd.Message,
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
|
@ -17,15 +17,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type fakeDashboardRepo struct {
|
type fakeDashboardRepo struct {
|
||||||
inserted []*dashboards.SaveDashboardItem
|
inserted []*dashboards.SaveDashboardDTO
|
||||||
|
provisioned []*m.DashboardProvisioning
|
||||||
getDashboard []*m.Dashboard
|
getDashboard []*m.Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardItem) (*m.Dashboard, error) {
|
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
|
||||||
repo.inserted = append(repo.inserted, json)
|
repo.inserted = append(repo.inserted, json)
|
||||||
return json.Dashboard, nil
|
return json.Dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning) (*m.Dashboard, error) {
|
||||||
|
repo.inserted = append(repo.inserted, dto)
|
||||||
|
return dto.Dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
|
||||||
|
return repo.provisioned, nil
|
||||||
|
}
|
||||||
|
|
||||||
var fakeRepo *fakeDashboardRepo
|
var fakeRepo *fakeDashboardRepo
|
||||||
|
|
||||||
// This tests two main scenarios. If a user has access to execute an action on a dashboard:
|
// This tests two main scenarios. If a user has access to execute an action on a dashboard:
|
||||||
|
@ -69,6 +69,11 @@ type Dashboard struct {
|
|||||||
Data *simplejson.Json
|
Data *simplejson.Json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dashboard) SetId(id int64) {
|
||||||
|
d.Id = id
|
||||||
|
d.Data.Set("id", id)
|
||||||
|
}
|
||||||
|
|
||||||
// NewDashboard creates a new dashboard
|
// NewDashboard creates a new dashboard
|
||||||
func NewDashboard(title string) *Dashboard {
|
func NewDashboard(title string) *Dashboard {
|
||||||
dash := &Dashboard{}
|
dash := &Dashboard{}
|
||||||
@ -219,6 +224,21 @@ type SaveDashboardCommand struct {
|
|||||||
Result *Dashboard
|
Result *Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DashboardProvisioning struct {
|
||||||
|
Id int64
|
||||||
|
DashboardId int64
|
||||||
|
Name string
|
||||||
|
ExternalId string
|
||||||
|
Updated time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaveProvisionedDashboardCommand struct {
|
||||||
|
DashboardCmd *SaveDashboardCommand
|
||||||
|
DashboardProvisioning *DashboardProvisioning
|
||||||
|
|
||||||
|
Result *Dashboard
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteDashboardCommand struct {
|
type DeleteDashboardCommand struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
@ -271,6 +291,12 @@ type GetDashboardSlugByIdQuery struct {
|
|||||||
Result string
|
Result string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetProvisionedDashboardDataQuery struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Result []*DashboardProvisioning
|
||||||
|
}
|
||||||
|
|
||||||
type GetDashboardsBySlugQuery struct {
|
type GetDashboardsBySlugQuery struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Slug string
|
Slug string
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
SaveDashboard(*SaveDashboardItem) (*models.Dashboard, error)
|
SaveDashboard(*SaveDashboardDTO) (*models.Dashboard, error)
|
||||||
|
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||||
|
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var repositoryInstance Repository
|
var repositoryInstance Repository
|
||||||
@ -22,7 +24,7 @@ func SetRepository(rep Repository) {
|
|||||||
repositoryInstance = rep
|
repositoryInstance = rep
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveDashboardItem struct {
|
type SaveDashboardDTO struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
UserId int64
|
UserId int64
|
||||||
@ -33,15 +35,25 @@ type SaveDashboardItem struct {
|
|||||||
|
|
||||||
type DashboardRepository struct{}
|
type DashboardRepository struct{}
|
||||||
|
|
||||||
func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.Dashboard, error) {
|
func (dr *DashboardRepository) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||||
dashboard := json.Dashboard
|
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
|
||||||
|
err := bus.Dispatch(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardRepository) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
|
||||||
|
dashboard := dto.Dashboard
|
||||||
|
|
||||||
if dashboard.Title == "" {
|
if dashboard.Title == "" {
|
||||||
return nil, models.ErrDashboardTitleEmpty
|
return nil, models.ErrDashboardTitleEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
|
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
|
||||||
OrgId: json.OrgId,
|
OrgId: dto.OrgId,
|
||||||
Dashboard: dashboard,
|
Dashboard: dashboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,33 +61,77 @@ func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.D
|
|||||||
return nil, models.ErrDashboardContainsInvalidAlertData
|
return nil, models.ErrDashboardContainsInvalidAlertData
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := models.SaveDashboardCommand{
|
cmd := &models.SaveDashboardCommand{
|
||||||
Dashboard: dashboard.Data,
|
Dashboard: dashboard.Data,
|
||||||
Message: json.Message,
|
Message: dto.Message,
|
||||||
OrgId: json.OrgId,
|
OrgId: dto.OrgId,
|
||||||
Overwrite: json.Overwrite,
|
Overwrite: dto.Overwrite,
|
||||||
UserId: json.UserId,
|
UserId: dto.UserId,
|
||||||
FolderId: dashboard.FolderId,
|
FolderId: dashboard.FolderId,
|
||||||
IsFolder: dashboard.IsFolder,
|
IsFolder: dashboard.IsFolder,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !json.UpdatedAt.IsZero() {
|
if !dto.UpdatedAt.IsZero() {
|
||||||
cmd.UpdatedAt = json.UpdatedAt
|
cmd.UpdatedAt = dto.UpdatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bus.Dispatch(&cmd)
|
return cmd, nil
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (dr *DashboardRepository) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
|
||||||
alertCmd := alerting.UpdateDashboardAlertsCommand{
|
alertCmd := alerting.UpdateDashboardAlertsCommand{
|
||||||
OrgId: json.OrgId,
|
OrgId: dto.OrgId,
|
||||||
UserId: json.UserId,
|
UserId: dto.UserId,
|
||||||
Dashboard: cmd.Result,
|
Dashboard: cmd.Result,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||||
return nil, models.ErrDashboardFailedToUpdateAlertData
|
return models.ErrDashboardFailedToUpdateAlertData
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardRepository) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||||
|
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCmd := &models.SaveProvisionedDashboardCommand{
|
||||||
|
DashboardCmd: cmd,
|
||||||
|
DashboardProvisioning: provisioning,
|
||||||
|
}
|
||||||
|
|
||||||
|
// dashboard
|
||||||
|
err = bus.Dispatch(saveCmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//alerts
|
||||||
|
err = dr.updateAlerting(cmd, dto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dr *DashboardRepository) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||||
|
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bus.Dispatch(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dr.updateAlerting(cmd, dto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.Result, nil
|
return cmd.Result, nil
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package dashboards
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
||||||
gocache "github.com/patrickmn/go-cache"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dashboardCache struct {
|
|
||||||
internalCache *gocache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDashboardCache() *dashboardCache {
|
|
||||||
return &dashboardCache{internalCache: gocache.New(5*time.Minute, 30*time.Minute)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fr *dashboardCache) addDashboardCache(key string, json *dashboards.SaveDashboardItem) {
|
|
||||||
fr.internalCache.Add(key, json, time.Minute*10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fr *dashboardCache) getCache(key string) (*dashboards.SaveDashboardItem, bool) {
|
|
||||||
obj, exist := fr.internalCache.Get(key)
|
|
||||||
if !exist {
|
|
||||||
return nil, exist
|
|
||||||
}
|
|
||||||
|
|
||||||
dash, ok := obj.(*dashboards.SaveDashboardItem)
|
|
||||||
if !ok {
|
|
||||||
return nil, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
return dash, ok
|
|
||||||
}
|
|
@ -29,8 +29,6 @@ type fileReader struct {
|
|||||||
Path string
|
Path string
|
||||||
log log.Logger
|
log log.Logger
|
||||||
dashboardRepo dashboards.Repository
|
dashboardRepo dashboards.Repository
|
||||||
cache *dashboardCache
|
|
||||||
createWalk func(fr *fileReader, folderId int64) filepath.WalkFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
|
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
|
||||||
@ -54,24 +52,22 @@ func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReade
|
|||||||
Path: path,
|
Path: path,
|
||||||
log: log,
|
log: log,
|
||||||
dashboardRepo: dashboards.GetRepository(),
|
dashboardRepo: dashboards.GetRepository(),
|
||||||
cache: NewDashboardCache(),
|
|
||||||
createWalk: createWalkFn,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *fileReader) ReadAndListen(ctx context.Context) error {
|
func (fr *fileReader) ReadAndListen(ctx context.Context) error {
|
||||||
ticker := time.NewTicker(checkDiskForChangesInterval)
|
|
||||||
|
|
||||||
if err := fr.startWalkingDisk(); err != nil {
|
if err := fr.startWalkingDisk(); err != nil {
|
||||||
fr.log.Error("failed to search for dashboards", "error", err)
|
fr.log.Error("failed to search for dashboards", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(checkDiskForChangesInterval)
|
||||||
|
|
||||||
running := false
|
running := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if !running { // avoid walking the filesystem in parallel. incase fs is very slow.
|
if !running { // avoid walking the filesystem in parallel. in-case fs is very slow.
|
||||||
running = true
|
running = true
|
||||||
go func() {
|
go func() {
|
||||||
if err := fr.startWalkingDisk(); err != nil {
|
if err := fr.startWalkingDisk(); err != nil {
|
||||||
@ -98,7 +94,91 @@ func (fr *fileReader) startWalkingDisk() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Walk(fr.Path, fr.createWalk(fr, folderId))
|
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardRepo, fr.Cfg.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filesFoundOnDisk := map[string]os.FileInfo{}
|
||||||
|
err = filepath.Walk(fr.Path, createWalkFn(filesFoundOnDisk))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find dashboards to delete since json file is missing
|
||||||
|
var dashboardToDelete []int64
|
||||||
|
for path, provisioningData := range provisionedDashboardRefs {
|
||||||
|
_, existsOnDisk := filesFoundOnDisk[path]
|
||||||
|
if !existsOnDisk {
|
||||||
|
dashboardToDelete = append(dashboardToDelete, provisioningData.DashboardId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete dashboard that are missing json file
|
||||||
|
for _, dashboardId := range dashboardToDelete {
|
||||||
|
fr.log.Debug("deleting provisioned dashboard. missing on disk", "id", dashboardId)
|
||||||
|
cmd := &models.DeleteDashboardCommand{OrgId: fr.Cfg.OrgId, Id: dashboardId}
|
||||||
|
err := bus.Dispatch(cmd)
|
||||||
|
if err != nil {
|
||||||
|
fr.log.Error("failed to delete dashboard", "id", cmd.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save dashboards based on json files
|
||||||
|
for path, fileInfo := range filesFoundOnDisk {
|
||||||
|
err = fr.saveDashboard(path, folderId, fileInfo, provisionedDashboardRefs)
|
||||||
|
if err != nil {
|
||||||
|
fr.log.Error("failed to save dashboard", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) error {
|
||||||
|
resolvedFileInfo, err := resolveSymlink(fileInfo, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
provisionedData, alreadyProvisioned := provisionedDashboardRefs[path]
|
||||||
|
if alreadyProvisioned && provisionedData.Updated.Unix() == resolvedFileInfo.ModTime().Unix() {
|
||||||
|
return nil // dashboard is already in sync with the database
|
||||||
|
}
|
||||||
|
|
||||||
|
dash, err := fr.readDashboardFromFile(path, resolvedFileInfo.ModTime(), folderId)
|
||||||
|
if err != nil {
|
||||||
|
fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dash.Dashboard.Id != 0 {
|
||||||
|
fr.log.Error("provisioned dashboard json files cannot contain id")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if alreadyProvisioned {
|
||||||
|
dash.Dashboard.SetId(provisionedData.DashboardId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.log.Debug("saving new dashboard", "file", path)
|
||||||
|
dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime()}
|
||||||
|
_, err = fr.dashboardRepo.SaveProvisionedDashboard(dash, dp)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||||
|
arr, err := repo.GetProvisionedDashboardData(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
byPath := map[string]*models.DashboardProvisioning{}
|
||||||
|
for _, pd := range arr {
|
||||||
|
byPath[pd.ExternalId] = pd
|
||||||
|
}
|
||||||
|
|
||||||
|
return byPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (int64, error) {
|
func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (int64, error) {
|
||||||
@ -115,7 +195,7 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (i
|
|||||||
|
|
||||||
// dashboard folder not found. create one.
|
// dashboard folder not found. create one.
|
||||||
if err == models.ErrDashboardNotFound {
|
if err == models.ErrDashboardNotFound {
|
||||||
dash := &dashboards.SaveDashboardItem{}
|
dash := &dashboards.SaveDashboardDTO{}
|
||||||
dash.Dashboard = models.NewDashboard(cfg.Folder)
|
dash.Dashboard = models.NewDashboard(cfg.Folder)
|
||||||
dash.Dashboard.IsFolder = true
|
dash.Dashboard.IsFolder = true
|
||||||
dash.Overwrite = true
|
dash.Overwrite = true
|
||||||
@ -129,83 +209,59 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (i
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !cmd.Result.IsFolder {
|
if !cmd.Result.IsFolder {
|
||||||
return 0, fmt.Errorf("Got invalid response. Expected folder, found dashboard")
|
return 0, fmt.Errorf("got invalid response. expected folder, found dashboard")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.Result.Id, nil
|
return cmd.Result.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWalkFn(fr *fileReader, folderId int64) filepath.WalkFunc {
|
func resolveSymlink(fileinfo os.FileInfo, path string) (os.FileInfo, error) {
|
||||||
|
checkFilepath, err := filepath.EvalSymlinks(path)
|
||||||
|
if path != checkFilepath {
|
||||||
|
path = checkFilepath
|
||||||
|
fi, err := os.Lstat(checkFilepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileinfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWalkFn(filesOnDisk map[string]os.FileInfo) filepath.WalkFunc {
|
||||||
return func(path string, fileInfo os.FileInfo, err error) error {
|
return func(path string, fileInfo os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fileInfo.IsDir() {
|
|
||||||
if strings.HasPrefix(fileInfo.Name(), ".") {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasSuffix(fileInfo.Name(), ".json") {
|
isValid, err := validateWalkablePath(fileInfo)
|
||||||
return nil
|
if !isValid {
|
||||||
}
|
|
||||||
|
|
||||||
checkFilepath, err := filepath.EvalSymlinks(path)
|
|
||||||
|
|
||||||
if path != checkFilepath {
|
|
||||||
path = checkFilepath
|
|
||||||
fi, err := os.Lstat(checkFilepath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileInfo = fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedDashboard, exist := fr.cache.getCache(path)
|
|
||||||
if exist && cachedDashboard.UpdatedAt == fileInfo.ModTime() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dash, err := fr.readDashboardFromFile(path, folderId)
|
|
||||||
if err != nil {
|
|
||||||
fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if dash.Dashboard.Id != 0 {
|
|
||||||
fr.log.Error("Cannot provision dashboard. Please remove the id property from the json file")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &models.GetDashboardQuery{Slug: dash.Dashboard.Slug}
|
|
||||||
err = bus.Dispatch(cmd)
|
|
||||||
|
|
||||||
// if we don't have the dashboard in the db, save it!
|
|
||||||
if err == models.ErrDashboardNotFound {
|
|
||||||
fr.log.Debug("saving new dashboard", "file", path)
|
|
||||||
_, err = fr.dashboardRepo.SaveDashboard(dash)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
filesOnDisk[path] = fileInfo
|
||||||
fr.log.Error("failed to query for dashboard", "slug", dash.Dashboard.Slug, "error", err)
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// break if db version is newer then fil version
|
|
||||||
if cmd.Result.Updated.Unix() >= fileInfo.ModTime().Unix() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fr.log.Debug("loading dashboard from disk into database.", "file", path)
|
|
||||||
_, err = fr.dashboardRepo.SaveDashboard(dash)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *fileReader) readDashboardFromFile(path string, folderId int64) (*dashboards.SaveDashboardItem, error) {
|
func validateWalkablePath(fileInfo os.FileInfo) (bool, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
if strings.HasPrefix(fileInfo.Name(), ".") {
|
||||||
|
return false, filepath.SkipDir
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(fileInfo.Name(), ".json") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time, folderId int64) (*dashboards.SaveDashboardDTO, error) {
|
||||||
reader, err := os.Open(path)
|
reader, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -217,17 +273,10 @@ func (fr *fileReader) readDashboardFromFile(path string, folderId int64) (*dashb
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := os.Stat(path)
|
dash, err := createDashboardJson(data, lastModified, fr.Cfg, folderId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dash, err := createDashboardJson(data, stat.ModTime(), fr.Cfg, folderId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fr.cache.addDashboardCache(path, dash)
|
|
||||||
|
|
||||||
return dash, nil
|
return dash, nil
|
||||||
}
|
}
|
||||||
|
@ -62,25 +62,8 @@ func TestDashboardFileReader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
So(dashboards, ShouldEqual, 2)
|
|
||||||
So(folders, ShouldEqual, 1)
|
So(folders, ShouldEqual, 1)
|
||||||
})
|
So(dashboards, ShouldEqual, 2)
|
||||||
|
|
||||||
Convey("Should not update dashboards when db is newer", func() {
|
|
||||||
cfg.Options["path"] = oneDashboard
|
|
||||||
|
|
||||||
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
|
|
||||||
Updated: time.Now().Add(time.Hour),
|
|
||||||
Slug: "grafana",
|
|
||||||
})
|
|
||||||
|
|
||||||
reader, err := NewDashboardFileReader(cfg, logger)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
err = reader.startWalkingDisk()
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
So(len(fakeRepo.inserted), ShouldEqual, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Can read default dashboard and replace old version in database", func() {
|
Convey("Can read default dashboard and replace old version in database", func() {
|
||||||
@ -161,26 +144,15 @@ func TestDashboardFileReader(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("Walking the folder with dashboards", func() {
|
Convey("Walking the folder with dashboards", func() {
|
||||||
cfg := &DashboardsAsConfig{
|
noFiles := map[string]os.FileInfo{}
|
||||||
Name: "Default",
|
|
||||||
Type: "file",
|
|
||||||
OrgId: 1,
|
|
||||||
Folder: "",
|
|
||||||
Options: map[string]interface{}{
|
|
||||||
"path": defaultDashboards,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
Convey("should skip dirs that starts with .", func() {
|
Convey("should skip dirs that starts with .", func() {
|
||||||
shouldSkip := reader.createWalk(reader, 0)("path", &FakeFileInfo{isDirectory: true, name: ".folder"}, nil)
|
shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: ".folder"}, nil)
|
||||||
So(shouldSkip, ShouldEqual, filepath.SkipDir)
|
So(shouldSkip, ShouldEqual, filepath.SkipDir)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("should keep walking if file is not .json", func() {
|
Convey("should keep walking if file is not .json", func() {
|
||||||
shouldSkip := reader.createWalk(reader, 0)("path", &FakeFileInfo{isDirectory: true, name: "folder"}, nil)
|
shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: "folder"}, nil)
|
||||||
So(shouldSkip, ShouldBeNil)
|
So(shouldSkip, ShouldBeNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -241,15 +213,26 @@ func (ffi FakeFileInfo) Sys() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fakeDashboardRepo struct {
|
type fakeDashboardRepo struct {
|
||||||
inserted []*dashboards.SaveDashboardItem
|
inserted []*dashboards.SaveDashboardDTO
|
||||||
|
provisioned []*models.DashboardProvisioning
|
||||||
getDashboard []*models.Dashboard
|
getDashboard []*models.Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardItem) (*models.Dashboard, error) {
|
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||||
repo.inserted = append(repo.inserted, json)
|
repo.inserted = append(repo.inserted, json)
|
||||||
return json.Dashboard, nil
|
return json.Dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||||
|
return repo.provisioned, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||||
|
repo.inserted = append(repo.inserted, dto)
|
||||||
|
repo.provisioned = append(repo.provisioned, provisioning)
|
||||||
|
return dto.Dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
|
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
|
||||||
for _, d := range fakeRepo.getDashboard {
|
for _, d := range fakeRepo.getDashboard {
|
||||||
if d.Slug == cmd.Slug {
|
if d.Slug == cmd.Slug {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"title": "Grafana",
|
"title": "Grafana1",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"timezone": "browser",
|
"timezone": "browser",
|
||||||
@ -170,4 +170,3 @@
|
|||||||
},
|
},
|
||||||
"version": 5
|
"version": 5
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"title": "Grafana",
|
"title": "Grafana2",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"timezone": "browser",
|
"timezone": "browser",
|
||||||
@ -170,4 +170,3 @@
|
|||||||
},
|
},
|
||||||
"version": 5
|
"version": 5
|
||||||
}
|
}
|
||||||
|
|
@ -18,8 +18,8 @@ type DashboardsAsConfig struct {
|
|||||||
Options map[string]interface{} `json:"options" yaml:"options"`
|
Options map[string]interface{} `json:"options" yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardItem, error) {
|
func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) {
|
||||||
dash := &dashboards.SaveDashboardItem{}
|
dash := &dashboards.SaveDashboardDTO{}
|
||||||
dash.Dashboard = models.NewDashboardFromJson(data)
|
dash.Dashboard = models.NewDashboardFromJson(data)
|
||||||
dash.UpdatedAt = lastModified
|
dash.UpdatedAt = lastModified
|
||||||
dash.Overwrite = true
|
dash.Overwrite = true
|
||||||
|
@ -30,120 +30,125 @@ var generateNewUid func() string = util.GenerateShortUid
|
|||||||
|
|
||||||
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||||
return inTransaction(func(sess *DBSession) error {
|
return inTransaction(func(sess *DBSession) error {
|
||||||
dash := cmd.GetDashboardModel()
|
return saveDashboard(sess, cmd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
|
func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||||
return err
|
dash := cmd.GetDashboardModel()
|
||||||
|
|
||||||
|
if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingByTitleAndFolder m.Dashboard
|
||||||
|
|
||||||
|
dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dashWithTitleAndFolderExists {
|
||||||
|
if dash.Id != existingByTitleAndFolder.Id {
|
||||||
|
if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
|
||||||
|
return m.ErrDashboardWithSameNameAsFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
|
||||||
|
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Overwrite {
|
||||||
|
dash.Id = existingByTitleAndFolder.Id
|
||||||
|
dash.Version = existingByTitleAndFolder.Version
|
||||||
|
|
||||||
|
if dash.Uid == "" {
|
||||||
|
dash.Uid = existingByTitleAndFolder.Uid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return m.ErrDashboardWithSameNameInFolderExists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var existingByTitleAndFolder m.Dashboard
|
if dash.Uid == "" {
|
||||||
|
uid, err := generateNewDashboardUid(sess, dash.OrgId)
|
||||||
dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
dash.Uid = uid
|
||||||
|
dash.Data.Set("uid", uid)
|
||||||
|
}
|
||||||
|
|
||||||
if dashWithTitleAndFolderExists {
|
err = setHasAcl(sess, dash)
|
||||||
if dash.Id != existingByTitleAndFolder.Id {
|
if err != nil {
|
||||||
if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
|
return err
|
||||||
return m.ErrDashboardWithSameNameAsFolder
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
|
parentVersion := dash.Version
|
||||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
affectedRows := int64(0)
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Overwrite {
|
if dash.Id == 0 {
|
||||||
dash.Id = existingByTitleAndFolder.Id
|
dash.Version = 1
|
||||||
dash.Version = existingByTitleAndFolder.Version
|
metrics.M_Api_Dashboard_Insert.Inc()
|
||||||
|
dash.Data.Set("version", dash.Version)
|
||||||
|
affectedRows, err = sess.Insert(dash)
|
||||||
|
} else {
|
||||||
|
dash.Version++
|
||||||
|
dash.Data.Set("version", dash.Version)
|
||||||
|
|
||||||
if dash.Uid == "" {
|
if !cmd.UpdatedAt.IsZero() {
|
||||||
dash.Uid = existingByTitleAndFolder.Uid
|
dash.Updated = cmd.UpdatedAt
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return m.ErrDashboardWithSameNameInFolderExists
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dash.Uid == "" {
|
affectedRows, err = sess.MustCols("folder_id", "has_acl").ID(dash.Id).Update(dash)
|
||||||
uid, err := generateNewDashboardUid(sess, dash.OrgId)
|
}
|
||||||
if err != nil {
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if affectedRows == 0 {
|
||||||
|
return m.ErrDashboardNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
dashVersion := &m.DashboardVersion{
|
||||||
|
DashboardId: dash.Id,
|
||||||
|
ParentVersion: parentVersion,
|
||||||
|
RestoredFrom: cmd.RestoredFrom,
|
||||||
|
Version: dash.Version,
|
||||||
|
Created: time.Now(),
|
||||||
|
CreatedBy: dash.UpdatedBy,
|
||||||
|
Message: cmd.Message,
|
||||||
|
Data: dash.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert version entry
|
||||||
|
if affectedRows, err = sess.Insert(dashVersion); err != nil {
|
||||||
|
return err
|
||||||
|
} else if affectedRows == 0 {
|
||||||
|
return m.ErrDashboardNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete existing tags
|
||||||
|
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert new tags
|
||||||
|
tags := dash.GetTags()
|
||||||
|
if len(tags) > 0 {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dash.Uid = uid
|
|
||||||
dash.Data.Set("uid", uid)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = setHasAcl(sess, dash)
|
cmd.Result = dash
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
parentVersion := dash.Version
|
return err
|
||||||
affectedRows := int64(0)
|
|
||||||
|
|
||||||
if dash.Id == 0 {
|
|
||||||
dash.Version = 1
|
|
||||||
metrics.M_Api_Dashboard_Insert.Inc()
|
|
||||||
dash.Data.Set("version", dash.Version)
|
|
||||||
affectedRows, err = sess.Insert(dash)
|
|
||||||
} else {
|
|
||||||
dash.Version++
|
|
||||||
dash.Data.Set("version", dash.Version)
|
|
||||||
|
|
||||||
if !cmd.UpdatedAt.IsZero() {
|
|
||||||
dash.Updated = cmd.UpdatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
affectedRows, err = sess.MustCols("folder_id", "has_acl").ID(dash.Id).Update(dash)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if affectedRows == 0 {
|
|
||||||
return m.ErrDashboardNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
dashVersion := &m.DashboardVersion{
|
|
||||||
DashboardId: dash.Id,
|
|
||||||
ParentVersion: parentVersion,
|
|
||||||
RestoredFrom: cmd.RestoredFrom,
|
|
||||||
Version: dash.Version,
|
|
||||||
Created: time.Now(),
|
|
||||||
CreatedBy: dash.UpdatedBy,
|
|
||||||
Message: cmd.Message,
|
|
||||||
Data: dash.Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert version entry
|
|
||||||
if affectedRows, err = sess.Insert(dashVersion); err != nil {
|
|
||||||
return err
|
|
||||||
} else if affectedRows == 0 {
|
|
||||||
return m.ErrDashboardNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete existing tags
|
|
||||||
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert new tags
|
|
||||||
tags := dash.GetTags()
|
|
||||||
if len(tags) > 0 {
|
|
||||||
for _, tag := range tags {
|
|
||||||
if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.Result = dash
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
|
func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
|
||||||
@ -456,6 +461,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
|||||||
"DELETE FROM dashboard_version WHERE dashboard_id = ?",
|
"DELETE FROM dashboard_version WHERE dashboard_id = ?",
|
||||||
"DELETE FROM dashboard WHERE folder_id = ?",
|
"DELETE FROM dashboard WHERE folder_id = ?",
|
||||||
"DELETE FROM annotation WHERE dashboard_id = ?",
|
"DELETE FROM annotation WHERE dashboard_id = ?",
|
||||||
|
"DELETE FROM dashboard_provisioning WHERE dashboard_id = ?",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sql := range deletes {
|
for _, sql := range deletes {
|
||||||
|
66
pkg/services/sqlstore/dashboard_provisioning.go
Normal file
66
pkg/services/sqlstore/dashboard_provisioning.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
|
||||||
|
bus.AddHandler("sql", SaveProvisionedDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardExtras struct {
|
||||||
|
Id int64
|
||||||
|
DashboardId int64
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
|
||||||
|
return inTransaction(func(sess *DBSession) error {
|
||||||
|
err := saveDashboard(sess, cmd.DashboardCmd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Result = cmd.DashboardCmd.Result
|
||||||
|
if cmd.DashboardProvisioning.Updated.IsZero() {
|
||||||
|
cmd.DashboardProvisioning.Updated = cmd.Result.Updated
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveProvionedData(sess, cmd.DashboardProvisioning, cmd.Result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveProvionedData(sess *DBSession, cmd *models.DashboardProvisioning, dashboard *models.Dashboard) error {
|
||||||
|
result := &models.DashboardProvisioning{}
|
||||||
|
|
||||||
|
exist, err := sess.Where("dashboard_id=?", dashboard.Id).Get(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Id = result.Id
|
||||||
|
cmd.DashboardId = dashboard.Id
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
_, err = sess.ID(result.Id).Update(cmd)
|
||||||
|
} else {
|
||||||
|
_, err = sess.Insert(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProvisionedDashboardDataQuery(cmd *models.GetProvisionedDashboardDataQuery) error {
|
||||||
|
var result []*models.DashboardProvisioning
|
||||||
|
|
||||||
|
if err := x.Where("name = ?", cmd.Name).Find(&result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Result = result
|
||||||
|
return nil
|
||||||
|
}
|
50
pkg/services/sqlstore/dashboard_provisioning_test.go
Normal file
50
pkg/services/sqlstore/dashboard_provisioning_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDashboardProvisioningTest(t *testing.T) {
|
||||||
|
Convey("Testing Dashboard provisioning", t, func() {
|
||||||
|
InitTestDB(t)
|
||||||
|
|
||||||
|
saveDashboardCmd := &models.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
FolderId: 0,
|
||||||
|
IsFolder: false,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test dashboard",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey("Saving dashboards with extras", func() {
|
||||||
|
cmd := &models.SaveProvisionedDashboardCommand{
|
||||||
|
DashboardCmd: saveDashboardCmd,
|
||||||
|
DashboardProvisioning: &models.DashboardProvisioning{
|
||||||
|
Name: "default",
|
||||||
|
ExternalId: "/var/grafana.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveProvisionedDashboard(cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cmd.Result, ShouldNotBeNil)
|
||||||
|
So(cmd.Result.Id, ShouldNotEqual, 0)
|
||||||
|
dashId := cmd.Result.Id
|
||||||
|
|
||||||
|
Convey("Can query for provisioned dashboards", func() {
|
||||||
|
query := &models.GetProvisionedDashboardDataQuery{Name: "default"}
|
||||||
|
err := GetProvisionedDashboardDataQuery(query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(query.Result), ShouldEqual, 1)
|
||||||
|
So(query.Result[0].DashboardId, ShouldEqual, dashId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -176,4 +176,20 @@ func addDashboardMigration(mg *Migrator) {
|
|||||||
Cols: []string{"org_id", "folder_id", "title"}, Type: UniqueIndex,
|
Cols: []string{"org_id", "folder_id", "title"}, Type: UniqueIndex,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
dashboardExtrasTable := Table{
|
||||||
|
Name: "dashboard_provisioning",
|
||||||
|
Columns: []*Column{
|
||||||
|
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||||
|
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
|
||||||
|
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||||
|
{Name: "external_id", Type: DB_Text, Nullable: false},
|
||||||
|
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||||
|
},
|
||||||
|
Indices: []*Index{
|
||||||
|
{Cols: []string{"dashboard_id"}},
|
||||||
|
{Cols: []string{"dashboard_id", "name"}, Type: IndexType},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mg.AddMigration("create dashboard_provisioning", NewAddTableMigration(dashboardExtrasTable))
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
@ -25,8 +23,6 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
|
|||||||
|
|
||||||
_, err := x.Insert(&playlist)
|
_, err := x.Insert(&playlist)
|
||||||
|
|
||||||
fmt.Printf("%v", playlist.Id)
|
|
||||||
|
|
||||||
playlistItems := make([]m.PlaylistItem, 0)
|
playlistItems := make([]m.PlaylistItem, 0)
|
||||||
for _, item := range cmd.Items {
|
for _, item := range cmd.Items {
|
||||||
playlistItems = append(playlistItems, m.PlaylistItem{
|
playlistItems = append(playlistItems, m.PlaylistItem{
|
||||||
|
Loading…
Reference in New Issue
Block a user