Implement initial email notifications
This commit is contained in:
parent
7ef293d9bc
commit
f62dc65ff4
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -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"
|
||||
|
|
111
notify/impl.go
111
notify/impl.go
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue