Add VictorOps Notifier

Add default VictorOpsAPIURL

Add VictorOps default config

Add VictorOpsConfig struct in notifiers

Add new template tags for victorops

Add notifications logic for victorops

Compiled template tags with make assets

Remove common labels from entity_id template

Set messageType default value to CRITICAL

Recovery messageType is not configurable anymore. Firing state only allows specific keys

Make assets

Using log.Debugf

EntityID should not be configureable

Remove entity_id from template

Use GroupKey(ctx) as entity_id

Improve debug logging

Fix type of entity_id
This commit is contained in:
Djordje Atlialp 2016-07-03 17:33:44 +02:00
parent 9a2132a45b
commit 8e0f405e67
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 += "/" 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{}{} names[rcv.Name] = struct{}{}
} }
@ -264,6 +275,7 @@ var DefaultGlobalConfig = GlobalConfig{
PagerdutyURL: "https://events.pagerduty.com/generic/2010-04-15/create_event.json", PagerdutyURL: "https://events.pagerduty.com/generic/2010-04-15/create_event.json",
HipchatURL: "https://api.hipchat.com/", HipchatURL: "https://api.hipchat.com/",
OpsGenieAPIHost: "https://api.opsgenie.com/", OpsGenieAPIHost: "https://api.opsgenie.com/",
VictorOpsAPIURL: "https://alert.victorops.com/integrations/generic/20131114/alert/",
} }
// GlobalConfig defines configuration parameters that are valid globally // GlobalConfig defines configuration parameters that are valid globally
@ -284,6 +296,7 @@ type GlobalConfig struct {
HipchatURL string `yaml:"hipchat_url"` HipchatURL string `yaml:"hipchat_url"`
HipchatAuthToken Secret `yaml:"hipchat_auth_token"` HipchatAuthToken Secret `yaml:"hipchat_auth_token"`
OpsGenieAPIHost string `yaml:"opsgenie_api_host"` OpsGenieAPIHost string `yaml:"opsgenie_api_host"`
VictorOpsAPIURL string `yaml:"victorops_api_url"`
} }
// UnmarshalYAML implements the yaml.Unmarshaler interface. // UnmarshalYAML implements the yaml.Unmarshaler interface.
@ -412,6 +425,7 @@ type Receiver struct {
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty"` WebhookConfigs []*WebhookConfig `yaml:"webhook_configs,omitempty"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty"` OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty"` PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty"`
// Catches all undefined fields and must be empty after parsing. // Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"` XXX map[string]interface{} `yaml:",inline"`

View File

@ -93,6 +93,16 @@ var (
// TODO: Add a details field with all the alerts. // 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 defines default values for Pushover configurations.
DefaultPushoverConfig = PushoverConfig{ DefaultPushoverConfig = PushoverConfig{
NotifierConfig: NotifierConfig{ NotifierConfig: NotifierConfig{
@ -309,6 +319,36 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
return checkOverflow(c.XXX, "opsgenie config") 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 type duration time.Duration
func (d *duration) UnmarshalText(text []byte) error { 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) n := NewHipchat(c, tmpl)
add(i, n, filter(n, c)) 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 { for i, c := range nc.PushoverConfigs {
n := NewPushover(c, tmpl) n := NewPushover(c, tmpl)
add(i, n, filter(n, c)) add(i, n, filter(n, c))
@ -730,6 +734,108 @@ func (n *OpsGenie) Notify(ctx context.Context, as ...*types.Alert) error {
return nil 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. // Pushover implements a Notifier for Pushover notifications.
type Pushover struct { type Pushover struct {
conf *config.PushoverConfig conf *config.PushoverConfig

View File

@ -45,6 +45,10 @@ Alerts Resolved:
{{ define "opsgenie.default.source" }}{{ template "__alertmanagerURL" . }}{{ end }} {{ 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.subject" }}{{ template "__subject" . }}{{ end }}
{{ define "email.default.html" }} {{ define "email.default.html" }}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!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;"> <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"> <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 }} {{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for {{ range .GroupLabels.SortedPairs }}
{{ .Name }}={{ .Value }} {{ .Name }}={{ .Value }}
{{ end }} {{ end }}
</td> </td>
</tr> </tr>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long