Implement initial email notifications

This commit is contained in:
Fabian Reinartz 2015-10-09 12:03:15 +02:00
parent 7ef293d9bc
commit f62dc65ff4
4 changed files with 133 additions and 9 deletions

View File

@ -36,6 +36,13 @@ var (
Fallback: "slack_default_fallback",
},
}
DefaultEmailConfig = EmailConfig{
Templates: EmailTemplates{
HTML: "email_default_html",
Plain: "email_default_plain",
},
}
)
// Configuration for notification via PagerDuty.
@ -63,12 +70,21 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
// Configuration for notification via mail.
type EmailConfig struct {
// Email address to notify.
Email string `yaml:"email"`
Email string `yaml:"email"`
Smarthost string `yaml:"smarthost,omitempty"`
Sender string `yaml:"sender"`
Templates EmailTemplates `yaml:"templates"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
type EmailTemplates struct {
HTML string `yaml:"html"`
Plain string `yaml:"plain"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain EmailConfig
@ -78,6 +94,12 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
if c.Email == "" {
return fmt.Errorf("missing email address in email config")
}
if c.Sender == "" {
return fmt.Errorf("missing SMTP sender in email config")
}
if c.Smarthost == "" {
return fmt.Errorf("missing smart host in email config")
}
return checkOverflow(c.XXX, "email config")
}

View File

@ -17,12 +17,12 @@ import (
"database/sql"
"flag"
"fmt"
"html/template"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"text/template"
"github.com/prometheus/common/log"
"github.com/prometheus/common/route"

View File

@ -2,10 +2,16 @@ package notify
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"html/template"
"net"
"net/http"
"text/template"
"net/smtp"
"os"
"strings"
"time"
"github.com/prometheus/common/log"
"github.com/prometheus/common/model"
@ -31,8 +37,11 @@ func Build(confs []*config.NotificationConfig) map[string]Notifier {
Notifier: NewWebhook(wc),
})
}
for range nc.EmailConfigs {
all = append(all, &LogNotifier{Log: log.With("name", nc.Name)})
for _, ec := range nc.EmailConfigs {
all = append(all, &LogNotifier{
Log: log.With("notifier", "email"),
Notifier: NewEmail(ec),
})
}
res[nc.Name] = all
@ -81,6 +90,97 @@ func (w *Webhook) Notify(ctx context.Context, alerts ...*types.Alert) error {
return nil
}
type Email struct {
conf *config.EmailConfig
}
func NewEmail(ec *config.EmailConfig) *Email {
return &Email{conf: ec}
}
func (n *Email) auth(mechs string) (smtp.Auth, *tls.Config, error) {
username := os.Getenv("SMTP_AUTH_USERNAME")
for _, mech := range strings.Split(mechs, " ") {
switch mech {
case "CRAM-MD5":
secret := os.Getenv("SMTP_AUTH_SECRET")
if secret == "" {
continue
}
return smtp.CRAMMD5Auth(username, secret), nil, nil
case "PLAIN":
password := os.Getenv("SMTP_AUTH_PASSWORD")
if password == "" {
continue
}
identity := os.Getenv("SMTP_AUTH_IDENTITY")
// We need to know the hostname for both auth and TLS.
host, _, err := net.SplitHostPort(n.conf.Smarthost)
if err != nil {
return nil, nil, fmt.Errorf("invalid address: %s", err)
}
var (
auth = smtp.PlainAuth(identity, username, password, host)
cfg = &tls.Config{ServerName: host}
)
return auth, cfg, nil
}
}
return nil, nil, nil
}
func (n *Email) Notify(ctx context.Context, as ...*types.Alert) error {
// Connect to the SMTP smarthost.
c, err := smtp.Dial(n.conf.Smarthost)
if err != nil {
return err
}
defer c.Quit()
if ok, mech := c.Extension("AUTH"); ok {
auth, tlsConf, err := n.auth(mech)
if err != nil {
return err
}
if tlsConf != nil {
if err := c.StartTLS(tlsConf); err != nil {
return fmt.Errorf("starttls failed: %s", err)
}
}
if auth != nil {
if err := c.Auth(auth); err != nil {
return fmt.Errorf("%T failed: %s", auth, err)
}
}
}
c.Mail(n.conf.Sender)
c.Rcpt(n.conf.Email)
// Send the email body.
wc, err := c.Data()
if err != nil {
return err
}
defer wc.Close()
// TODO(fabxc): do a multipart write that considers the plain template.
return tmpl.ExecuteTemplate(wc, n.conf.Templates.HTML, struct {
Alerts model.Alerts
From string
To string
Date string
}{
Alerts: types.Alerts(as...),
From: n.conf.Sender,
To: n.conf.Email,
Date: time.Now().Format(time.RFC1123Z),
})
}
type Slack struct {
conf *config.SlackConfig
}
@ -112,14 +212,13 @@ type slackAttachmentField struct {
}
func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error {
alerts := types.Alerts(as...)
var (
alerts = types.Alerts(as...)
color = n.conf.ColorResolved
status = string(model.AlertResolved)
status = string(alerts.Status())
)
if alerts.HasFiring() {
color = n.conf.ColorFiring
status = string(model.AlertFiring)
}
var title, link, pretext, text, fallback bytes.Buffer

View File

@ -86,10 +86,13 @@ func (ns Notifiers) Notify(ctx context.Context, alerts ...*types.Alert) error {
var wg sync.WaitGroup
wg.Add(len(ns))
failed := false
for _, n := range ns {
go func(n Notifier) {
err := n.Notify(ctx, alerts...)
if err != nil {
failed = true
log.Errorf("Error on notify: %s", err)
}
wg.Done()
@ -98,7 +101,7 @@ func (ns Notifiers) Notify(ctx context.Context, alerts ...*types.Alert) error {
wg.Wait()
return nil
return fmt.Errorf("some errors occurred")
}
type RetryNotifier struct {