فهرست منبع

Merge pull request #6856 from kylemcc/opsgenie-alerting

alerting: add support for OpsGenie
Carl Bergquist 9 سال پیش
والد
کامیت
0294446af9

+ 2 - 0
pkg/metrics/metrics.go

@@ -46,6 +46,7 @@ var (
 	M_Alerting_Notification_Sent_Webhook   Counter
 	M_Alerting_Notification_Sent_PagerDuty Counter
 	M_Alerting_Notification_Sent_Victorops Counter
+	M_Alerting_Notification_Sent_OpsGenie  Counter
 
 	// Timers
 	M_DataSource_ProxyReq_Timer Timer
@@ -110,6 +111,7 @@ func initMetricVars(settings *MetricSettings) {
 	M_Alerting_Notification_Sent_Webhook = RegCounter("alerting.notifications_sent", "type", "webhook")
 	M_Alerting_Notification_Sent_PagerDuty = RegCounter("alerting.notifications_sent", "type", "pagerduty")
 	M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops")
+	M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie")
 
 	// Timers
 	M_DataSource_ProxyReq_Timer = RegTimer("api.dataproxy.request.all")

+ 118 - 0
pkg/services/alerting/notifiers/opsgenie.go

@@ -0,0 +1,118 @@
+package notifiers
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"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"
+)
+
+func init() {
+	alerting.RegisterNotifier("opsgenie", NewOpsGenieNotifier)
+}
+
+var (
+	opsgenieCreateAlertURL string = "https://api.opsgenie.com/v1/json/alert"
+	opsgenieCloseAlertURL  string = "https://api.opsgenie.com/v1/json/alert/close"
+)
+
+func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
+	autoClose := model.Settings.Get("autoClose").MustBool(true)
+	apiKey := model.Settings.Get("apiKey").MustString()
+	if apiKey == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
+	}
+
+	return &OpsGenieNotifier{
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		ApiKey:       apiKey,
+		AutoClose:    autoClose,
+		log:          log.New("alerting.notifier.opsgenie"),
+	}, nil
+}
+
+type OpsGenieNotifier struct {
+	NotifierBase
+	ApiKey    string
+	AutoClose bool
+	log       log.Logger
+}
+
+func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
+	metrics.M_Alerting_Notification_Sent_OpsGenie.Inc(1)
+
+	var err error
+	switch evalContext.Rule.State {
+	case m.AlertStateOK:
+		if this.AutoClose {
+			err = this.closeAlert(evalContext)
+		}
+	case m.AlertStateAlerting:
+		err = this.createAlert(evalContext)
+	}
+	return err
+}
+
+func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error {
+	this.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name)
+
+	ruleUrl, err := evalContext.GetRuleUrl()
+	if err != nil {
+		this.log.Error("Failed get rule link", "error", err)
+		return err
+	}
+
+	bodyJSON := simplejson.New()
+	bodyJSON.Set("apiKey", this.ApiKey)
+	bodyJSON.Set("message", evalContext.Rule.Name)
+	bodyJSON.Set("source", "Grafana")
+	bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
+	bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message))
+
+	details := simplejson.New()
+	details.Set("url", ruleUrl)
+	if evalContext.ImagePublicUrl != "" {
+		details.Set("image", evalContext.ImagePublicUrl)
+	}
+
+	bodyJSON.Set("details", details)
+	body, _ := bodyJSON.MarshalJSON()
+
+	cmd := &m.SendWebhookSync{
+		Url:        opsgenieCreateAlertURL,
+		Body:       string(body),
+		HttpMethod: "POST",
+	}
+
+	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+		this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
+	}
+
+	return nil
+}
+
+func (this *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error {
+	this.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name)
+
+	bodyJSON := simplejson.New()
+	bodyJSON.Set("apiKey", this.ApiKey)
+	bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
+	body, _ := bodyJSON.MarshalJSON()
+
+	cmd := &m.SendWebhookSync{
+		Url:        opsgenieCloseAlertURL,
+		Body:       string(body),
+		HttpMethod: "POST",
+	}
+
+	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+		this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
+	}
+
+	return nil
+}

+ 52 - 0
pkg/services/alerting/notifiers/opsgenie_test.go

@@ -0,0 +1,52 @@
+package notifiers
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	m "github.com/grafana/grafana/pkg/models"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestOpsGenieNotifier(t *testing.T) {
+	Convey("OpsGenie 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:     "opsgenie_testing",
+					Type:     "opsgenie",
+					Settings: settingsJSON,
+				}
+
+				_, err := NewOpsGenieNotifier(model)
+				So(err, ShouldNotBeNil)
+			})
+
+			Convey("settings should trigger incident", func() {
+				json := `
+				{
+          "apiKey": "abcdefgh0123456789"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "opsgenie_testing",
+					Type:     "opsgenie",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewOpsGenieNotifier(model)
+				opsgenieNotifier := not.(*OpsGenieNotifier)
+
+				So(err, ShouldBeNil)
+				So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing")
+				So(opsgenieNotifier.Type, ShouldEqual, "opsgenie")
+				So(opsgenieNotifier.ApiKey, ShouldEqual, "abcdefgh0123456789")
+			})
+		})
+	})
+}

+ 1 - 0
public/app/features/alerting/alert_tab_ctrl.ts

@@ -94,6 +94,7 @@ export class AlertTabCtrl {
       case "victorops": return "fa fa-pagelines";
       case "webhook": return "fa fa-cubes";
       case "pagerduty": return "fa fa-bullhorn";
+      case "opsgenie": return "fa fa-bell";
     }
   }
 

+ 18 - 1
public/app/features/alerting/partials/notification_edit.html

@@ -19,7 +19,7 @@
       <div class="gf-form">
         <span class="gf-form-label width-12">Type</span>
         <div class="gf-form-select-wrapper width-15">
-          <select class="gf-form-input" ng-model="ctrl.model.type" ng-options="t for t in ['webhook', 'email', 'slack', 'pagerduty', 'victorops']" ng-change="ctrl.typeChanged(notification, $index)">
+          <select class="gf-form-input" ng-model="ctrl.model.type" ng-options="t for t in ['webhook', 'email', 'slack', 'pagerduty', 'victorops', 'opsgenie']" ng-change="ctrl.typeChanged(notification, $index)">
           </select>
         </div>
       </div>
@@ -122,6 +122,23 @@
       </div>
     </div>
 
+    <div class="gf-form-group" ng-if="ctrl.model.type === 'opsgenie'">
+      <h3 class="page-heading">OpsGenie settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">API Key</span>
+        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiKey" placeholder="OpsGenie API Key"></input>
+      </div>
+      <div class="gf-form">
+        <gf-form-switch
+           class="gf-form"
+           label="Auto close incidents"
+           label-class="width-14"
+           checked="ctrl.model.settings.autoClose"
+           tooltip="Automatically close alerts in OpseGenie once the alert goes back to ok.">
+        </gf-form-switch>
+      </div>
+    </div>
+
     <div class="gf-form-group">
       <div class="gf-form-inline">
         <div class="gf-form width-6">