diff --git a/README.md b/README.md index e67b280..c60507c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Run `osem_notify help config` for details and an example configuration. `transport` | `options` ------------|------------ `email` | `recipients`: list of email addresses +`slack` | - `xmpp` | `recipients`: list of JIDs Want more? [add it](#contribute)! diff --git a/cmd/cmd_root.go b/cmd/cmd_root.go index e4e7dca..e0a3d42 100644 --- a/cmd/cmd_root.go +++ b/cmd/cmd_root.go @@ -59,6 +59,10 @@ var configHelpCmd = &cobra.Command{ pass: bar from: hildegunst@example.com + # only needed when sending notifications via Slack + slack: + webhook: https://hooks.slack.com/services/T030YPW07/xxxxxxx/xxxxxxxxxxxxxxxxxxxxxx + # only needed when sending notifications via XMPP xmpp: host: jabber.example.com:5222 @@ -72,6 +76,7 @@ var configHelpCmd = &cobra.Command{ transport | options ----------|------------------------------------- email | recipients: list of email addresses + slack | - xmpp | recipients: list of JIDs diff --git a/core/notifier_slack.go b/core/notifier_slack.go new file mode 100644 index 0000000..2524737 --- /dev/null +++ b/core/notifier_slack.go @@ -0,0 +1,73 @@ +package core + +import ( + "fmt" + "io/ioutil" + "net/http" + + "github.com/dghubble/sling" + "github.com/spf13/viper" +) + +var slackClient = sling.New().Client(&http.Client{}) + +var notificationColors = map[string]string { + CheckOk: "#00ff00", + CheckErr: "#ff0000", +} + +// slack Notifier has no configuration +type SlackNotifier struct { + webhook string +} + +type SlackMessage struct { + Text string `json:"text"` + Username string `json:"username,omitempty` + Attachments []SlackAttachment `json:"attachments,omitempty"` +} + +type SlackAttachment struct { + Text string `json:"text"` + Color string `json:"color,omitempty"` +} + +func (n SlackNotifier) New(config TransportConfig) (AbstractNotifier, error) { + // validate transport configuration + // :TransportConfSourceHack + baseUrl := viper.GetString("slack.webhook") + if baseUrl == "" { + return nil, fmt.Errorf("Missing configuration key slack.webhook") + } + + return SlackNotifier{ + webhook: baseUrl, + }, nil +} + +func (n SlackNotifier) Submit(notification Notification) error { + message := &SlackMessage{ + Username: "osem_notify box healthcheck", + Text: notification.Subject, + Attachments: []SlackAttachment{ { notification.Body, notificationColors[notification.Status] } }, + } + + req, err := slackClient.Post(n.webhook).BodyJSON(message).Request() + if err != nil { + return err + } + + c := http.Client{} + res, err2 := c.Do(req) + if err2 != nil { + return err2 + } + + if res.StatusCode > 200 { + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + return fmt.Errorf("slack webhook failed: %s", body) + } + + return nil +} diff --git a/core/notifier_xmpp.go b/core/notifier_xmpp.go index 5fab50c..c18a300 100644 --- a/core/notifier_xmpp.go +++ b/core/notifier_xmpp.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/viper" ) -var client = &xmpp.Client{} // @Hacky +var xmppClient = &xmpp.Client{} // @Hacky // box config required for the XmppNotifier (TransportConfig.Options) type XmppNotifier struct { @@ -27,12 +27,12 @@ func (n XmppNotifier) New(config TransportConfig) (AbstractNotifier, error) { // establish connection with server once, and share it accross instances // @Hacky - if client.JID() == "" { + if xmppClient.JID() == "" { c, err := connectXmpp() if err != nil { return nil, err } - client = c + xmppClient = c } // assign configuration to the notifier after ensuring the correct type. @@ -65,12 +65,12 @@ func (n XmppNotifier) New(config TransportConfig) (AbstractNotifier, error) { } func (n XmppNotifier) Submit(notification Notification) error { - if client.JID() == "" { + if xmppClient.JID() == "" { return fmt.Errorf("xmpp client not correctly initialized!") } for _, recipient := range n.Recipients { - _, err := client.Send(xmpp.Chat{ + _, err := xmppClient.Send(xmpp.Chat{ Remote: recipient, Subject: notification.Subject, Text: fmt.Sprintf("%s\n\n%s", notification.Subject, notification.Body), diff --git a/core/notifiers.go b/core/notifiers.go index 4019e26..b32881a 100644 --- a/core/notifiers.go +++ b/core/notifiers.go @@ -10,6 +10,7 @@ import ( var Notifiers = map[string]AbstractNotifier{ "email": EmailNotifier{}, + "slack": SlackNotifier{}, "xmpp": XmppNotifier{}, } @@ -19,6 +20,7 @@ type AbstractNotifier interface { } type Notification struct { + Status string // one of CheckOk | CheckErr Body string Subject string } @@ -137,17 +139,21 @@ func ComposeNotification(box *Box, checks []CheckResult) Notification { resolved string resolvedList string errList string + status string ) if len(resolvedTexts) != 0 { resolvedList = fmt.Sprintf("Resolved issue(s):\n\n%s\n\n", strings.Join(resolvedTexts, "\n")) } if len(errTexts) != 0 { errList = fmt.Sprintf("New issue(s):\n\n%s\n\n", strings.Join(errTexts, "\n")) + status = CheckErr } else { resolved = "resolved " + status = CheckOk } return Notification{ + Status: status, Subject: fmt.Sprintf("Issues %swith your box \"%s\" on opensensemap.org!", resolved, box.Name), Body: fmt.Sprintf("A check at %s identified the following updates for your box \"%s\":\n\n%s%sYou may visit https://opensensemap.org/explore/%s for more details.\n\n--\nSent automatically by osem_notify (https://github.com/noerw/osem_notify)", time.Now().Round(time.Minute), box.Name, errList, resolvedList, box.Id),