Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify Mattermost via Incoming Webhooks #206

Merged
merged 2 commits into from Dec 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -450,6 +450,23 @@ prefix: `consul-alerts/config/notifiers/mattermost/`
| team | The mattermost team (mandatory) |
| channel | The channel to post the notification (mandatory) |

Notifications can also be sent to [Incoming
Webhooks](https://docs.mattermost.com/developer/webhooks-incoming.html). To enable, set
`consul-alerts/config/notifiers/mattermost-webhook/enabled` to `true` and set
`consul-alerts/config/notifiers/mattermost-webhook/url` to URL of the webhook created on
the previous step.

prefix: `consul-alerts/config/notifiers/mattermost-webhook/`

| key | description |
|--------------|----------------------------------------------------- |
| enabled | Enable the Slack notifier. [Default: false] |
| cluster-name | The name of the cluster. [Default: `Consul Alerts`] |
| url | The incoming-webhook url (mandatory) [eg: `https://mattermost.com/hooks/...`] |
| channel | The channel to post the notification (mandatory) [eg: `consul-alerts`] |
| username | The username to appear on the post [eg: `Consul Alerts`] |
| icon-url | URL of a custom image for the notification [eg: `http://someimage.com/someimage.png`] |

#### PagerDuty

To enable PagerDuty built-in notifier, set `consul-alerts/config/notifiers/pagerduty/enabled` to `true`. This is disabled by default. Service key and client details also needs to be configured.
Expand Down
4 changes: 4 additions & 0 deletions consul-alerts.go
Expand Up @@ -243,6 +243,7 @@ func builtinNotifiers() map[string]notifier.Notifier {
influxdbNotifier := consulClient.InfluxdbNotifier()
slackNotifier := consulClient.SlackNotifier()
mattermostNotifier := consulClient.MattermostNotifier()
mattermostWebhookNotifier := consulClient.MattermostWebhookNotifier()
pagerdutyNotifier := consulClient.PagerDutyNotifier()
hipchatNotifier := consulClient.HipChatNotifier()
opsgenieNotifier := consulClient.OpsGenieNotifier()
Expand All @@ -265,6 +266,9 @@ func builtinNotifiers() map[string]notifier.Notifier {
if mattermostNotifier.Enabled {
notifiers[mattermostNotifier.NotifierName()] = mattermostNotifier
}
if mattermostWebhookNotifier.Enabled {
notifiers[mattermostWebhookNotifier.NotifierName()] = mattermostWebhookNotifier
}
if pagerdutyNotifier.Enabled {
notifiers[pagerdutyNotifier.NotifierName()] = pagerdutyNotifier
}
Expand Down
30 changes: 27 additions & 3 deletions consul/client.go
Expand Up @@ -170,6 +170,20 @@ func (c *ConsulAlertClient) LoadConfig() {
case "consul-alerts/config/notifiers/mattermost/detailed":
valErr = loadCustomValue(&config.Notifiers.Mattermost.Detailed, val, ConfigTypeBool)

// mattermost webhook notifier config
case "consul-alerts/config/notifiers/mattermost-webhook/enabled":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.Enabled, val, ConfigTypeBool)
case "consul-alerts/config/notifiers/mattermost-webhook/cluster-name":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.ClusterName, val, ConfigTypeString)
case "consul-alerts/config/notifiers/mattermost-webhook/url":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.Url, val, ConfigTypeString)
case "consul-alerts/config/notifiers/mattermost-webhook/channel":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.Channel, val, ConfigTypeString)
case "consul-alerts/config/notifiers/mattermost-webhook/username":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.Username, val, ConfigTypeString)
case "consul-alerts/config/notifiers/mattermost-webhook/icon-url":
valErr = loadCustomValue(&config.Notifiers.MattermostWebhook.IconUrl, val, ConfigTypeString)

