Added flowdock notifier

This commit is contained in:
Tomas Karasek 2015-05-18 19:07:56 +03:00
parent 82ced0dc2a
commit bfcded79e8
4 changed files with 152 additions and 2 deletions

View File

@ -77,6 +77,14 @@ func (c Config) Validate() error {
return fmt.Errorf("Missing webhook URL in Slack config: %s", proto.MarshalTextString(sc))
}
}
for _, fc := range nc.FlowdockConfig {
if fc.ApiToken == nil {
return fmt.Errorf("Missing API token in Flowdock config: %s", proto.MarshalTextString(fc))
}
if fc.FromAddress == nil {
return fmt.Errorf("Missing from_address Flowdock config: %s", proto.MarshalTextString(fc))
}
}
if _, ok := ncNames[nc.GetName()]; ok {
return fmt.Errorf("Notification config name not unique: %s", nc.GetName())

View File

@ -70,6 +70,17 @@ message SlackConfig {
optional bool send_resolved = 5 [default = false];
}
message FlowdockConfig {
// Flowdock flow API token
optional string api_token = 2;
// Flowdock from_address
optional string from_address = 3;
// Flowdock flow tags
repeated string tags = 4;
// Color of message when triggered.
optional bool send_resolved = 7 [default = false];
}
// Notification configuration definition.
message NotificationConfig {
// Name of this NotificationConfig. Referenced from AggregationRule.
@ -84,6 +95,8 @@ message NotificationConfig {
repeated HipChatConfig hipchat_config = 5;
// Zero or more slack notification configurations.
repeated SlackConfig slack_config = 6;
// Zero or more Flowdock notification configurations.
repeated FlowdockConfig flowdock_config = 7;
}
// A regex-based label filter used in aggregations.

View File

@ -14,6 +14,7 @@ It has these top-level messages:
PushoverConfig
HipChatConfig
SlackConfig
FlowdockConfig
NotificationConfig
Filter
AggregationRule
@ -241,6 +242,52 @@ func (m *SlackConfig) GetSendResolved() bool {
return Default_SlackConfig_SendResolved
}
type FlowdockConfig struct {
// Flowdock flow API token
ApiToken *string `protobuf:"bytes,2,opt,name=api_token" json:"api_token,omitempty"`
// Flowdock from_address
FromAddress *string `protobuf:"bytes,3,opt,name=from_address" json:"from_address,omitempty"`
// Flowdock flow tags
Tags []string `protobuf:"bytes,4,rep,name=tags" json:"tags,omitempty"`
// Color of message when triggered.
SendResolved *bool `protobuf:"varint,7,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *FlowdockConfig) Reset() { *m = FlowdockConfig{} }
func (m *FlowdockConfig) String() string { return proto.CompactTextString(m) }
func (*FlowdockConfig) ProtoMessage() {}
const Default_FlowdockConfig_SendResolved bool = false
func (m *FlowdockConfig) GetApiToken() string {
if m != nil && m.ApiToken != nil {
return *m.ApiToken
}
return ""
}
func (m *FlowdockConfig) GetFromAddress() string {
if m != nil && m.FromAddress != nil {
return *m.FromAddress
}
return ""
}
func (m *FlowdockConfig) GetTags() []string {
if m != nil {
return m.Tags
}
return nil
}
func (m *FlowdockConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_FlowdockConfig_SendResolved
}
// Notification configuration definition.
type NotificationConfig struct {
// Name of this NotificationConfig. Referenced from AggregationRule.
@ -254,8 +301,10 @@ type NotificationConfig struct {
// Zero or more hipchat notification configurations.
HipchatConfig []*HipChatConfig `protobuf:"bytes,5,rep,name=hipchat_config" json:"hipchat_config,omitempty"`
// Zero or more slack notification configurations.
SlackConfig []*SlackConfig `protobuf:"bytes,6,rep,name=slack_config" json:"slack_config,omitempty"`
XXX_unrecognized []byte `json:"-"`
SlackConfig []*SlackConfig `protobuf:"bytes,6,rep,name=slack_config" json:"slack_config,omitempty"`
// Zero or more Flowdock notification configurations.
FlowdockConfig []*FlowdockConfig `protobuf:"bytes,7,rep,name=flowdock_config" json:"flowdock_config,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *NotificationConfig) Reset() { *m = NotificationConfig{} }
@ -304,6 +353,13 @@ func (m *NotificationConfig) GetSlackConfig() []*SlackConfig {
return nil
}
func (m *NotificationConfig) GetFlowdockConfig() []*FlowdockConfig {
if m != nil {
return m.FlowdockConfig
}
return nil
}
// A regex-based label filter used in aggregations.
type Filter struct {
// The regex matching the label name.

View File

@ -65,6 +65,7 @@ var (
smtpSmartHost = flag.String("notification.smtp.smarthost", "", "Address of the smarthost to send all email notifications to.")
smtpSender = flag.String("notification.smtp.sender", "alertmanager@example.org", "Sender email address to use in email notifications.")
hipchatURL = flag.String("notification.hipchat.url", "https://api.hipchat.com/v2", "HipChat API V2 URL.")
flowdockURL = flag.String("notification.flowdock.url", "https://api.flowdock.com/v1/messages/team_inbox", "Flowdock API V1 URL.")
)
type notificationOp int
@ -325,6 +326,59 @@ func (n *notifier) sendSlackNotification(op notificationOp, config *pb.SlackConf
return nil
}
// https://www.flowdock.com/api/team-inbox
// compulsory fields in Flowdock JSON: source, from_address, subject, content
// Content-type: application/json
// POST to https://api.flowdock.com/v1/messages/team_inbox/:flow_api_token
type FlowdockMessage struct {
Source string `json:"source"`
FromAddress string `json:"from_address"`
Subject string `json:"subject"`
Content string `json:"content"`
Format string `json:"format"`
Link string `json:"link"`
Tags []string `json:"tags,omitempty"`
}
func jsonize(msg interface{}) []byte {
buf, err := json.Marshal(msg)
if err != nil {
glog.Errorln("Error compiling JSON:", err)
}
return buf
}
func getFlowdockNotificationMessage(op notificationOp, config *pb.FlowdockConfig, a *Alert) *FlowdockMessage {
status := ""
switch op {
case notificationOpTrigger:
status = "firing"
case notificationOpResolve:
status = "resolved"
}
msg := &FlowdockMessage{
Source: "Prometheus",
FromAddress: config.GetFromAddress(),
Subject: html.EscapeString(a.Summary),
Format: "html",
Content: fmt.Sprintf("*%s %s*: %s (<%s|view>)", html.EscapeString(a.Labels["alertname"]), status, html.EscapeString(a.Summary), a.Payload["GeneratorURL"]),
Link: a.Payload["GeneratorURL"],
Tags: config.GetTags(),
}
return msg
}
func postJSONtoURL(jsonMessage []byte, url string) *http.Response {
timeout := time.Duration(5 * time.Second)
client := http.Client{
Timeout: timeout,
}
response, err := client.Post(url, contentTypeJSON, bytes.NewBuffer(jsonMessage))
if err != nil {
glog.Errorln("Error while sending Flowdock notification:", err)
}
return response
}
func writeEmailBody(w io.Writer, from, to, status string, a *Alert) error {
return writeEmailBodyWithTime(w, from, to, status, a, time.Now())
}
@ -452,6 +506,16 @@ func (n *notifier) sendPushoverNotification(token string, op notificationOp, use
return err
}
func processResponse(r *http.Response, targetName string, a *Alert) {
defer r.Body.Close()
respBuf, err := ioutil.ReadAll(r.Body)
if err != nil {
glog.Errorln("Error reading HTTP response:", err)
}
glog.Infof("Sent %s notification for alert %v. Response: HTTP %d: %s", targetName, a.Fingerprint(), r.StatusCode, respBuf)
}
func (n *notifier) handleNotification(a *Alert, op notificationOp, config *pb.NotificationConfig) {
for _, pdConfig := range config.PagerdutyConfig {
if err := n.sendPagerDutyNotification(pdConfig.GetServiceKey(), op, a); err != nil {
@ -494,6 +558,15 @@ func (n *notifier) handleNotification(a *Alert, op notificationOp, config *pb.No
glog.Errorln("Error sending Slack notification:", err)
}
}
for _, fdConfig := range config.FlowdockConfig {
if op == notificationOpResolve && !fdConfig.GetSendResolved() {
continue
}
flowdockMessage := getFlowdockNotificationMessage(op, fdConfig, a)
url := *flowdockURL + "/" + fdConfig.GetApiToken()
httpResponse := postJSONtoURL(jsonize(flowdockMessage), url)
processResponse(httpResponse, "Flowdock", a)
}
}
func (n *notifier) Dispatch() {