Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{}
}

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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"`
Expand Down
40 changes: 40 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down
106 changes: 106 additions & 0 deletions notify/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion template/default.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down
6 changes: 3 additions & 3 deletions template/internal/deftmpl/bindata.go

Large diffs are not rendered by default.

120 changes: 60 additions & 60 deletions ui/bindata.go

Large diffs are not rendered by default.