Add opsgenie notifier

This commit is contained in:
Jimmi Dyson 2015-11-24 22:29:25 +00:00
parent ab2a66c6b7
commit 3e7d614eb2
4 changed files with 144 additions and 5 deletions

View File

@ -163,6 +163,17 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
pdc.URL = c.Global.PagerdutyURL
}
}
for _, ogc := range rcv.OpsGenieConfigs {
if ogc.APIHost == "" {
if c.Global.OpsGenieAPIHost == "" {
return fmt.Errorf("no global OpsGenie URL set")
}
ogc.APIHost = c.Global.OpsGenieAPIHost
}
if !strings.HasSuffix(ogc.APIHost, "/") {
ogc.APIHost += "/"
}
}
names[rcv.Name] = struct{}{}
}
return checkOverflow(c.XXX, "config")
@ -170,16 +181,18 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
// DefaultGlobalConfig provides global default values.
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",
OpsGenieAPIHost: "https://api.opsgenie.com/",
}
// GlobalConfig defines configuration parameters that are valid globally
// unless overwritten.
type GlobalConfig struct {
SMTPFrom string `yaml:"smtp_from"`
SMTPSmarthost string `yaml:"smtp_smarthost"`
SlackURL string `yaml:"slack_url"`
PagerdutyURL string `yaml:"pagerduty_url"`
SMTPFrom string `yaml:"smtp_from"`
SMTPSmarthost string `yaml:"smtp_smarthost"`
SlackURL string `yaml:"slack_url"`
PagerdutyURL string `yaml:"pagerduty_url"`
OpsGenieAPIHost string `yaml:"opsgenie_api_host"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@ -309,6 +322,7 @@ type Receiver struct {
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs"`
SlackConfigs []*SlackConfig `yaml:"slack_configs"`
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs"`
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`

View File

@ -47,6 +47,12 @@ var (
Text: `{{template "slack.default.text" . }}`,
Fallback: `{{template "slack.default.fallback" . }}`,
}
// DefaultOpsGenieConfig defines default values for OpsGenie configurations.
DefaultOpsGenieConfig = OpsGenieConfig{
Description: `{{template "opsgenie.default.description" .}}`,
// TODO: Add a details field with all the alerts.
}
)
// FlowdockConfig configures notifications via Flowdock.
@ -280,3 +286,27 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
return checkOverflow(c.XXX, "slack config")
}
// OpsGenieConfig configures notifications via OpsGenie.
type OpsGenieConfig struct {
APIKey string `yaml:"api_key"`
APIHost string `yaml:"api_host"`
Description string `yaml:"description"`
Details map[string]string `yaml:"details"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultOpsGenieConfig
type plain OpsGenieConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.APIKey == "" {
return fmt.Errorf("missing service key in OpsGenie config")
}
return checkOverflow(c.XXX, "opsgenie config")
}

View File

@ -58,6 +58,9 @@ func Build(confs []*config.Receiver, tmpl *template.Template) map[string]Fanout
for i, c := range nc.PagerdutyConfigs {
add(i, NewPagerDuty(c, tmpl))
}
for i, c := range nc.OpsGenieConfigs {
add(i, NewOpsGenie(c, tmpl))
}
res[nc.Name] = fo
}
@ -516,3 +519,93 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error {
return nil
}
// OpsGenie implements a Notifier for OpsGenie notifications.
type OpsGenie struct {
conf *config.OpsGenieConfig
tmpl *template.Template
}
// NewOpsGenieDuty returns a new OpsGenie notifier.
func NewOpsGenie(c *config.OpsGenieConfig, t *template.Template) *OpsGenie {
return &OpsGenie{conf: c, tmpl: t}
}
type opsGenieMessage struct {
APIKey string `json:"apiKey"`
Alias model.Fingerprint `json:"alias"`
}
type opsGenieCreateMessage struct {
*opsGenieMessage `json:,inline`
Message string `json:"message"`
Details map[string]string `json:"details"`
}
type opsGenieCloseMessage struct {
*opsGenieMessage `json:,inline`
}
// Notify implements the Notifier interface.
func (n *OpsGenie) Notify(ctx context.Context, as ...*types.Alert) error {
data := generateTemplateData(ctx, as...)
key, ok := GroupKey(ctx)
if !ok {
return fmt.Errorf("group key missing")
}
log.With("incident", key).Debugln("notifying OpsGenie")
var err error
tmpl := func(name string) (s string) {
if err != nil {
return
}
s, err = n.tmpl.ExecuteTextString(name, data)
return s
}
details := make(map[string]string, len(n.conf.Details))
for k, v := range n.conf.Details {
details[k] = tmpl(v)
}
var (
msg interface{}
apiURL string
)
apiMsg := opsGenieMessage{
APIKey: n.conf.APIKey,
Alias: key,
}
alerts := types.Alerts(as...)
switch alerts.Status() {
case model.AlertResolved:
apiURL = n.conf.APIHost + "v1/json/alert/close"
msg = &opsGenieCloseMessage{&apiMsg}
default:
apiURL = n.conf.APIHost + "v1/json/alert"
msg = &opsGenieCreateMessage{
opsGenieMessage: &apiMsg,
Message: tmpl(n.conf.Description),
Details: details,
}
}
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
}
resp.Body.Close()
if resp.StatusCode/100 != 2 {
return fmt.Errorf("unexpected status code %v", resp.StatusCode)
}
return nil
}

View File

@ -17,6 +17,8 @@
{{ define "email.default.subject" }}{{ template "__subject" . }}{{ end }}
{{ define "opsgenie.default.description" }}{{ template "__subject" . }}{{ end }}
{{ define "email.default.html" }}
{{/*