Merge pull request #417 from rhazdon/master

Add VictorOps Notifier
This commit is contained in:
Fabian Reinartz 2016-07-27 11:58:32 -07:00 committed by GitHub
commit 42696d91de
6 changed files with 228 additions and 64 deletions

View File

@ -217,6 +217,17 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
ogc.APIHost += "/"
}
}
for _, voc := range rcv.VictorOpsConfigs {
if voc.APIURL == "" {
if c.Global.VictorOpsAPIURL == "" {
return fmt.Errorf("no global VictorOps URL set")
}
voc.APIURL = c.Global.VictorOpsAPIURL
}
if !strings.HasSuffix(voc.APIURL, "/") {
voc.APIURL += "/"
}
}
names[rcv.Name] = struct{}{}
}
@ -264,6 +275,7 @@ var DefaultGlobalConfig = GlobalConfig{
PagerdutyURL: "https://events.pagerduty.com/generic/2010-04-15/create_event.json",
HipchatURL: "https://api.hipchat.com/",
OpsGenieAPIHost: "https://api.opsgenie.com/",
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
}
// GlobalConfig defines configuration parameters that are valid globally
@ -284,6 +296,7 @@ type GlobalConfig struct {
HipchatURL string `yaml:"hipchat_url"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token"`
OpsGenieAPIHost string `yaml:"opsgenie_api_host"`
VictorOpsAPIURL string `yaml:"victorops_api_url"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@ -412,6 +425,7 @@ type Receiver struct {
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`

View File

@ -93,6 +93,16 @@ var (
// TODO: Add a details field with all the alerts.
}
// DefaultVictorOpsConfig defines default values for VictorOps configurations.
DefaultVictorOpsConfig = VictorOpsConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
MessageType: `CRITICAL`,
StateMessage: `{{ template "victorops.default.message" . }}`,
From: `{{ template "victorops.default.from" . }}`,
}
// DefaultPushoverConfig defines default values for Pushover configurations.
DefaultPushoverConfig = PushoverConfig{
NotifierConfig: NotifierConfig{
@ -309,6 +319,36 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
return checkOverflow(c.XXX, "opsgenie config")
}
// VictorOpsConfig configures notifications via VictorOps.
type VictorOpsConfig struct {
NotifierConfig `yaml:",inline"`
APIKey Secret `yaml:"api_key"`
APIURL string `yaml:"api_url"`
RoutingKey string `yaml:"routing_key"`
MessageType string `yaml:"message_type"`
StateMessage string `yaml:"message"`
From string `yaml:"from"`
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *VictorOpsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultVictorOpsConfig
type plain VictorOpsConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.APIKey == "" {
return fmt.Errorf("missing API key in VictorOps config")
}
if c.RoutingKey == "" {
return fmt.Errorf("missing Routing key in VictorOps config")
}
return checkOverflow(c.XXX, "victorops config")
}
type duration time.Duration
func (d *duration) UnmarshalText(text []byte) error {

View File

@ -136,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.VictorOpsConfigs {
n := NewVictorOps(c, tmpl)
add(i, n, filter(n, c))
}
for i, c := range nc.PushoverConfigs {
n := NewPushover(c, tmpl)
add(i, n, filter(n, c))
@ -730,6 +734,108 @@ func (n *OpsGenie) Notify(ctx context.Context, as ...*types.Alert) error {
return nil
}
// VictorOps implements a Notifier for VictorOps notifications.
type VictorOps struct {
conf *config.VictorOpsConfig
tmpl *template.Template
}
// NewVictorOps returns a new VictorOps notifier.
func NewVictorOps(c *config.VictorOpsConfig, t *template.Template) *VictorOps {
return &VictorOps{
conf: c,
tmpl: t,
}
}
func (*VictorOps) name() string { return "victorops" }
const (
victorOpsEventTrigger = "CRITICAL"
victorOpsEventResolve = "RECOVERY"
)
type victorOpsMessage struct {
MessageType string `json:"message_type"`
EntityID model.Fingerprint `json:"entity_id"`
StateMessage string `json:"state_message"`
From string `json:"monitoring_tool"`
}
type victorOpsErrorResponse struct {
Result string `json:"result"`
Message string `json:"message"`
}
// Notify implements the Notifier interface.
func (n *VictorOps) Notify(ctx context.Context, as ...*types.Alert) error {
victorOpsAllowedEvents := map[string]bool{
"INFO": true,
"WARNING": true,
"CRITICAL": true,
}
key, ok := GroupKey(ctx)
if !ok {
return fmt.Errorf("group key missing")
}
var err error
var (
alerts = types.Alerts(as...)
data = n.tmpl.Data(receiver(ctx), groupLabels(ctx), as...)
tmpl = tmplText(n.tmpl, data, &err)
apiURL = fmt.Sprintf("%s%s/%s", n.conf.APIURL, n.conf.APIKey, n.conf.RoutingKey)
messageType = n.conf.MessageType
)
if alerts.Status() == model.AlertFiring && !victorOpsAllowedEvents[messageType] {
messageType = victorOpsEventTrigger
}
if alerts.Status() == model.AlertResolved {
messageType = victorOpsEventResolve
}
msg := &victorOpsMessage{
MessageType: messageType,
EntityID: key,
StateMessage: tmpl(n.conf.StateMessage),
From: tmpl(n.conf.From),
}
if err != nil {
return fmt.Errorf("templating error: %s", err)
}
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
return err
}
resp, err := ctxhttp.Post(ctx, http.DefaultClient, apiURL, contentTypeJSON, &buf)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
body, _ := ioutil.ReadAll(resp.Body)
var responseMessage victorOpsErrorResponse
if err := json.Unmarshal(body, &responseMessage); err != nil {
return fmt.Errorf("could not parse error response %q", body)
}
log.With("incident", key).Debugf("unexpected VictorOps response from %s (POSTed %s), %s: %s", apiURL, msg, resp.Status, body)
return fmt.Errorf("error when posting alert: result %q, message %q",
responseMessage.Result, responseMessage.Message)
}
return nil
}
// Pushover implements a Notifier for Pushover notifications.
type Pushover struct {
conf *config.PushoverConfig

View File

@ -45,6 +45,10 @@ Alerts Resolved:
{{ define "opsgenie.default.source" }}{{ template "__alertmanagerURL" . }}{{ end }}
{{ define "victorops.default.message" }}{{ template "__subject" . }} | {{ template "__alertmanagerURL" . }}{{ end }}
{{ define "victorops.default.from" }}{{ template "__alertmanager" . }}{{ end }}
{{ define "email.default.subject" }}{{ template "__subject" . }}{{ end }}
{{ define "email.default.html" }}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -93,7 +97,7 @@ SOFTWARE.
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background-color: #E6522C; margin: 0; padding: 20px;" align="center" bgcolor="#E6522C" valign="top">
{{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for {{ range .GroupLabels.SortedPairs }}
{{ .Name }}={{ .Value }}
{{ .Name }}={{ .Value }}
{{ end }}
</td>
</tr>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long