diff --git a/main.go b/main.go index 65dbd6a5..8999edbf 100644 --- a/main.go +++ b/main.go @@ -17,13 +17,11 @@ import ( "database/sql" "flag" "fmt" - "html/template" "net/http" "os" "os/signal" "path/filepath" "syscall" - txttemplate "text/template" "github.com/prometheus/common/log" "github.com/prometheus/common/route" @@ -32,6 +30,7 @@ import ( "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider" + "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" ) @@ -50,6 +49,8 @@ func main() { } defer db.Close() + tmpl := &template.Template{} + alerts, err := provider.NewSQLAlerts(db) if err != nil { log.Fatal(err) @@ -115,7 +116,7 @@ func main() { for _, ec := range nc.EmailConfigs { all = append(all, ¬ify.LogNotifier{ Log: log.With("notifier", "email"), - Notifier: notify.NewEmail(ec), + Notifier: notify.NewEmail(ec, tmpl), }) } @@ -145,14 +146,13 @@ func main() { } routedNotifier.Lock() - log.Debugf("set notifiers for routes %#v", res) routedNotifier.Notifiers = res routedNotifier.Unlock() } disp := NewDispatcher(alerts, notifier) - if err := reloadConfig(*configFile, disp, types.ReloadFunc(build), inhibitor); err != nil { + if err := reloadConfig(*configFile, disp, types.ReloadFunc(build), inhibitor, tmpl); err != nil { log.Fatalf("Couldn't load configuration (-config.file=%s): %v", *configFile, err) } @@ -174,7 +174,7 @@ func main() { go func() { for range hup { - if err := reloadConfig(*configFile, disp, types.ReloadFunc(build), inhibitor); err != nil { + if err := reloadConfig(*configFile, disp, types.ReloadFunc(build), inhibitor, tmpl); err != nil { log.Errorf("Couldn't load configuration (-config.file=%s): %v", *configFile, err) } } @@ -194,23 +194,6 @@ func reloadConfig(filename string, rls ...types.Reloadable) error { return err } - t := template.New("") - for _, tpath := range conf.Templates { - t, err = t.ParseGlob(tpath) - if err != nil { - return err - } - } - tt := txttemplate.New("") - for _, tpath := range conf.Templates { - tt, err = tt.ParseGlob(tpath) - if err != nil { - return err - } - } - - notify.SetTemplate(t, tt) - for _, rl := range rls { rl.ApplyConfig(conf) } diff --git a/notify/impl.go b/notify/impl.go index a76aae24..59fb8465 100644 --- a/notify/impl.go +++ b/notify/impl.go @@ -5,13 +5,11 @@ import ( "crypto/tls" "encoding/json" "fmt" - "html/template" "net" "net/http" "net/smtp" "os" "strings" - txttemplate "text/template" "time" "github.com/prometheus/common/log" @@ -20,6 +18,7 @@ import ( "golang.org/x/net/context/ctxhttp" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" ) @@ -68,10 +67,11 @@ func (w *Webhook) Notify(ctx context.Context, alerts ...*types.Alert) error { type Email struct { conf *config.EmailConfig + tmpl *template.Template } -func NewEmail(ec *config.EmailConfig) *Email { - return &Email{conf: ec} +func NewEmail(c *config.EmailConfig, t *template.Template) *Email { + return &Email{conf: c, tmpl: t} } func (n *Email) auth(mechs string) (smtp.Auth, *tls.Config, error) { @@ -161,16 +161,17 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) error { Date: time.Now().Format(time.RFC1123Z), } - if err := txttmpl.ExecuteTemplate(wc, n.conf.Templates.Header, &data); err != nil { + if err := n.tmpl.ExecuteText(wc, n.conf.Templates.Header, &data); err != nil { return err } // TODO(fabxc): do a multipart write that considers the plain template. - return tmpl.ExecuteTemplate(wc, n.conf.Templates.HTML, &data) + return n.tmpl.ExecuteHTML(wc, n.conf.Templates.HTML, &data) } type Slack struct { conf *config.SlackConfig + tmpl *template.Template } // slackReq is the request for sending a slack notification. @@ -209,21 +210,25 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error { color = n.conf.ColorFiring } - var title, link, pretext, text, fallback bytes.Buffer - - if err := tmpl.ExecuteTemplate(&title, n.conf.Templates.Title, alerts); err != nil { - return err - } - if err := tmpl.ExecuteTemplate(&text, n.conf.Templates.Text, alerts); err != nil { - return err + var err error + tmpl := func(name string) (s string) { + if err != nil { + return + } + s, err = n.tmpl.ExecuteHTMLString(name, struct { + Alerts model.Alerts + }{ + Alerts: alerts, + }) + return s } attachment := &slackAttachment{ - Title: title.String(), - TitleLink: link.String(), - Pretext: pretext.String(), - Text: text.String(), - Fallback: fallback.String(), + Title: tmpl(n.conf.Templates.Title), + TitleLink: tmpl(n.conf.Templates.TitleLink), + Pretext: tmpl(n.conf.Templates.Pretext), + Text: tmpl(n.conf.Templates.Text), + Fallback: tmpl(n.conf.Templates.Fallback), Fields: []slackAttachmentField{{ Title: "Status", @@ -233,6 +238,10 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error { Color: color, MrkdwnIn: []string{"fallback", "pretext"}, } + if err != nil { + return err + } + req := &slackReq{ Channel: n.conf.Channel, Attachments: []slackAttachment{*attachment}, @@ -256,11 +265,3 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) error { return nil } - -var tmpl *template.Template -var txttmpl *txttemplate.Template - -func SetTemplate(t *template.Template, tt *txttemplate.Template) { - tmpl = t - txttmpl = tt -} diff --git a/template/template.go b/template/template.go new file mode 100644 index 00000000..6fd83d86 --- /dev/null +++ b/template/template.go @@ -0,0 +1,67 @@ +package template + +import ( + text_tmpl "html/template" + html_tmpl "text/template" + + "bytes" + "io" + "strings" + + "github.com/prometheus/alertmanager/config" +) + +type Template struct { + text *text_tmpl.Template + html *html_tmpl.Template +} + +type FuncMap map[string]interface{} + +var DefaultFuncs = FuncMap{ + "upper": strings.ToUpper, + "lower": strings.ToLower, +} + +func (t *Template) funcs(fm FuncMap) *Template { + t.text.Funcs(text_tmpl.FuncMap(fm)) + t.html.Funcs(html_tmpl.FuncMap(fm)) + return t +} + +func (t *Template) ExecuteTextString(name string, data interface{}) (string, error) { + var buf bytes.Buffer + err := t.ExecuteText(&buf, name, data) + return buf.String(), err +} + +func (t *Template) ExecuteText(w io.Writer, name string, data interface{}) error { + return t.text.ExecuteTemplate(w, name, data) +} + +func (t *Template) ExecuteHTMLString(name string, data interface{}) (string, error) { + var buf bytes.Buffer + err := t.ExecuteHTML(&buf, name, data) + return buf.String(), err +} + +func (t *Template) ExecuteHTML(w io.Writer, name string, data interface{}) error { + return t.html.ExecuteTemplate(w, name, data) +} + +func (t *Template) ApplyConfig(conf *config.Config) { + var ( + tt = text_tmpl.New("") + ht = html_tmpl.New("") + ) + + for _, tf := range conf.Templates { + tt = text_tmpl.Must(tt.ParseGlob(tf)) + ht = html_tmpl.Must(ht.ParseGlob(tf)) + } + + t.text = tt + t.html = ht + + t.funcs(DefaultFuncs) +}