Alerting: Support WeCom as a contact point type (#40975)

* add wecom notifier

* fix backend lint

* fix alerting channel test

* update wecom doc

* update notifiers

* update wecom notifier test

* Apply suggestions from code review

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* unify wecom alerting

* fix backend lint

* fix front lint

* fix wecom test

* update docs

* Update pkg/services/ngalert/notifier/channels/wecom.go

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* Update docs/sources/alerting/old-alerting/notifications.md

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* Update docs/sources/alerting/old-alerting/notifications.md

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* Update docs/sources/alerting/old-alerting/notifications.md

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* remove old wecom notifier

* remove old notifier doc

* fix backend test

* Update docs/sources/alerting/unified-alerting/contact-points.md

Co-authored-by: gotjosh <josue.abreu@gmail.com>

* fix doc style

Co-authored-by: gotjosh <josue.abreu@gmail.com>
This commit is contained in:
smallpath
2021-12-16 00:42:03 +08:00
committed by GitHub
parent f16696a069
commit aec14cba42
6 changed files with 299 additions and 0 deletions

View File

@@ -519,6 +519,8 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
n, err = channels.NewKafkaNotifier(cfg, tmpl)
case "webhook":
n, err = channels.NewWebHookNotifier(cfg, tmpl, am.decryptFn)
case "wecom":
n, err = channels.NewWeComNotifier(cfg, tmpl, am.decryptFn)
case "sensugo":
n, err = channels.NewSensuGoNotifier(cfg, tmpl, am.decryptFn)
case "discord":

View File

@@ -637,6 +637,30 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
},
},
},
{
Type: "wecom",
Name: "WeCom",
Description: "Send alerts generated by Grafana to WeCom",
Heading: "WeCom settings",
Options: []alerting.NotifierOption{
{
Label: "Url",
Element: alerting.ElementTypeInput,
InputType: alerting.InputTypeText,
Placeholder: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx",
PropertyName: "url",
Required: true,
Secure: true,
},
{
Label: "Message",
Description: "Custom WeCom message. You can use template variables.",
Element: alerting.ElementTypeTextArea,
Placeholder: `{{ template "default.message" . }}`,
PropertyName: "message",
},
},
},
{
Type: "prometheus-alertmanager",
Name: "Alertmanager",

View File

@@ -0,0 +1,87 @@
package channels
import (
"context"
"encoding/json"
"fmt"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/prometheus/alertmanager/template"
)
// NewWeComNotifier is the constructor for WeCom notifier.
func NewWeComNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*WeComNotifier, error) {
url := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString())
if url == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find webhook URL in settings"}
}
return &WeComNotifier{
Base: NewBase(&models.AlertNotification{
Uid: model.UID,
Name: model.Name,
Type: model.Type,
DisableResolveMessage: model.DisableResolveMessage,
Settings: model.Settings,
}),
URL: url,
log: log.New("alerting.notifier.wecom"),
Message: model.Settings.Get("message").MustString(`{{ template "default.message" .}}`),
tmpl: t,
}, nil
}
// WeComNotifier is responsible for sending alert notifications to WeCom.
type WeComNotifier struct {
*Base
URL string
Message string
tmpl *template.Template
log log.Logger
}
// Notify send an alert notification to WeCom.
func (w *WeComNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
w.log.Info("executing WeCom notification", "notification", w.Name)
var tmplErr error
tmpl, _ := TmplText(ctx, w.tmpl, as, w.log, &tmplErr)
bodyMsg := map[string]interface{}{
"msgtype": "markdown",
}
content := fmt.Sprintf("# %s\n%s\n",
tmpl(`{{ template "default.title" . }}`),
tmpl(w.Message),
)
bodyMsg["markdown"] = map[string]interface{}{
"content": content,
}
body, err := json.Marshal(bodyMsg)
if err != nil {
return false, err
}
cmd := &models.SendWebhookSync{
Url: w.URL,
Body: string(body),
}
if err := bus.DispatchCtx(ctx, cmd); err != nil {
w.log.Error("failed to send WeCom webhook", "error", err, "notification", w.Name)
return false, err
}
return true, nil
}
func (w *WeComNotifier) SendResolved() bool {
return !w.GetDisableResolveMessage()
}

View File

@@ -0,0 +1,132 @@
package channels
import (
"context"
"encoding/json"
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
)
func TestWeComNotifier(t *testing.T) {
tmpl := templateForTests(t)
externalURL, err := url.Parse("http://localhost")
require.NoError(t, err)
tmpl.ExternalURL = externalURL
cases := []struct {
name string
settings string
alerts []*types.Alert
expMsg map[string]interface{}
expInitError string
expMsgError error
}{
{
name: "Default config with one alert",
settings: `{"url": "http://localhost"}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: map[string]interface{}{
"markdown": map[string]interface{}{
"content": "# [FIRING:1] (val1)\n**Firing**\n\nValue: <no value>\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n\n",
},
"msgtype": "markdown",
},
expMsgError: nil,
}, {
name: "Custom config with multiple alerts",
settings: `{
"url": "http://localhost",
"message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved"
}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1"},
},
}, {
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
Annotations: model.LabelSet{"ann1": "annv2"},
},
},
},
expMsg: map[string]interface{}{
"markdown": map[string]interface{}{
"content": "# [FIRING:2] \n2 alerts are firing, 0 are resolved\n",
},
"msgtype": "markdown",
},
expMsgError: nil,
}, {
name: "Error in initing",
settings: `{}`,
expInitError: `failed to validate receiver "wecom_testing" of type "wecom": could not find webhook URL in settings`,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
settingsJSON, err := simplejson.NewJson([]byte(c.settings))
require.NoError(t, err)
m := &NotificationChannelConfig{
Name: "wecom_testing",
Type: "wecom",
Settings: settingsJSON,
}
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
decryptFn := secretsService.GetDecryptedValue
pn, err := NewWeComNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Equal(t, c.expInitError, err.Error())
return
}
require.NoError(t, err)
body := ""
bus.AddHandlerCtx("test", func(ctx context.Context, webhook *models.SendWebhookSync) error {
body = webhook.Body
return nil
})
ctx := notify.WithGroupKey(context.Background(), "alertname")
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
ok, err := pn.Notify(ctx, c.alerts...)
if c.expMsgError != nil {
require.False(t, ok)
require.Error(t, err)
require.Equal(t, c.expMsgError.Error(), err.Error())
return
}
require.NoError(t, err)
require.True(t, ok)
expBody, err := json.Marshal(c.expMsg)
require.NoError(t, err)
require.JSONEq(t, string(expBody), body)
})
}
}

View File

@@ -1294,6 +1294,47 @@ var expAvailableChannelJsonOutput = `
}
]
},
{
"type": "wecom",
"name": "WeCom",
"heading": "WeCom settings",
"description": "Send alerts generated by Grafana to WeCom",
"info": "",
"options": [
{
"element": "input",
"inputType": "text",
"label": "Url",
"description": "",
"placeholder": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx",
"propertyName": "url",
"selectOptions": null,
"showWhen": {
"field": "",
"is": ""
},
"required": true,
"validationRule": "",
"secure": true
},
{
"element": "textarea",
"inputType": "",
"label": "Message",
"description": "Custom WeCom message. You can use template variables.",
"placeholder": "{{ template \"default.message\" . }}",
"propertyName": "message",
"selectOptions": null,
"showWhen": {
"field": "",
"is": ""
},
"required": false,
"validationRule": "",
"secure": false
}
]
},
{
"type": "prometheus-alertmanager",
"name": "Alertmanager",