CLI to run health checks against sensor stations on openSenseMap.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

notifiers.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package core
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. log "github.com/sirupsen/logrus"
  7. )
  8. var Notifiers = map[string]AbstractNotifier{
  9. "email": EmailNotifier{},
  10. "slack": SlackNotifier{},
  11. "xmpp": XmppNotifier{},
  12. }
  13. type AbstractNotifier interface {
  14. New(config TransportConfig) (AbstractNotifier, error)
  15. Submit(notification Notification) error
  16. }
  17. type Notification struct {
  18. Status string // one of CheckOk | CheckErr
  19. Body string
  20. Subject string
  21. }
  22. //////
  23. func (box Box) GetNotifier() (AbstractNotifier, error) {
  24. return GetNotifier(&box.NotifyConf.Notifications)
  25. }
  26. func GetNotifier(config *TransportConfig) (AbstractNotifier, error) {
  27. transport := config.Transport
  28. if transport == "" {
  29. return nil, fmt.Errorf("No notification transport provided")
  30. }
  31. notifier := Notifiers[transport]
  32. if notifier == nil {
  33. return nil, fmt.Errorf("%s is not a supported notification transport", transport)
  34. }
  35. return notifier.New(*config)
  36. }
  37. func (results BoxCheckResults) SendNotifications(notifyTypes []string, useCache bool) error {
  38. if useCache {
  39. results = results.filterChangedFromCache()
  40. }
  41. toCheck := results.Size(notifyTypes)
  42. if toCheck == 0 {
  43. log.Info("No notifications due.")
  44. } else {
  45. log.Infof("Notifying for %v checks changing state to %v...", toCheck, notifyTypes)
  46. }
  47. errs := []string{}
  48. for box, resultsBox := range results {
  49. // only submit results which are errors
  50. resultsDue := []CheckResult{}
  51. for _, result := range resultsBox {
  52. if result.HasStatus(notifyTypes) {
  53. resultsDue = append(resultsDue, result)
  54. }
  55. }
  56. transport := box.NotifyConf.Notifications.Transport
  57. notifyLog := log.WithFields(log.Fields{
  58. "boxId": box.Id,
  59. "transport": transport,
  60. })
  61. if len(resultsDue) != 0 {
  62. notifier, err := box.GetNotifier()
  63. if err != nil {
  64. notifyLog.Error(err)
  65. errs = append(errs, err.Error())
  66. continue
  67. }
  68. notification := ComposeNotification(box, resultsDue)
  69. var submitErr error
  70. submitErr = notifier.Submit(notification)
  71. for retry := 0; submitErr != nil && retry < 2; retry++ {
  72. notifyLog.Warnf("sending notification failed (retry %v): %v", retry, submitErr)
  73. time.Sleep(10 * time.Second)
  74. submitErr = notifier.Submit(notification)
  75. }
  76. if submitErr != nil {
  77. notifyLog.Error(submitErr)
  78. errs = append(errs, submitErr.Error())
  79. continue
  80. }
  81. }
  82. // update cache (with /all/ changed results to reset status)
  83. if useCache {
  84. notifyLog.Debug("updating cache")
  85. updateCache(box, resultsBox)
  86. }
  87. if len(resultsDue) != 0 {
  88. notifyLog.Infof("Sent notification for %s via %s with %v updated issues", box.Name, transport, len(resultsDue))
  89. }
  90. }
  91. // persist changes to cache
  92. if useCache {
  93. err := writeCache()
  94. if err != nil {
  95. log.Error("could not write cache of notification results: ", err)
  96. errs = append(errs, err.Error())
  97. }
  98. }
  99. if len(errs) != 0 {
  100. return fmt.Errorf(strings.Join(errs, "\n"))
  101. }
  102. return nil
  103. }
  104. func ComposeNotification(box *Box, checks []CheckResult) Notification {
  105. errTexts := []string{}
  106. resolvedTexts := []string{}
  107. for _, check := range checks {
  108. if check.Status == CheckErr {
  109. errTexts = append(errTexts, check.String())
  110. } else {
  111. resolvedTexts = append(resolvedTexts, check.String())
  112. }
  113. }
  114. var (
  115. resolved string
  116. resolvedList string
  117. errList string
  118. status string
  119. )
  120. if len(resolvedTexts) != 0 {
  121. resolvedList = fmt.Sprintf("Resolved issue(s):\n\n%s\n\n", strings.Join(resolvedTexts, "\n"))
  122. }
  123. if len(errTexts) != 0 {
  124. errList = fmt.Sprintf("New issue(s):\n\n%s\n\n", strings.Join(errTexts, "\n"))
  125. status = CheckErr
  126. } else {
  127. resolved = "resolved "
  128. status = CheckOk
  129. }
  130. return Notification{
  131. Status: status,
  132. Subject: fmt.Sprintf("Issues %swith your box \"%s\" on opensensemap.org!", resolved, box.Name),
  133. Body: fmt.Sprintf("A check at %s identified the following updates for your box \"%s\":\n\n%s%sYou may visit https://opensensemap.org/explore/%s for more details.",
  134. time.Now().Round(time.Minute), box.Name, errList, resolvedList, box.Id),
  135. }
  136. }