Tool to help you manage your Grafana dashboards using Git.

puller.go 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io/ioutil"
  6. "os"
  7. "git"
  8. "grafana"
  9. gogit "gopkg.in/src-d/go-git.v4"
  10. )
  11. // diffVersion represents a dashboard version diff.
  12. type diffVersion struct {
  13. oldVersion int
  14. newVersion int
  15. }
  16. // PullGrafanaAndCommit pulls all the dashboards from Grafana then commits each
  17. // of them to Git except for those that have a newer or equal version number
  18. // already versionned in the repo
  19. func PullGrafanaAndCommit(
  20. client *grafana.Client,
  21. repoURL string, clonePath string, privateKeyPath string,
  22. ) error {
  23. dv := make(map[string]diffVersion)
  24. dbVersions, err := getDashboardsVersions()
  25. if err != nil {
  26. return err
  27. }
  28. repo, err := git.Sync(repoURL, clonePath, privateKeyPath)
  29. if err != nil {
  30. return err
  31. }
  32. w, err := repo.Worktree()
  33. if err != nil {
  34. return err
  35. }
  36. uris, err := client.GetDashboardsURIs()
  37. if err != nil {
  38. return err
  39. }
  40. for _, uri := range uris {
  41. dashboard, err := client.GetDashboard(uri)
  42. if err != nil {
  43. return err
  44. }
  45. version, ok := dbVersions[dashboard.Slug]
  46. if !ok || dashboard.Version > version {
  47. if err = addDashboardChangesToRepo(dashboard, w); err != nil {
  48. return err
  49. }
  50. dv[dashboard.Slug] = diffVersion{
  51. oldVersion: version,
  52. newVersion: dashboard.Version,
  53. }
  54. }
  55. }
  56. status, err := w.Status()
  57. if err != nil {
  58. return err
  59. }
  60. if !status.IsClean() {
  61. if err = commitNewVersions(dbVersions, dv, w); err != nil {
  62. return err
  63. }
  64. }
  65. if err = git.Push(repo, privateKeyPath); err != nil {
  66. return err
  67. }
  68. return nil
  69. }
  70. // addDashboardChangesToRepo writes a dashboard content in a file, then adds the
  71. // file to the git index so it can be comitted afterwards.
  72. // Returns an error if there was an issue with either of the steps.
  73. func addDashboardChangesToRepo(
  74. dashboard *grafana.Dashboard, worktree *gogit.Worktree,
  75. ) error {
  76. slugExt := dashboard.Slug + ".json"
  77. if err := rewriteFile(
  78. *clonePath+"/"+slugExt,
  79. dashboard.RawJSON,
  80. ); err != nil {
  81. return err
  82. }
  83. if _, err := worktree.Add(slugExt); err != nil {
  84. return err
  85. }
  86. return nil
  87. }
  88. // rewriteFile removes a given file and re-creates it with a new content. The
  89. // content is provided as JSON, and is then indented before being written down.
  90. // We need the whole "remove then recreate" thing because, if the file already
  91. // exists, ioutil.WriteFile will append the content to it. However, we want to
  92. // replace the oldest version with another (so git can diff it), so we re-create
  93. // the file with the changed content.
  94. // Returns an error if there was an issue when removing or writing the file, or
  95. // indenting the JSON content.
  96. func rewriteFile(filename string, content []byte) error {
  97. if err := os.Remove(filename); err != nil {
  98. pe, ok := err.(*os.PathError)
  99. if !ok || pe.Err.Error() != "no such file or directory" {
  100. return err
  101. }
  102. }
  103. indentedContent, err := indent(content)
  104. if err != nil {
  105. return err
  106. }
  107. return ioutil.WriteFile(filename, indentedContent, 0644)
  108. }
  109. // indent indents a given JSON content with tabs.
  110. // We need to indent the content as the Grafana API returns a one-lined JSON
  111. // string, which isn't great to work with.
  112. // Returns an error if there was an issue with the process.
  113. func indent(srcJSON []byte) (indentedJSON []byte, err error) {
  114. buf := bytes.NewBuffer(nil)
  115. if err = json.Indent(buf, srcJSON, "", "\t"); err != nil {
  116. return
  117. }
  118. indentedJSON, err = ioutil.ReadAll(buf)
  119. return
  120. }