Re-implement pushover notifications

This feature was dropped during the rewrite, but I use and like
Pushover.

fixes #107
This commit is contained in:
Michael Stapelberg 2016-02-26 09:35:00 +01:00
parent a31e60bf12
commit 4c0aa00bcf
4 changed files with 142 additions and 0 deletions

View File

@ -389,6 +389,7 @@ type Receiver struct {
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty"`
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`

View File

@ -16,6 +16,7 @@ package config
import (
"fmt"
"strings"
"time"
)
var (
@ -89,6 +90,19 @@ var (
Source: `{{ template "opsgenie.default.source" . }}`,
// TODO: Add a details field with all the alerts.
}
// DefaultPushoverConfig defines default values for Pushover configurations.
DefaultPushoverConfig = PushoverConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Title: `{{ template "pushover.default.title" . }}`,
Message: `{{ template "pushover.default.message" . }}`,
URL: `{{ template "pushover.default.url" . }}`,
Priority: `{{ if eq .Status "firing" }}2{{ else }}0{{ end }}`, // emergency (firing) or normal
Retry: duration(1 * time.Minute),
Expire: duration(1 * time.Hour),
}
)
// NotifierConfig contains base options common across all notifier configurations.
@ -283,3 +297,49 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
}
return checkOverflow(c.XXX, "opsgenie config")
}
type duration time.Duration
func (d *duration) UnmarshalText(text []byte) error {
parsed, err := time.ParseDuration(string(text))
if err == nil {
*d = duration(parsed)
}
return err
}
func (d duration) MarshalText() ([]byte, error) {
return []byte(time.Duration(d).String()), nil
}
type PushoverConfig struct {
NotifierConfig `yaml:",inline"`
UserKey Secret `yaml:"user_key"`
Token Secret `yaml:"token"`
Title string `yaml:"title"`
Message string `yaml:"message"`
URL string `yaml:"url"`
Priority string `yaml:"priority"`
Retry duration `yaml:"retry"`
Expire duration `yaml:"expire"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultPushoverConfig
type plain PushoverConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.UserKey == "" {
return fmt.Errorf("missing user key in Pushover config")
}
if c.Token == "" {
return fmt.Errorf("missing token in Pushover config")
}
return checkOverflow(c.XXX, "pushover config")
}

View File

@ -19,11 +19,13 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime"
"net"
"net/http"
"net/mail"
"net/smtp"
"net/url"
"os"
"strings"
"time"
@ -134,6 +136,10 @@ func Build(confs []*config.Receiver, tmpl *template.Template) map[string]Fanout
n := NewHipchat(c, tmpl)
add(i, n, filter(n, c))
}
for i, c := range nc.PushoverConfigs {
n := NewPushover(c, tmpl)
add(i, n, filter(n, c))
}
res[nc.Name] = fo
}
@ -682,6 +688,76 @@ func (n *OpsGenie) Notify(ctx context.Context, as ...*types.Alert) error {
return nil
}
// Pushover implements a Notifier for Pushover notifications.
type Pushover struct {
conf *config.PushoverConfig
tmpl *template.Template
}
// NewPushover returns a new Pushover notifier.
func NewPushover(c *config.PushoverConfig, t *template.Template) *Pushover {
return &Pushover{conf: c, tmpl: t}
}
func (*Pushover) name() string { return "pushover" }
// Notify implements the Notifier interface.
func (n *Pushover) Notify(ctx context.Context, as ...*types.Alert) error {
key, ok := GroupKey(ctx)
if !ok {
return fmt.Errorf("group key missing")
}
data := n.tmpl.Data(receiver(ctx), groupLabels(ctx), as...)
log.With("incident", key).Debugln("notifying Pushover")
var err error
tmpl := tmplText(n.tmpl, data, &err)
parameters := url.Values{}
parameters.Add("token", tmpl(string(n.conf.Token)))
parameters.Add("user", tmpl(string(n.conf.UserKey)))
title := tmpl(n.conf.Title)
message := tmpl(n.conf.Message)
parameters.Add("title", title)
if len(title)+len(message) > 512 {
message = message[:512]
log.With("incident", key).Debugf("Truncated message to %q due to Pushover message limit", message)
}
if message == "" {
// Pushover rejects empty messages.
message = "(no details)"
}
parameters.Add("message", message)
parameters.Add("url", tmpl(n.conf.URL))
parameters.Add("priority", tmpl(n.conf.Priority))
parameters.Add("retry", fmt.Sprintf("%d", int64(time.Duration(n.conf.Retry).Seconds())))
parameters.Add("expire", fmt.Sprintf("%d", int64(time.Duration(n.conf.Expire).Seconds())))
apiURL := "https://api.pushover.net/1/messages.json"
u, err := url.Parse(apiURL)
if err != nil {
return err
}
u.RawQuery = parameters.Encode()
log.With("incident", key).Debugf("Pushover URL = %q", u.String())
resp, err := ctxhttp.Post(ctx, http.DefaultClient, u.String(), "text/plain", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return fmt.Errorf("unexpected status code %v (body: %s)", resp.StatusCode, string(body))
}
return nil
}
func tmplText(tmpl *template.Template, data *template.Data, err *error) func(string) string {
return func(name string) (s string) {
if *err != nil {

View File

@ -162,3 +162,8 @@ SOFTWARE.
</html>
{{ end }}
{{ define "pushover.default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "pushover.default.message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }}
{{ template "__text_alert_list" .Alerts.Firing }}{{ end }}
{{ define "pushover.default.url" }}{{ template "__alertmanagerURL" . }}{{ end }}