// pager-duty notfier config
case "consul-alerts/config/notifiers/pagerduty/enabled":
valErr = loadCustomValue(&config.Notifiers.PagerDuty.Enabled, val, ConfigTypeBool)
Expand Down Expand Up @@ -492,6 +506,10 @@ func (c *ConsulAlertClient) MattermostNotifier() *notifier.MattermostNotifier {
return c.config.Notifiers.Mattermost
}

func (c *ConsulAlertClient) MattermostWebhookNotifier() *notifier.MattermostWebhookNotifier {
return c.config.Notifiers.MattermostWebhook
}

func (c *ConsulAlertClient) PagerDutyNotifier() *notifier.PagerDutyNotifier {
return c.config.Notifiers.PagerDuty
}
Expand Down Expand Up @@ -749,21 +767,27 @@ func (c *ConsulAlertClient) IsBlacklisted(check *Check) bool {

node := check.Node
nodeCheckKey := fmt.Sprintf("consul-alerts/config/checks/blacklist/nodes/%s", node)
nodeBlacklisted := func() bool { return c.CheckKeyExists(nodeCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/nodes", node) }
nodeBlacklisted := func() bool {
return c.CheckKeyExists(nodeCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/nodes", node)
}

service := "_"
serviceBlacklisted := func() bool { return false }
if check.ServiceID != "" {
service = check.ServiceID
serviceCheckKey := fmt.Sprintf("consul-alerts/config/checks/blacklist/services/%s", service)

serviceBlacklisted = func() bool { return c.CheckKeyExists(serviceCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/services", service) }
serviceBlacklisted = func() bool {
return c.CheckKeyExists(serviceCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/services", service)
}
}

checkID := check.CheckID
checkCheckKey := fmt.Sprintf("consul-alerts/config/checks/blacklist/checks/%s", checkID)

checkBlacklisted := func() bool { return c.CheckKeyExists(checkCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/checks", checkID) }
checkBlacklisted := func() bool {
return c.CheckKeyExists(checkCheckKey) || c.CheckKeyMatchesRegexp("consul-alerts/config/checks/blacklist/checks", checkID)
}

singleKey := fmt.Sprintf("consul-alerts/config/checks/blacklist/single/%s/%s/%s", node, service, checkID)
singleBlacklisted := func() bool { return c.CheckKeyExists(singleKey) }
Expand Down
30 changes: 18 additions & 12 deletions consul/interface.go
Expand Up @@ -74,6 +74,7 @@ type Consul interface {
InfluxdbNotifier() *notifier.InfluxdbNotifier
SlackNotifier() *notifier.SlackNotifier
MattermostNotifier() *notifier.MattermostNotifier
MattermostWebhookNotifier() *notifier.MattermostWebhookNotifier
PagerDutyNotifier() *notifier.PagerDutyNotifier
HipChatNotifier() *notifier.HipChatNotifier
OpsGenieNotifier() *notifier.OpsGenieNotifier
Expand Down Expand Up @@ -139,6 +140,11 @@ func DefaultAlertConfig() *ConsulAlertConfig {
ClusterName: "Consul-Alerts",
}

mattermostWebhook := &notifier.MattermostWebhookNotifier{
Enabled: false,
ClusterName: "Consul-Alerts",
}

pagerduty := &notifier.PagerDutyNotifier{
Enabled: false,
}
Expand All @@ -163,18 +169,18 @@ func DefaultAlertConfig() *ConsulAlertConfig {
}

notifiers := &notifier.Notifiers{
Email: email,
Log: log,
Influxdb: influxdb,
Slack: slack,
Mattermost: mattermost,
PagerDuty: pagerduty,
HipChat: hipchat,
OpsGenie: opsgenie,
AwsSns: awsSns,
VictorOps: victorOps,
Custom: []string{},

Email: email,
Log: log,
Influxdb: influxdb,
Slack: slack,
Mattermost: mattermost,
MattermostWebhook: mattermostWebhook,
PagerDuty: pagerduty,
HipChat: hipchat,
OpsGenie: opsgenie,
AwsSns: awsSns,
VictorOps: victorOps,
Custom: []string{},
}

return &ConsulAlertConfig{
Expand Down
78 changes: 78 additions & 0 deletions notifier/mattermost-webhook-notifier.go
@@ -0,0 +1,78 @@
package notifier

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"

log "github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/Sirupsen/logrus"
)

type MattermostWebhookNotifier struct {
ClusterName string `json:"cluster_name"`
Url string `json:"-"`
Channel string `json:"channel"`
Username string `json:"username"`
IconUrl string `json:"icon_url"`
Text string `json:"text,omitempty"`
Detailed bool `json:"-"`
Enabled bool `json:"enabled"`
}

// NotifierName provides name for notifier selection
func (n *MattermostWebhookNotifier) NotifierName() string {
return "mattermost-webhook"
}

func (n *MattermostWebhookNotifier) Copy() Notifier {
notifier := *n
return &notifier
}

//Notify sends messages to the endpoint notifier
func (n *MattermostWebhookNotifier) Notify(messages Messages) bool {
return n.notifySimple(messages)
}

func (n *MattermostWebhookNotifier) notifySimple(messages Messages) bool {
overallStatus, pass, warn, fail := messages.Summary()
text := fmt.Sprintf(header, n.ClusterName, overallStatus, fail, warn, pass)
for _, message := range messages {
text += fmt.Sprintf("\n%s:%s:%s is %s.", message.Node, message.Service, message.Check, message.Status)
text += fmt.Sprintf("\n%s", message.Output)
}
n.Text = text
return n.postToMattermostWebhook()
}

func (n *MattermostWebhookNotifier) postToMattermostWebhook() bool {
jsonData, err := json.Marshal(n)
if err != nil {
log.Println("Unable to marshal Mattermost payload:", err)
return false
}

values := url.Values{}
values.Set("payload", string(jsonData))
data := values.Encode()
log.Debugf("struct = %+v, payload = %s", n, string(data))

b := bytes.NewBufferString(data)
res, err := http.Post(n.Url, "application/x-www-form-urlencoded", b)
if err != nil {
log.Println("Unable to send data to Mattermost:", err)
return false
}
defer res.Body.Close()
statusCode := res.StatusCode
if statusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
log.Println("Unable to notify Mattermost:", string(body))
return false
}
log.Println("Mattermost notification sent.")
return true
}
86 changes: 86 additions & 0 deletions notifier/mattermost-webhook-notifier_test.go
@@ -0,0 +1,86 @@
package notifier

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
)

func TestMattermostWebhookJsonUnmarshalling(t *testing.T) {
cluster_name := "my_cluster"
channel := "test_channel"
username := "test_username"
icon_url := "test_icon_url"
text := "test_text"

expectedNotifier := MattermostWebhookNotifier{
ClusterName: cluster_name,
Channel: channel,
Username: username,
IconUrl: icon_url,
Text: text,
}
var unmarshalledNotifier MattermostWebhookNotifier

data := []byte(fmt.Sprintf(`{
"cluster_name": "%s",
"channel": "%s",
"username": "%s",
"icon_url": "%s",
"text": "%s"
}`, cluster_name, channel, username, icon_url, text))

fmt.Printf("%s\n", data)
if err := json.Unmarshal(data, &unmarshalledNotifier); err != nil {
t.Error(err.Error())
}

if !reflect.DeepEqual(expectedNotifier, unmarshalledNotifier) {
t.Fatalf("Expected mattermostWebhookNotifier to be %s, got %s\n", expectedNotifier, unmarshalledNotifier)
}
}

func TestMattermostWebhookPost(t *testing.T) {
cluster_name := "my_cluster"
channel := "test_channel"
username := "test_username"
icon_url := "test_icon_url"
text := "test_text"
enabled := true

expectedValues := url.Values{}
expectedValues.Set("payload", fmt.Sprintf(`{"cluster_name":"%s","channel":"%s","username":"%s","icon_url":"%s","text":"%s","enabled":%t}`, cluster_name, channel, username, icon_url, text, enabled))
expectedPayload := []byte(expectedValues.Encode())

var actualPayload []byte

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
actualPayload, err = ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
log.Fatal(err)
}
}))
defer ts.Close()

n := MattermostWebhookNotifier{
Url: ts.URL,
ClusterName: cluster_name,
Channel: channel,
Username: username,
IconUrl: icon_url,
Text: text,
Enabled: enabled,
}
n.postToMattermostWebhook()
if string(expectedPayload) != string(actualPayload) {
t.Errorf("Expected request body to be %s, got %s\n", expectedPayload, actualPayload)
}
}
25 changes: 14 additions & 11 deletions notifier/notifier.go
Expand Up @@ -41,17 +41,18 @@ type Notifier interface {
}

type Notifiers struct {
Email *EmailNotifier `json:"email"`
Log *LogNotifier `json:"log"`
Influxdb *InfluxdbNotifier `json:"influxdb"`
Slack *SlackNotifier `json:"slack"`
Mattermost *MattermostNotifier `json:"mattermost"`
PagerDuty *PagerDutyNotifier `json:"pagerduty"`
HipChat *HipChatNotifier `json:"hipchat"`
OpsGenie *OpsGenieNotifier `json:"opsgenie"`
AwsSns *AwsSnsNotifier `json:"awssns"`
VictorOps *VictorOpsNotifier `json:"victorops"`
Custom []string `json:"custom"`
Email *EmailNotifier `json:"email"`
Log *LogNotifier `json:"log"`
Influxdb *InfluxdbNotifier `json:"influxdb"`
Slack *SlackNotifier `json:"slack"`
Mattermost *MattermostNotifier `json:"mattermost"`
MattermostWebhook *MattermostWebhookNotifier `json:"mattermost-webhook"`
PagerDuty *PagerDutyNotifier `json:"pagerduty"`
HipChat *HipChatNotifier `json:"hipchat"`
OpsGenie *OpsGenieNotifier `json:"opsgenie"`
AwsSns *AwsSnsNotifier `json:"awssns"`
VictorOps *VictorOpsNotifier `json:"victorops"`
Custom []string `json:"custom"`
}

func (n Notifiers) GetNotifier(name string) (Notifier, bool) {
Expand All @@ -66,6 +67,8 @@ func (n Notifiers) GetNotifier(name string) (Notifier, bool) {
return n.Slack, true
case n.Mattermost != nil && n.Mattermost.NotifierName() == name:
return n.Mattermost, true
case n.MattermostWebhook != nil && n.MattermostWebhook.NotifierName() == name:
return n.MattermostWebhook, true
case n.HipChat != nil && n.HipChat.NotifierName() == name:
return n.HipChat, true
case n.PagerDuty != nil && n.PagerDuty.NotifierName() == name:
Expand Down
2 changes: 1 addition & 1 deletion notifier/slack_notifier_test.go
Expand Up @@ -7,7 +7,7 @@ import (
"testing"
)

func TestJsonUnmarshalling(t *testing.T) {
func TestSlackJsonUnmarshalling(t *testing.T) {
cluster_name := "my_cluster"
channel := "test_channel"
username := "test_username"
Expand Down
5 changes: 3 additions & 2 deletions send-notifs.go
Expand Up @@ -3,11 +3,12 @@ package main
import (
"bytes"
"encoding/json"
"github.com/imdario/mergo"
"github.com/mitchellh/hashstructure"
"os/exec"

log "github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/imdario/mergo"
"github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/mitchellh/hashstructure"

"github.com/AcalephStorage/consul-alerts/notifier"
)

Expand Down