provisioning.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package provisioning
  2. import (
  3. "context"
  4. "path"
  5. "sync"
  6. "github.com/grafana/grafana/pkg/infra/log"
  7. "github.com/grafana/grafana/pkg/util/errutil"
  8. "github.com/grafana/grafana/pkg/registry"
  9. "github.com/grafana/grafana/pkg/services/provisioning/dashboards"
  10. "github.com/grafana/grafana/pkg/services/provisioning/datasources"
  11. "github.com/grafana/grafana/pkg/services/provisioning/notifiers"
  12. "github.com/grafana/grafana/pkg/setting"
  13. )
  14. type DashboardProvisioner interface {
  15. Provision() error
  16. PollChanges(ctx context.Context)
  17. GetProvisionerResolvedPath(name string) string
  18. }
  19. type DashboardProvisionerFactory func(string) (DashboardProvisioner, error)
  20. func init() {
  21. registry.RegisterService(NewProvisioningServiceImpl(
  22. func(path string) (DashboardProvisioner, error) {
  23. return dashboards.NewDashboardProvisionerImpl(path)
  24. },
  25. notifiers.Provision,
  26. datasources.Provision,
  27. ))
  28. }
  29. func NewProvisioningServiceImpl(
  30. newDashboardProvisioner DashboardProvisionerFactory,
  31. provisionNotifiers func(string) error,
  32. provisionDatasources func(string) error,
  33. ) *provisioningServiceImpl {
  34. return &provisioningServiceImpl{
  35. log: log.New("provisioning"),
  36. newDashboardProvisioner: newDashboardProvisioner,
  37. provisionNotifiers: provisionNotifiers,
  38. provisionDatasources: provisionDatasources,
  39. }
  40. }
  41. type provisioningServiceImpl struct {
  42. Cfg *setting.Cfg `inject:""`
  43. log log.Logger
  44. pollingCtxCancel context.CancelFunc
  45. newDashboardProvisioner DashboardProvisionerFactory
  46. dashboardProvisioner DashboardProvisioner
  47. provisionNotifiers func(string) error
  48. provisionDatasources func(string) error
  49. mutex sync.Mutex
  50. }
  51. func (ps *provisioningServiceImpl) Init() error {
  52. err := ps.ProvisionDatasources()
  53. if err != nil {
  54. return err
  55. }
  56. err = ps.ProvisionNotifications()
  57. if err != nil {
  58. return err
  59. }
  60. err = ps.ProvisionDashboards()
  61. if err != nil {
  62. return err
  63. }
  64. return nil
  65. }
  66. func (ps *provisioningServiceImpl) Run(ctx context.Context) error {
  67. for {
  68. // Wait for unlock. This is tied to new dashboardProvisioner to be instantiated before we start polling.
  69. ps.mutex.Lock()
  70. // Using background here because otherwise if root context was canceled the select later on would
  71. // non-deterministically take one of the route possibly going into one polling loop before exiting.
  72. pollingContext, cancelFun := context.WithCancel(context.Background())
  73. ps.pollingCtxCancel = cancelFun
  74. ps.dashboardProvisioner.PollChanges(pollingContext)
  75. ps.mutex.Unlock()
  76. select {
  77. case <-pollingContext.Done():
  78. // Polling was canceled.
  79. continue
  80. case <-ctx.Done():
  81. // Root server context was cancelled so cancel polling and leave.
  82. ps.cancelPolling()
  83. return ctx.Err()
  84. }
  85. }
  86. }
  87. func (ps *provisioningServiceImpl) ProvisionDatasources() error {
  88. datasourcePath := path.Join(ps.Cfg.ProvisioningPath, "datasources")
  89. err := ps.provisionDatasources(datasourcePath)
  90. return errutil.Wrap("Datasource provisioning error", err)
  91. }
  92. func (ps *provisioningServiceImpl) ProvisionNotifications() error {
  93. alertNotificationsPath := path.Join(ps.Cfg.ProvisioningPath, "notifiers")
  94. err := ps.provisionNotifiers(alertNotificationsPath)
  95. return errutil.Wrap("Alert notification provisioning error", err)
  96. }
  97. func (ps *provisioningServiceImpl) ProvisionDashboards() error {
  98. dashboardPath := path.Join(ps.Cfg.ProvisioningPath, "dashboards")
  99. dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath)
  100. if err != nil {
  101. return errutil.Wrap("Failed to create provisioner", err)
  102. }
  103. ps.mutex.Lock()
  104. defer ps.mutex.Unlock()
  105. ps.cancelPolling()
  106. if err := dashProvisioner.Provision(); err != nil {
  107. // If we fail to provision with the new provisioner, mutex will unlock and the polling we restart with the
  108. // old provisioner as we did not switch them yet.
  109. return errutil.Wrap("Failed to provision dashboards", err)
  110. }
  111. ps.dashboardProvisioner = dashProvisioner
  112. return nil
  113. }
  114. func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string {
  115. return ps.dashboardProvisioner.GetProvisionerResolvedPath(name)
  116. }
  117. func (ps *provisioningServiceImpl) cancelPolling() {
  118. if ps.pollingCtxCancel != nil {
  119. ps.log.Debug("Stop polling for dashboard changes")
  120. ps.pollingCtxCancel()
  121. }
  122. ps.pollingCtxCancel = nil
  123. }