This repository has been archived by the owner on Jul 27, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Daemon: Add support for slack target
- Loading branch information
Showing
5 changed files
with
248 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package target | ||
|
||
import ( | ||
"github.com/jmoiron/sqlx/types" | ||
"github.com/juju/errors" | ||
) | ||
|
||
type Slack struct { | ||
Channel string | ||
} | ||
|
||
func newSlack(configJSON types.JSONText) (Target, error) { | ||
var config SlackDBModel | ||
err := configJSON.Unmarshal(&config) | ||
if err != nil { | ||
return nil, errors.Maskf(err, "deserialize target config") | ||
} | ||
|
||
return &Slack{Channel: config.Channel}, nil | ||
} | ||
|
||
func (Slack) Type() Type { | ||
return slackType{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package target | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/juju/errors" | ||
"github.com/yext/revere/state" | ||
) | ||
|
||
const timeFormat = "Mon Jan 2 2006 15:04:05 MST" | ||
|
||
var ( | ||
stateColors = map[state.State]string{ | ||
// Green | ||
state.Normal: "good", | ||
// Yellow | ||
state.Warning: "warning", | ||
// Red | ||
state.Error: "danger", | ||
// Black | ||
state.Critical: "#000000", | ||
// Grey | ||
state.Unknown: "#808080", | ||
} | ||
) | ||
|
||
type slackNotifier struct { | ||
alert *Alert | ||
name string | ||
url string | ||
} | ||
|
||
type payload struct { | ||
Username string `json:"username"` | ||
Channel string `json:"channel,omitempty"` | ||
Attachments []attachment `json:"attachments"` | ||
} | ||
|
||
type attachment struct { | ||
Title string `json:"title"` | ||
TitleLink string `json:"title_link"` | ||
Fallback string `json:"fallback"` | ||
Color string `json:"color"` | ||
Text string `json:"text"` | ||
Timestamp int64 `json:"ts"` | ||
} | ||
|
||
func (s slackNotifier) sendAll(channels map[string]struct{}) error { | ||
var ( | ||
failedChannelNames []string | ||
err error | ||
) | ||
|
||
for channel, _ := range channels { | ||
err = s.send(channel) | ||
if err != nil { | ||
failedChannelNames = append(failedChannelNames, channel) | ||
} | ||
} | ||
|
||
if len(failedChannelNames) > 0 { | ||
return errors.Maskf(err, "sending slack notifications to %v", failedChannelNames) | ||
} | ||
return nil | ||
} | ||
|
||
func (s slackNotifier) send(channel string) error { | ||
message, err := s.formatMessage(channel) | ||
if err != nil { | ||
return errors.Maskf(err, "formatting slack message") | ||
} | ||
|
||
resp, err := http.Post(s.url, "application/json", message) | ||
if err != nil { | ||
return errors.Maskf(err, "sending slack notification to: %s", channel) | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
return errors.Errorf( | ||
"not-OK HTTP status code: %d, when sending slack notification to: %s", | ||
resp.StatusCode, | ||
channel) | ||
} | ||
return nil | ||
} | ||
|
||
func (s slackNotifier) formatMessage(channel string) (io.Reader, error) { | ||
var text string | ||
if s.alert.OldState != s.alert.NewState { | ||
text = fmt.Sprintf("State change: %s->%s", s.alert.OldState, s.alert.NewState) | ||
} else { | ||
text = fmt.Sprintf("Has been %s since: %s", | ||
s.alert.NewState, s.alert.EnteredState.UTC().Format(timeFormat)) | ||
} | ||
|
||
if s.alert.NewState != state.Normal { | ||
text = fmt.Sprintf("%s\nWas last Normal at: %s", | ||
text, s.alert.LastNormal.UTC().Format(timeFormat)) | ||
} | ||
|
||
payload := payload{ | ||
Username: s.name, | ||
Channel: channel, | ||
Attachments: []attachment{ | ||
{ | ||
Title: fmt.Sprintf("%s/%s", s.alert.MonitorName, s.alert.SubprobeName), | ||
TitleLink: fmt.Sprintf("http://revere.khan/monitors/%d/subprobes/%d", | ||
s.alert.MonitorID, s.alert.SubprobeID), | ||
Fallback: fmt.Sprintf("%s/%s entered state: %s", | ||
s.alert.MonitorName, s.alert.SubprobeName, s.alert.NewState), | ||
Color: stateColors[s.alert.NewState], | ||
Text: text, | ||
Timestamp: s.alert.Recorded.Unix(), | ||
}, | ||
}, | ||
} | ||
|
||
buf, err := json.Marshal(payload) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return bytes.NewBuffer(buf), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package target | ||
|
||
import ( | ||
"github.com/jmoiron/sqlx/types" | ||
"github.com/juju/errors" | ||
|
||
"github.com/yext/revere/db" | ||
"github.com/yext/revere/setting" | ||
) | ||
|
||
type slackType struct{} | ||
|
||
func init() { | ||
registerTargetType(slackType{}) | ||
} | ||
|
||
func (slackType) ID() db.TargetType { | ||
return 2 | ||
} | ||
|
||
func (slackType) New(config types.JSONText) (Target, error) { | ||
return newSlack(config) | ||
} | ||
|
||
func (slackType) Alert( | ||
Db *db.DB, a *Alert, toAlert map[db.TriggerID]Target, inactive []Target) []ErrorAndTriggerIDs { | ||
triggerIDs := make([]db.TriggerID, 0, len(toAlert)) | ||
for id := range toAlert { | ||
triggerIDs = append(triggerIDs, id) | ||
} | ||
|
||
channels := make(map[string]struct{}) | ||
for _, target := range toAlert { | ||
target := target.(*Slack) | ||
channels[target.Channel] = struct{}{} | ||
} | ||
|
||
slackSetting := setting.SlackSetting{} | ||
dbSettings, err := Db.LoadSettingsOfType(slackSetting.Type().Id()) | ||
if err != nil || len(dbSettings) == 0 { | ||
return []ErrorAndTriggerIDs{{ | ||
Err: errors.Maskf(err, "getting settings from db"), | ||
IDs: triggerIDs, | ||
}} | ||
} | ||
|
||
settingsFromDB, err := setting.LoadFromDB(slackSetting.Type().Id(), dbSettings[0].Setting) | ||
if err != nil { | ||
return []ErrorAndTriggerIDs{{ | ||
Err: errors.Maskf(err, "unmarshalling db settings"), | ||
IDs: triggerIDs, | ||
}} | ||
} | ||
|
||
slackSettings, found := settingsFromDB.(*setting.SlackSetting) | ||
if !found { | ||
return []ErrorAndTriggerIDs{{ | ||
Err: errors.Maskf(err, "extracting slack settings"), | ||
IDs: triggerIDs, | ||
}} | ||
} | ||
|
||
notifier := slackNotifier{ | ||
alert: a, | ||
name: slackSettings.BotName, | ||
url: slackSettings.WebhookURL, | ||
} | ||
err = notifier.sendAll(channels) | ||
if err != nil { | ||
return []ErrorAndTriggerIDs{{ | ||
Err: errors.Trace(err), | ||
IDs: triggerIDs, | ||
}} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters