file_reader.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package dashboard
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/grafana/grafana/pkg/services/alerting"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/grafana/grafana/pkg/bus"
  12. "github.com/grafana/grafana/pkg/components/simplejson"
  13. "github.com/grafana/grafana/pkg/log"
  14. "github.com/grafana/grafana/pkg/models"
  15. )
  16. type fileReader struct {
  17. Cfg *DashboardsAsConfig
  18. Path string
  19. log log.Logger
  20. dashboardCache *dashboardCache
  21. }
  22. type dashboardCache struct {
  23. mutex *sync.Mutex
  24. dashboards map[string]*DashboardJson
  25. }
  26. func newDashboardCache() *dashboardCache {
  27. return &dashboardCache{
  28. dashboards: map[string]*DashboardJson{},
  29. mutex: &sync.Mutex{},
  30. }
  31. }
  32. func (dc *dashboardCache) addCache(json *DashboardJson) {
  33. dc.mutex.Lock()
  34. defer dc.mutex.Unlock()
  35. dc.dashboards[json.Path] = json
  36. }
  37. func (dc *dashboardCache) getCache(path string) (*DashboardJson, bool) {
  38. dc.mutex.Lock()
  39. defer dc.mutex.Unlock()
  40. v, exist := dc.dashboards[path]
  41. return v, exist
  42. }
  43. func NewDashboardFilereader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
  44. path, ok := cfg.Options["folder"].(string)
  45. if !ok {
  46. return nil, fmt.Errorf("Failed to load dashboards. folder param is not a string")
  47. }
  48. if _, err := os.Stat(path); os.IsNotExist(err) {
  49. log.Error("Cannot read directory", "error", err)
  50. }
  51. return &fileReader{
  52. Cfg: cfg,
  53. Path: path,
  54. log: log,
  55. dashboardCache: newDashboardCache(),
  56. }, nil
  57. }
  58. func (fr *fileReader) ReadAndListen(ctx context.Context) error {
  59. ticker := time.NewTicker(time.Second * 10)
  60. if err := fr.walkFolder(); err != nil {
  61. fr.log.Error("failed to search for dashboards", "error", err)
  62. }
  63. for {
  64. select {
  65. case <-ticker.C:
  66. fr.walkFolder()
  67. case <-ctx.Done():
  68. return nil
  69. }
  70. }
  71. }
  72. func (fr *fileReader) walkFolder() error {
  73. if _, err := os.Stat(fr.Path); err != nil {
  74. if os.IsNotExist(err) {
  75. return err
  76. }
  77. }
  78. return filepath.Walk(fr.Path, func(path string, f os.FileInfo, err error) error {
  79. if err != nil {
  80. return err
  81. }
  82. if f.IsDir() {
  83. if strings.HasPrefix(f.Name(), ".") {
  84. return filepath.SkipDir
  85. }
  86. return nil
  87. }
  88. if !strings.HasSuffix(f.Name(), ".json") {
  89. return nil
  90. }
  91. cachedDashboard, exist := fr.dashboardCache.getCache(path)
  92. if exist && cachedDashboard.ModTime == f.ModTime() {
  93. return nil
  94. }
  95. dash, err := fr.readDashboardFromFile(path)
  96. if err != nil {
  97. fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
  98. return nil
  99. }
  100. cmd := &models.GetDashboardQuery{Slug: dash.Dashboard.Slug}
  101. err = bus.Dispatch(cmd)
  102. if err == models.ErrDashboardNotFound {
  103. fr.log.Debug("saving new dashboard", "file", path)
  104. return fr.saveDashboard(dash)
  105. }
  106. if err != nil {
  107. fr.log.Error("failed to query for dashboard", "slug", dash.Dashboard.Slug, "error", err)
  108. return nil
  109. }
  110. if cmd.Result.Updated.Unix() >= f.ModTime().Unix() {
  111. fr.log.Debug("already using latest version", "dashboard", dash.Dashboard.Slug)
  112. return nil
  113. }
  114. fr.log.Debug("no dashboard in cache. Loading dashboard from disk into database.", "file", path)
  115. return fr.saveDashboard(dash)
  116. })
  117. }
  118. func (fr *fileReader) readDashboardFromFile(path string) (*DashboardJson, error) {
  119. reader, err := os.Open(path)
  120. if err != nil {
  121. return nil, err
  122. }
  123. defer reader.Close()
  124. data, err := simplejson.NewFromReader(reader)
  125. if err != nil {
  126. return nil, err
  127. }
  128. stat, _ := os.Stat(path)
  129. dash := &DashboardJson{}
  130. dash.Dashboard = models.NewDashboardFromJson(data)
  131. dash.TitleLower = strings.ToLower(dash.Dashboard.Title)
  132. dash.Path = path
  133. dash.ModTime = stat.ModTime()
  134. dash.OrgId = fr.Cfg.OrgId
  135. dash.Folder = fr.Cfg.Folder
  136. if dash.Dashboard.Title == "" {
  137. return nil, models.ErrDashboardTitleEmpty
  138. }
  139. fr.dashboardCache.addCache(dash)
  140. return dash, nil
  141. }
  142. func (fr *fileReader) saveDashboard(dashboardJson *DashboardJson) error {
  143. dash := dashboardJson.Dashboard
  144. if dash.Title == "" {
  145. return models.ErrDashboardTitleEmpty
  146. }
  147. validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
  148. OrgId: dashboardJson.OrgId,
  149. Dashboard: dash,
  150. }
  151. if err := bus.Dispatch(&validateAlertsCmd); err != nil {
  152. return models.ErrDashboardContainsInvalidAlertData
  153. }
  154. cmd := models.SaveDashboardCommand{
  155. Dashboard: dash.Data,
  156. Message: "Dashboard created from file.",
  157. OrgId: dashboardJson.OrgId,
  158. Overwrite: true,
  159. UpdatedAt: dashboardJson.ModTime,
  160. }
  161. err := bus.Dispatch(&cmd)
  162. if err != nil {
  163. return err
  164. }
  165. alertCmd := alerting.UpdateDashboardAlertsCommand{
  166. OrgId: dashboardJson.OrgId,
  167. Dashboard: cmd.Result,
  168. }
  169. if err := bus.Dispatch(&alertCmd); err != nil {
  170. return err
  171. }
  172. return nil
  173. }