mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
[Alerting]: Add Pushover integration with the alert manager (#34371)
* [Alerting]: Add Pushover integration with the alert manager * lint * Set boundary only for tests * Remove title field * fix imports
This commit is contained in:
parent
1d2febfa85
commit
a79a4838b8
@ -412,6 +412,8 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
||||
n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template.
|
||||
case "pagerduty":
|
||||
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
|
||||
case "pushover":
|
||||
n, err = channels.NewPushoverNotifier(cfg, tmpl)
|
||||
case "slack":
|
||||
n, err = channels.NewSlackNotifier(cfg, tmpl)
|
||||
case "telegram":
|
||||
|
@ -4,6 +4,103 @@ import "github.com/grafana/grafana/pkg/services/alerting"
|
||||
|
||||
// GetAvailableNotifiers returns the metadata of all the notification channels that can be configured.
|
||||
func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
||||
pushoverSoundOptions := []alerting.SelectOption{
|
||||
{
|
||||
Value: "default",
|
||||
Label: "Default",
|
||||
},
|
||||
{
|
||||
Value: "pushover",
|
||||
Label: "Pushover",
|
||||
}, {
|
||||
Value: "bike",
|
||||
Label: "Bike",
|
||||
}, {
|
||||
Value: "bugle",
|
||||
Label: "Bugle",
|
||||
}, {
|
||||
Value: "cashregister",
|
||||
Label: "Cashregister",
|
||||
}, {
|
||||
Value: "classical",
|
||||
Label: "Classical",
|
||||
}, {
|
||||
Value: "cosmic",
|
||||
Label: "Cosmic",
|
||||
}, {
|
||||
Value: "falling",
|
||||
Label: "Falling",
|
||||
}, {
|
||||
Value: "gamelan",
|
||||
Label: "Gamelan",
|
||||
}, {
|
||||
Value: "incoming",
|
||||
Label: "Incoming",
|
||||
}, {
|
||||
Value: "intermission",
|
||||
Label: "Intermission",
|
||||
}, {
|
||||
Value: "magic",
|
||||
Label: "Magic",
|
||||
}, {
|
||||
Value: "mechanical",
|
||||
Label: "Mechanical",
|
||||
}, {
|
||||
Value: "pianobar",
|
||||
Label: "Pianobar",
|
||||
}, {
|
||||
Value: "siren",
|
||||
Label: "Siren",
|
||||
}, {
|
||||
Value: "spacealarm",
|
||||
Label: "Spacealarm",
|
||||
}, {
|
||||
Value: "tugboat",
|
||||
Label: "Tugboat",
|
||||
}, {
|
||||
Value: "alien",
|
||||
Label: "Alien",
|
||||
}, {
|
||||
Value: "climb",
|
||||
Label: "Climb",
|
||||
}, {
|
||||
Value: "persistent",
|
||||
Label: "Persistent",
|
||||
}, {
|
||||
Value: "echo",
|
||||
Label: "Echo",
|
||||
}, {
|
||||
Value: "updown",
|
||||
Label: "Updown",
|
||||
}, {
|
||||
Value: "none",
|
||||
Label: "None",
|
||||
},
|
||||
}
|
||||
|
||||
pushoverPriorityOptions := []alerting.SelectOption{
|
||||
{
|
||||
Value: "2",
|
||||
Label: "Emergency",
|
||||
},
|
||||
{
|
||||
Value: "1",
|
||||
Label: "High",
|
||||
},
|
||||
{
|
||||
Value: "0",
|
||||
Label: "Normal",
|
||||
},
|
||||
{
|
||||
Value: "-1",
|
||||
Label: "Low",
|
||||
},
|
||||
{
|
||||
Value: "-2",
|
||||
Label: "Lowest",
|
||||
},
|
||||
}
|
||||
|
||||
return []*alerting.NotifierPlugin{
|
||||
{
|
||||
Type: "dingding",
|
||||
@ -137,6 +234,85 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "pushover",
|
||||
Name: "Pushover",
|
||||
Description: "Sends HTTP POST request to the Pushover API",
|
||||
Heading: "Pushover settings",
|
||||
Options: []alerting.NotifierOption{
|
||||
{
|
||||
Label: "API Token",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "Application token",
|
||||
PropertyName: "apiToken",
|
||||
Required: true,
|
||||
Secure: true,
|
||||
},
|
||||
{
|
||||
Label: "User key(s)",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "comma-separated list",
|
||||
PropertyName: "userKey",
|
||||
Required: true,
|
||||
Secure: true,
|
||||
},
|
||||
{
|
||||
Label: "Device(s) (optional)",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "comma-separated list; leave empty to send to all devices",
|
||||
PropertyName: "device",
|
||||
},
|
||||
{
|
||||
Label: "Alerting priority",
|
||||
Element: alerting.ElementTypeSelect,
|
||||
SelectOptions: pushoverPriorityOptions,
|
||||
PropertyName: "priority",
|
||||
},
|
||||
{
|
||||
Label: "OK priority",
|
||||
Element: alerting.ElementTypeSelect,
|
||||
SelectOptions: pushoverPriorityOptions,
|
||||
PropertyName: "okPriority",
|
||||
},
|
||||
{
|
||||
Description: "How often (in seconds) the Pushover servers will send the same alerting or OK notification to the user.",
|
||||
Label: "Retry (Only used for Emergency Priority)",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "minimum 30 seconds",
|
||||
PropertyName: "retry",
|
||||
},
|
||||
{
|
||||
Description: "How many seconds the alerting or OK notification will continue to be retried.",
|
||||
Label: "Expire (Only used for Emergency Priority)",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "maximum 86400 seconds",
|
||||
PropertyName: "expire",
|
||||
},
|
||||
{
|
||||
Label: "Alerting sound",
|
||||
Element: alerting.ElementTypeSelect,
|
||||
SelectOptions: pushoverSoundOptions,
|
||||
PropertyName: "sound",
|
||||
},
|
||||
{
|
||||
Label: "OK sound",
|
||||
Element: alerting.ElementTypeSelect,
|
||||
SelectOptions: pushoverSoundOptions,
|
||||
PropertyName: "okSound",
|
||||
},
|
||||
{ // New in 8.0.
|
||||
Label: "Message",
|
||||
Element: alerting.ElementTypeTextArea,
|
||||
Placeholder: `{{ template "default.message" . }}`,
|
||||
PropertyName: "message",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "slack",
|
||||
Name: "Slack",
|
||||
|
250
pkg/services/ngalert/notifier/channels/pushover.go
Normal file
250
pkg/services/ngalert/notifier/channels/pushover.go
Normal file
@ -0,0 +1,250 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
PUSHOVERENDPOINT = "https://api.pushover.net/1/messages.json"
|
||||
)
|
||||
|
||||
// getBoundary is used for overriding the behaviour for tests
|
||||
// and set a boundary
|
||||
var getBoundary = func() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// PushoverNotifier is responsible for sending
|
||||
// alert notifications to Pushover
|
||||
type PushoverNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
UserKey string
|
||||
APIToken string
|
||||
AlertingPriority int
|
||||
OKPriority int
|
||||
Retry int
|
||||
Expire int
|
||||
Device string
|
||||
AlertingSound string
|
||||
OKSound string
|
||||
Upload bool
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewSlackNotifier is the constructor for the Slack notifier
|
||||
func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template) (*PushoverNotifier, error) {
|
||||
userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString())
|
||||
APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString())
|
||||
device := model.Settings.Get("device").MustString()
|
||||
alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert alerting priority to integer: %w", err)
|
||||
}
|
||||
okPriority, err := strconv.Atoi(model.Settings.Get("okPriority").MustString("0")) // default Normal
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert OK priority to integer: %w", err)
|
||||
}
|
||||
retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
|
||||
expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString())
|
||||
alertingSound := model.Settings.Get("sound").MustString()
|
||||
okSound := model.Settings.Get("okSound").MustString()
|
||||
uploadImage := model.Settings.Get("uploadImage").MustBool(true)
|
||||
|
||||
if userKey == "" {
|
||||
return nil, alerting.ValidationError{Reason: "user key not found"}
|
||||
}
|
||||
if APIToken == "" {
|
||||
return nil, alerting.ValidationError{Reason: "API token not found"}
|
||||
}
|
||||
return &PushoverNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
DisableResolveMessage: model.DisableResolveMessage,
|
||||
Settings: model.Settings,
|
||||
SecureSettings: model.SecureSettings,
|
||||
}),
|
||||
UserKey: userKey,
|
||||
APIToken: APIToken,
|
||||
AlertingPriority: alertingPriority,
|
||||
OKPriority: okPriority,
|
||||
Retry: retry,
|
||||
Expire: expire,
|
||||
Device: device,
|
||||
AlertingSound: alertingSound,
|
||||
OKSound: okSound,
|
||||
Upload: uploadImage,
|
||||
Message: model.Settings.Get("message").MustString(`{{ template "default.message" .}}`),
|
||||
tmpl: t,
|
||||
log: log.New("alerting.notifier.pushover"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Notify sends an alert notification to Slack.
|
||||
func (pn *PushoverNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
headers, uploadBody, err := pn.genPushoverBody(ctx, as...)
|
||||
if err != nil {
|
||||
pn.log.Error("Failed to generate body for pushover", "error", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: PUSHOVERENDPOINT,
|
||||
HttpMethod: "POST",
|
||||
HttpHeader: headers,
|
||||
Body: uploadBody.String(),
|
||||
}
|
||||
|
||||
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
||||
pn.log.Error("Failed to send pushover notification", "error", err, "webhook", pn.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
func (pn *PushoverNotifier) SendResolved() bool {
|
||||
return !pn.GetDisableResolveMessage()
|
||||
}
|
||||
|
||||
func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Alert) (map[string]string, bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
u, err := url.Parse(pn.tmpl.ExternalURL.String())
|
||||
if err != nil {
|
||||
return nil, b, fmt.Errorf("failed to parse ")
|
||||
}
|
||||
u.Path = path.Join(u.Path, "/alerting/list")
|
||||
ruleURL := u.String()
|
||||
|
||||
alerts := types.Alerts(as...)
|
||||
|
||||
var tmplErr error
|
||||
data := notify.GetTemplateData(ctx, pn.tmpl, as, gokit_log.NewNopLogger())
|
||||
tmpl := notify.TmplText(pn.tmpl, data, &tmplErr)
|
||||
|
||||
w := multipart.NewWriter(&b)
|
||||
boundary := getBoundary()
|
||||
if boundary != "" {
|
||||
err = w.SetBoundary(boundary)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the user token
|
||||
err = w.WriteField("user", pn.UserKey)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
// Add the api token
|
||||
err = w.WriteField("token", pn.APIToken)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
// Add priority
|
||||
priority := pn.AlertingPriority
|
||||
if alerts.Status() == model.AlertResolved {
|
||||
priority = pn.OKPriority
|
||||
}
|
||||
err = w.WriteField("priority", strconv.Itoa(priority))
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
if priority == 2 {
|
||||
err = w.WriteField("retry", strconv.Itoa(pn.Retry))
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
err = w.WriteField("expire", strconv.Itoa(pn.Expire))
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add device
|
||||
if pn.Device != "" {
|
||||
err = w.WriteField("device", pn.Device)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add sound
|
||||
sound := pn.AlertingSound
|
||||
if alerts.Status() == model.AlertResolved {
|
||||
sound = pn.OKSound
|
||||
}
|
||||
if sound != "default" {
|
||||
err = w.WriteField("sound", sound)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add title
|
||||
err = w.WriteField("title", tmpl(`{{ template "default.title" . }}`))
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
// Add URL
|
||||
err = w.WriteField("url", ruleURL)
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
// Add URL title
|
||||
err = w.WriteField("url_title", "Show alert rule")
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
// Add message
|
||||
err = w.WriteField("message", tmpl(pn.Message))
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
return nil, b, errors.Wrap(tmplErr, "failed to template pushover message")
|
||||
}
|
||||
|
||||
// Mark as html message
|
||||
err = w.WriteField("html", "1")
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": w.FormDataContentType(),
|
||||
}
|
||||
|
||||
return headers, b, nil
|
||||
}
|
203
pkg/services/ngalert/notifier/channels/pushover_test.go
Normal file
203
pkg/services/ngalert/notifier/channels/pushover_test.go
Normal file
@ -0,0 +1,203 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPushoverNotifier(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]string
|
||||
expInitError error
|
||||
expMsgError error
|
||||
}{
|
||||
{
|
||||
name: "Correct config with one alert",
|
||||
settings: `{
|
||||
"userKey": "<userKey>",
|
||||
"apiToken": "<apiToken>"
|
||||
}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]string{
|
||||
"user": "<userKey>",
|
||||
"token": "<apiToken>",
|
||||
"priority": "0",
|
||||
"sound": "",
|
||||
"title": "[FIRING:1] (rule uid val1)",
|
||||
"url": "http://localhost/alerting/list",
|
||||
"url_title": "Show alert rule",
|
||||
"message": "\n**Firing**\nLabels:\n - alertname = alert1\n - __alert_rule_uid__ = rule uid\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"html": "1",
|
||||
},
|
||||
expInitError: nil,
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Custom config with multiple alerts",
|
||||
settings: `{
|
||||
"userKey": "<userKey>",
|
||||
"apiToken": "<apiToken>",
|
||||
"device": "device",
|
||||
"priority": "2",
|
||||
"okpriority": "0",
|
||||
"retry": "30",
|
||||
"expire": "86400",
|
||||
"sound": "echo",
|
||||
"oksound": "magic",
|
||||
"message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved"
|
||||
}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "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]string{
|
||||
"user": "<userKey>",
|
||||
"token": "<apiToken>",
|
||||
"priority": "2",
|
||||
"sound": "echo",
|
||||
"title": "[FIRING:2] ",
|
||||
"url": "http://localhost/alerting/list",
|
||||
"url_title": "Show alert rule",
|
||||
"message": "2 alerts are firing, 0 are resolved",
|
||||
"html": "1",
|
||||
"retry": "30",
|
||||
"expire": "86400",
|
||||
"device": "device",
|
||||
},
|
||||
expInitError: nil,
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Missing user key",
|
||||
settings: `{
|
||||
"apiToken": "<apiToken>"
|
||||
}`,
|
||||
expInitError: alerting.ValidationError{Reason: "user key not found"},
|
||||
}, {
|
||||
name: "Missing api key",
|
||||
settings: `{
|
||||
"userKey": "<userKey>"
|
||||
}`,
|
||||
expInitError: alerting.ValidationError{Reason: "API token not found"},
|
||||
}, {
|
||||
name: "Error in building message",
|
||||
settings: `{
|
||||
"apiToken": "<apiToken>",
|
||||
"userKey": "<userKey>",
|
||||
"message": "{{ .BrokenTemplate }"
|
||||
}`,
|
||||
expMsgError: errors.New("failed to template pushover message: template: :1: unexpected \"}\" in operand"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
origGetBoundary := getBoundary
|
||||
boundary := "abcd"
|
||||
getBoundary = func() string {
|
||||
return boundary
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
getBoundary = origGetBoundary
|
||||
})
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
settingsJSON, err := simplejson.NewJson([]byte(c.settings))
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &NotificationChannelConfig{
|
||||
Name: "pushover_testing",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
pn, err := NewPushoverNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), 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.Error(t, err)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, c.expMsgError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
bodyReader := multipart.NewReader(strings.NewReader(body), boundary)
|
||||
for {
|
||||
part, err := bodyReader.NextPart()
|
||||
if part == nil || errors.Is(err, io.EOF) {
|
||||
assert.Empty(t, c.expMsg, fmt.Sprintf("expected fields %v", c.expMsg))
|
||||
break
|
||||
}
|
||||
formField := part.FormName()
|
||||
expected, ok := c.expMsg[formField]
|
||||
assert.True(t, ok, fmt.Sprintf("unexpected field %s", formField))
|
||||
actual := []byte("")
|
||||
if expected != "" {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(part)
|
||||
require.NoError(t, err)
|
||||
actual = buf.Bytes()
|
||||
}
|
||||
assert.Equal(t, expected, string(actual))
|
||||
delete(c.expMsg, formField)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -287,6 +287,403 @@ var expAvailableChannelJsonOutput = `
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "pushover",
|
||||
"name": "Pushover",
|
||||
"description": "Sends HTTP POST request to the Pushover API",
|
||||
"heading": "Pushover settings",
|
||||
"info": "",
|
||||
"options": [
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "API Token",
|
||||
"description": "",
|
||||
"placeholder": "Application token",
|
||||
"propertyName": "apiToken",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": true,
|
||||
"validationRule": "",
|
||||
"secure": true
|
||||
},
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "User key(s)",
|
||||
"description": "",
|
||||
"placeholder": "comma-separated list",
|
||||
"propertyName": "userKey",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": true,
|
||||
"validationRule": "",
|
||||
"secure": true
|
||||
},
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "Device(s) (optional)",
|
||||
"description": "",
|
||||
"placeholder": "comma-separated list; leave empty to send to all devices",
|
||||
"propertyName": "device",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "select",
|
||||
"inputType": "",
|
||||
"label": "Alerting priority",
|
||||
"description": "",
|
||||
"placeholder": "",
|
||||
"propertyName": "priority",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Emergency"
|
||||
},
|
||||
{
|
||||
"value": "1",
|
||||
"label": "High"
|
||||
},
|
||||
{
|
||||
"value": "0",
|
||||
"label": "Normal"
|
||||
},
|
||||
{
|
||||
"value": "-1",
|
||||
"label": "Low"
|
||||
},
|
||||
{
|
||||
"value": "-2",
|
||||
"label": "Lowest"
|
||||
}
|
||||
],
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "select",
|
||||
"inputType": "",
|
||||
"label": "OK priority",
|
||||
"description": "",
|
||||
"placeholder": "",
|
||||
"propertyName": "okPriority",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Emergency"
|
||||
},
|
||||
{
|
||||
"value": "1",
|
||||
"label": "High"
|
||||
},
|
||||
{
|
||||
"value": "0",
|
||||
"label": "Normal"
|
||||
},
|
||||
{
|
||||
"value": "-1",
|
||||
"label": "Low"
|
||||
},
|
||||
{
|
||||
"value": "-2",
|
||||
"label": "Lowest"
|
||||
}
|
||||
],
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "Retry (Only used for Emergency Priority)",
|
||||
"description": "How often (in seconds) the Pushover servers will send the same alerting or OK notification to the user.",
|
||||
"placeholder": "minimum 30 seconds",
|
||||
"propertyName": "retry",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "Expire (Only used for Emergency Priority)",
|
||||
"description": "How many seconds the alerting or OK notification will continue to be retried.",
|
||||
"placeholder": "maximum 86400 seconds",
|
||||
"propertyName": "expire",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "select",
|
||||
"inputType": "",
|
||||
"label": "Alerting sound",
|
||||
"description": "",
|
||||
"placeholder": "",
|
||||
"propertyName": "sound",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": "default",
|
||||
"label": "Default"
|
||||
},
|
||||
{
|
||||
"value": "pushover",
|
||||
"label": "Pushover"
|
||||
},
|
||||
{
|
||||
"value": "bike",
|
||||
"label": "Bike"
|
||||
},
|
||||
{
|
||||
"value": "bugle",
|
||||
"label": "Bugle"
|
||||
},
|
||||
{
|
||||
"value": "cashregister",
|
||||
"label": "Cashregister"
|
||||
},
|
||||
{
|
||||
"value": "classical",
|
||||
"label": "Classical"
|
||||
},
|
||||
{
|
||||
"value": "cosmic",
|
||||
"label": "Cosmic"
|
||||
},
|
||||
{
|
||||
"value": "falling",
|
||||
"label": "Falling"
|
||||
},
|
||||
{
|
||||
"value": "gamelan",
|
||||
"label": "Gamelan"
|
||||
},
|
||||
{
|
||||
"value": "incoming",
|
||||
"label": "Incoming"
|
||||
},
|
||||
{
|
||||
"value": "intermission",
|
||||
"label": "Intermission"
|
||||
},
|
||||
{
|
||||
"value": "magic",
|
||||
"label": "Magic"
|
||||
},
|
||||
{
|
||||
"value": "mechanical",
|
||||
"label": "Mechanical"
|
||||
},
|
||||
{
|
||||
"value": "pianobar",
|
||||
"label": "Pianobar"
|
||||
},
|
||||
{
|
||||
"value": "siren",
|
||||
"label": "Siren"
|
||||
},
|
||||
{
|
||||
"value": "spacealarm",
|
||||
"label": "Spacealarm"
|
||||
},
|
||||
{
|
||||
"value": "tugboat",
|
||||
"label": "Tugboat"
|
||||
},
|
||||
{
|
||||
"value": "alien",
|
||||
"label": "Alien"
|
||||
},
|
||||
{
|
||||
"value": "climb",
|
||||
"label": "Climb"
|
||||
},
|
||||
{
|
||||
"value": "persistent",
|
||||
"label": "Persistent"
|
||||
},
|
||||
{
|
||||
"value": "echo",
|
||||
"label": "Echo"
|
||||
},
|
||||
{
|
||||
"value": "updown",
|
||||
"label": "Updown"
|
||||
},
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
}
|
||||
],
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "select",
|
||||
"inputType": "",
|
||||
"label": "OK sound",
|
||||
"description": "",
|
||||
"placeholder": "",
|
||||
"propertyName": "okSound",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": "default",
|
||||
"label": "Default"
|
||||
},
|
||||
{
|
||||
"value": "pushover",
|
||||
"label": "Pushover"
|
||||
},
|
||||
{
|
||||
"value": "bike",
|
||||
"label": "Bike"
|
||||
},
|
||||
{
|
||||
"value": "bugle",
|
||||
"label": "Bugle"
|
||||
},
|
||||
{
|
||||
"value": "cashregister",
|
||||
"label": "Cashregister"
|
||||
},
|
||||
{
|
||||
"value": "classical",
|
||||
"label": "Classical"
|
||||
},
|
||||
{
|
||||
"value": "cosmic",
|
||||
"label": "Cosmic"
|
||||
},
|
||||
{
|
||||
"value": "falling",
|
||||
"label": "Falling"
|
||||
},
|
||||
{
|
||||
"value": "gamelan",
|
||||
"label": "Gamelan"
|
||||
},
|
||||
{
|
||||
"value": "incoming",
|
||||
"label": "Incoming"
|
||||
},
|
||||
{
|
||||
"value": "intermission",
|
||||
"label": "Intermission"
|
||||
},
|
||||
{
|
||||
"value": "magic",
|
||||
"label": "Magic"
|
||||
},
|
||||
{
|
||||
"value": "mechanical",
|
||||
"label": "Mechanical"
|
||||
},
|
||||
{
|
||||
"value": "pianobar",
|
||||
"label": "Pianobar"
|
||||
},
|
||||
{
|
||||
"value": "siren",
|
||||
"label": "Siren"
|
||||
},
|
||||
{
|
||||
"value": "spacealarm",
|
||||
"label": "Spacealarm"
|
||||
},
|
||||
{
|
||||
"value": "tugboat",
|
||||
"label": "Tugboat"
|
||||
},
|
||||
{
|
||||
"value": "alien",
|
||||
"label": "Alien"
|
||||
},
|
||||
{
|
||||
"value": "climb",
|
||||
"label": "Climb"
|
||||
},
|
||||
{
|
||||
"value": "persistent",
|
||||
"label": "Persistent"
|
||||
},
|
||||
{
|
||||
"value": "echo",
|
||||
"label": "Echo"
|
||||
},
|
||||
{
|
||||
"value": "updown",
|
||||
"label": "Updown"
|
||||
},
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
}
|
||||
],
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "textarea",
|
||||
"inputType": "",
|
||||
"label": "Message",
|
||||
"description": "",
|
||||
"placeholder": "{{ template \"default.message\" . }}",
|
||||
"propertyName": "message",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "slack",
|
||||
"name": "Slack",
|
||||
|
Loading…
Reference in New Issue
Block a user