Forráskód Böngészése

Merge pull request #7482 from dbrgn/master

Alerting: Add Threema Gateway integration
Carl Bergquist 8 éve
szülő
commit
5d3da99806

+ 2 - 0
pkg/metrics/metrics.go

@@ -49,6 +49,7 @@ var (
 	M_Alerting_Notification_Sent_Victorops Counter
 	M_Alerting_Notification_Sent_OpsGenie  Counter
 	M_Alerting_Notification_Sent_Telegram  Counter
+	M_Alerting_Notification_Sent_Threema   Counter
 	M_Alerting_Notification_Sent_Sensu     Counter
 	M_Alerting_Notification_Sent_Pushover  Counter
 	M_Aws_CloudWatch_GetMetricStatistics   Counter
@@ -119,6 +120,7 @@ func initMetricVars(settings *MetricSettings) {
 	M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops")
 	M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie")
 	M_Alerting_Notification_Sent_Telegram = RegCounter("alerting.notifications_sent", "type", "telegram")
+	M_Alerting_Notification_Sent_Threema = RegCounter("alerting.notifications_sent", "type", "threema")
 	M_Alerting_Notification_Sent_Sensu = RegCounter("alerting.notifications_sent", "type", "sensu")
 	M_Alerting_Notification_Sent_LINE = RegCounter("alerting.notifications_sent", "type", "LINE")
 	M_Alerting_Notification_Sent_Pushover = RegCounter("alerting.notifications_sent", "type", "pushover")

+ 159 - 0
pkg/services/alerting/notifiers/threema.go

@@ -0,0 +1,159 @@
+package notifiers
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/metrics"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/alerting"
+)
+
+var (
+	threemaGwBaseURL = "https://msgapi.threema.ch/%s"
+)
+
+func init() {
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "threema",
+		Name:        "Threema Gateway",
+		Description: "Sends notifications to Threema using the Threema Gateway",
+		Factory:     NewThreemaNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Threema Gateway settings</h3>
+      <p>
+        Notifications can be configured for any Threema Gateway ID of type
+        "Basic". End-to-End IDs are not currently supported.
+      </p>
+      <p>
+        The Threema Gateway ID can be set up at
+        <a href="https://gateway.threema.ch/" target="_blank" rel="noopener noreferrer">https://gateway.threema.ch/</a>.
+      </p>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">Gateway ID</span>
+        <input type="text" required maxlength="8" pattern="\*[0-9A-Z]{7}"
+          class="gf-form-input max-width-14"
+          ng-model="ctrl.model.settings.gateway_id"
+          placeholder="*3MAGWID">
+        </input>
+        <info-popover mode="right-normal">
+          Your 8 character Threema Gateway ID (starting with a *)
+        </info-popover>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">Recipient ID</span>
+        <input type="text" required maxlength="8" pattern="[0-9A-Z]{8}"
+          class="gf-form-input max-width-14"
+          ng-model="ctrl.model.settings.recipient_id"
+          placeholder="YOUR3MID">
+        </input>
+        <info-popover mode="right-normal">
+          The 8 character Threema ID that should receive the alerts
+        </info-popover>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">API Secret</span>
+        <input type="text" required
+          class="gf-form-input max-width-24"
+          ng-model="ctrl.model.settings.api_secret">
+        </input>
+        <info-popover mode="right-normal">
+          Your Threema Gateway API secret
+        </info-popover>
+      </div>
+    `,
+	})
+
+}
+
+type ThreemaNotifier struct {
+	NotifierBase
+	GatewayID   string
+	RecipientID string
+	APISecret   string
+	log         log.Logger
+}
+
+func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
+	if model.Settings == nil {
+		return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
+	}
+
+	gatewayID := model.Settings.Get("gateway_id").MustString()
+	recipientID := model.Settings.Get("recipient_id").MustString()
+	apiSecret := model.Settings.Get("api_secret").MustString()
+
+	// Validation
+	if gatewayID == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find Threema Gateway ID in settings"}
+	}
+	if !strings.HasPrefix(gatewayID, "*") {
+		return nil, alerting.ValidationError{Reason: "Invalid Threema Gateway ID: Must start with a *"}
+	}
+	if len(gatewayID) != 8 {
+		return nil, alerting.ValidationError{Reason: "Invalid Threema Gateway ID: Must be 8 characters long"}
+	}
+	if recipientID == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find Threema Recipient ID in settings"}
+	}
+	if len(recipientID) != 8 {
+		return nil, alerting.ValidationError{Reason: "Invalid Threema Recipient ID: Must be 8 characters long"}
+	}
+	if apiSecret == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find Threema API secret in settings"}
+	}
+
+	return &ThreemaNotifier{
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		GatewayID:    gatewayID,
+		RecipientID:  recipientID,
+		APISecret:    apiSecret,
+		log:          log.New("alerting.notifier.threema"),
+	}, nil
+}
+
+func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error {
+	notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID)
+	notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID)
+	metrics.M_Alerting_Notification_Sent_Threema.Inc(1)
+
+	// Set up basic API request data
+	data := url.Values{}
+	data.Set("from", notifier.GatewayID)
+	data.Set("to", notifier.RecipientID)
+	data.Set("secret", notifier.APISecret)
+
+	// Build message
+	message := fmt.Sprintf("%s\n\n*State:* %s\n*Message:* %s\n",
+		evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
+	ruleURL, err := evalContext.GetRuleUrl()
+	if err == nil {
+		message = message + fmt.Sprintf("*URL:* %s\n", ruleURL)
+	}
+	if evalContext.ImagePublicUrl != "" {
+		message = message + fmt.Sprintf("*Image:* %s\n", evalContext.ImagePublicUrl)
+	}
+	data.Set("text", message)
+
+	// Prepare and send request
+	url := fmt.Sprintf(threemaGwBaseURL, "send_simple")
+	body := data.Encode()
+	headers := map[string]string{
+		"Content-Type": "application/x-www-form-urlencoded",
+	}
+	cmd := &m.SendWebhookSync{
+		Url:        url,
+		Body:       body,
+		HttpMethod: "POST",
+		HttpHeader: headers,
+	}
+	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+		notifier.log.Error("Failed to send webhook", "error", err, "webhook", notifier.Name)
+		return err
+	}
+
+	return nil
+}

+ 119 - 0
pkg/services/alerting/notifiers/threema_test.go

@@ -0,0 +1,119 @@
+package notifiers
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/alerting"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestThreemaNotifier(t *testing.T) {
+	Convey("Threema notifier tests", t, func() {
+
+		Convey("Parsing alert notification from settings", func() {
+			Convey("empty settings should return error", func() {
+				json := `{ }`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "threema_testing",
+					Type:     "threema",
+					Settings: settingsJSON,
+				}
+
+				_, err := NewThreemaNotifier(model)
+				So(err, ShouldNotBeNil)
+			})
+
+			Convey("valid settings should be parsed successfully", func() {
+				json := `
+				{
+					"gateway_id": "*3MAGWID",
+					"recipient_id": "ECHOECHO",
+					"api_secret": "1234"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "threema_testing",
+					Type:     "threema",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewThreemaNotifier(model)
+				So(err, ShouldBeNil)
+				threemaNotifier := not.(*ThreemaNotifier)
+
+				So(err, ShouldBeNil)
+				So(threemaNotifier.Name, ShouldEqual, "threema_testing")
+				So(threemaNotifier.Type, ShouldEqual, "threema")
+				So(threemaNotifier.GatewayID, ShouldEqual, "*3MAGWID")
+				So(threemaNotifier.RecipientID, ShouldEqual, "ECHOECHO")
+				So(threemaNotifier.APISecret, ShouldEqual, "1234")
+			})
+
+			Convey("invalid Threema Gateway IDs should be rejected (prefix)", func() {
+				json := `
+				{
+					"gateway_id": "ECHOECHO",
+					"recipient_id": "ECHOECHO",
+					"api_secret": "1234"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "threema_testing",
+					Type:     "threema",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewThreemaNotifier(model)
+				So(not, ShouldBeNil)
+				So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Gateway ID: Must start with a *")
+			})
+
+			Convey("invalid Threema Gateway IDs should be rejected (length)", func() {
+				json := `
+				{
+					"gateway_id": "*ECHOECHO",
+					"recipient_id": "ECHOECHO",
+					"api_secret": "1234"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "threema_testing",
+					Type:     "threema",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewThreemaNotifier(model)
+				So(not, ShouldBeNil)
+				So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Gateway ID: Must be 8 characters long")
+			})
+
+			Convey("invalid Threema Recipient IDs should be rejected (length)", func() {
+				json := `
+				{
+					"gateway_id": "*3MAGWID",
+					"recipient_id": "ECHOECH",
+					"api_secret": "1234"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "threema_testing",
+					Type:     "threema",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewThreemaNotifier(model)
+				So(not, ShouldBeNil)
+				So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long")
+			})
+
+		})
+	})
+}