file_reader.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package dashboards
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "time"
  9. "github.com/grafana/grafana/pkg/services/dashboards"
  10. "github.com/grafana/grafana/pkg/bus"
  11. "github.com/grafana/grafana/pkg/components/simplejson"
  12. "github.com/grafana/grafana/pkg/log"
  13. "github.com/grafana/grafana/pkg/models"
  14. gocache "github.com/patrickmn/go-cache"
  15. )
  16. type fileReader struct {
  17. Cfg *DashboardsAsConfig
  18. Path string
  19. log log.Logger
  20. dashboardRepo dashboards.Repository
  21. cache *gocache.Cache
  22. }
  23. func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
  24. path, ok := cfg.Options["folder"].(string)
  25. if !ok {
  26. return nil, fmt.Errorf("Failed to load dashboards. folder param is not a string")
  27. }
  28. if _, err := os.Stat(path); os.IsNotExist(err) {
  29. log.Error("Cannot read directory", "error", err)
  30. }
  31. return &fileReader{
  32. Cfg: cfg,
  33. Path: path,
  34. log: log,
  35. dashboardRepo: dashboards.GetRepository(),
  36. cache: gocache.New(5*time.Minute, 30*time.Minute),
  37. }, nil
  38. }
  39. func (fr *fileReader) addCache(key string, json *dashboards.SaveDashboardItem) {
  40. fr.cache.Add(key, json, time.Minute*10)
  41. }
  42. func (fr *fileReader) getCache(key string) (*dashboards.SaveDashboardItem, bool) {
  43. obj, exist := fr.cache.Get(key)
  44. if !exist {
  45. return nil, exist
  46. }
  47. dash, ok := obj.(*dashboards.SaveDashboardItem)
  48. if !ok {
  49. return nil, ok
  50. }
  51. return dash, ok
  52. }
  53. func (fr *fileReader) ReadAndListen(ctx context.Context) error {
  54. ticker := time.NewTicker(time.Second * 3)
  55. if err := fr.walkFolder(); err != nil {
  56. fr.log.Error("failed to search for dashboards", "error", err)
  57. }
  58. running := false
  59. for {
  60. select {
  61. case <-ticker.C:
  62. if !running { // avoid walking the filesystem in parallel. incase fs is very slow.
  63. running = true
  64. go func() {
  65. fr.walkFolder()
  66. running = false
  67. }()
  68. }
  69. case <-ctx.Done():
  70. return nil
  71. }
  72. }
  73. }
  74. func (fr *fileReader) walkFolder() error {
  75. if _, err := os.Stat(fr.Path); err != nil {
  76. if os.IsNotExist(err) {
  77. return err
  78. }
  79. }
  80. return filepath.Walk(fr.Path, func(path string, fileInfo os.FileInfo, err error) error {
  81. if err != nil {
  82. return err
  83. }
  84. if fileInfo.IsDir() {
  85. if strings.HasPrefix(fileInfo.Name(), ".") {
  86. return filepath.SkipDir
  87. }
  88. return nil
  89. }
  90. if !strings.HasSuffix(fileInfo.Name(), ".json") {
  91. return nil
  92. }
  93. cachedDashboard, exist := fr.getCache(path)
  94. if exist && cachedDashboard.UpdatedAt == fileInfo.ModTime() {
  95. return nil
  96. }
  97. dash, err := fr.readDashboardFromFile(path)
  98. if err != nil {
  99. fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
  100. return nil
  101. }
  102. // id = 0 indicates ID validation should be avoided before writing to the db.
  103. dash.Dashboard.Id = 0
  104. cmd := &models.GetDashboardQuery{Slug: dash.Dashboard.Slug}
  105. err = bus.Dispatch(cmd)
  106. // if we dont have the dashboard in the db, save it!
  107. if err == models.ErrDashboardNotFound {
  108. fr.log.Debug("saving new dashboard", "file", path)
  109. _, err = fr.dashboardRepo.SaveDashboard(dash)
  110. return err
  111. }
  112. if err != nil {
  113. fr.log.Error("failed to query for dashboard", "slug", dash.Dashboard.Slug, "error", err)
  114. return nil
  115. }
  116. // break if db version is newer then fil version
  117. if cmd.Result.Updated.Unix() >= fileInfo.ModTime().Unix() {
  118. return nil
  119. }
  120. fr.log.Debug("loading dashboard from disk into database.", "file", path)
  121. _, err = fr.dashboardRepo.SaveDashboard(dash)
  122. return err
  123. })
  124. }
  125. func (fr *fileReader) readDashboardFromFile(path string) (*dashboards.SaveDashboardItem, error) {
  126. reader, err := os.Open(path)
  127. if err != nil {
  128. return nil, err
  129. }
  130. defer reader.Close()
  131. data, err := simplejson.NewFromReader(reader)
  132. if err != nil {
  133. return nil, err
  134. }
  135. stat, err := os.Stat(path)
  136. if err != nil {
  137. return nil, err
  138. }
  139. dash, err := createDashboardJson(data, stat.ModTime(), fr.Cfg)
  140. if err != nil {
  141. return nil, err
  142. }
  143. fr.addCache(path, dash)
  144. return dash, nil
  145. }