Skip to content

Commit

Permalink
Extract templating so it can be used from multiple notifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
flosell committed Jul 2, 2017
1 parent b2ab0f2 commit 36c9060
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 44 deletions.
54 changes: 10 additions & 44 deletions notifier/email-notifier.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package notifier

import (
"bytes"
"fmt"

"html/template"
"net/smtp"

log "github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/Sirupsen/logrus"
Expand All @@ -28,27 +26,6 @@ type EmailNotifier struct {
OnePerNode bool `json:"one-per-node"`
}

type EmailData struct {
ClusterName string
SystemStatus string
FailCount int
WarnCount int
PassCount int
Nodes map[string]Messages
}

func (e EmailData) IsCritical() bool {
return e.SystemStatus == SYSTEM_CRITICAL
}

func (e EmailData) IsWarning() bool {
return e.SystemStatus == SYSTEM_UNSTABLE
}

func (e EmailData) IsPassing() bool {
return e.SystemStatus == SYSTEM_HEALTHY
}

// NotifierName provides name for notifier selection
func (emailNotifier *EmailNotifier) NotifierName() string {
return "email"
Expand All @@ -65,11 +42,11 @@ func (emailNotifier *EmailNotifier) Notify(alerts Messages) bool {
overAllStatus, pass, warn, fail := alerts.Summary()
nodeMap := mapByNodes(alerts)

var emailDataList []EmailData
var emailDataList []TemplateData

if emailNotifier.OnePerAlert {
log.Println("Going to send one email per alert")
emailDataList = []EmailData{}
emailDataList = []TemplateData{}
for _, check := range alerts {

singleAlertChecks := make(Messages, 0)
Expand All @@ -80,7 +57,7 @@ func (emailNotifier *EmailNotifier) Notify(alerts Messages) bool {

alertClusterName := emailNotifier.ClusterName + " " + check.Node + " - " + check.CheckId

e := EmailData{
e := TemplateData{
ClusterName: alertClusterName,
SystemStatus: alertStatus,
FailCount: alertFailures,
Expand All @@ -92,14 +69,14 @@ func (emailNotifier *EmailNotifier) Notify(alerts Messages) bool {
}
} else if emailNotifier.OnePerNode {
log.Println("Going to send one email per node")
emailDataList = []EmailData{}
emailDataList = []TemplateData{}
for nodeName, checks := range nodeMap {
singleNodeMap := mapByNodes(checks)
nodeStatus, nodePassing, nodeWarnings, nodeFailures := checks.Summary()

nodeClusterName := emailNotifier.ClusterName + " " + nodeName

e := EmailData{
e := TemplateData{
ClusterName: nodeClusterName,
SystemStatus: nodeStatus,
FailCount: nodeFailures,
Expand All @@ -111,7 +88,7 @@ func (emailNotifier *EmailNotifier) Notify(alerts Messages) bool {
}
} else {
log.Println("Going to send one email for many alerts")
e := EmailData{
e := TemplateData{
ClusterName: emailNotifier.ClusterName,
SystemStatus: overAllStatus,
FailCount: fail,
Expand All @@ -120,34 +97,23 @@ func (emailNotifier *EmailNotifier) Notify(alerts Messages) bool {
Nodes: nodeMap,
}

emailDataList = []EmailData{e}
emailDataList = []TemplateData{e}
}

success := true

for _, e := range emailDataList {

var tmpl *template.Template
var renderedTemplate string
var err error
if emailNotifier.Template == "" {
tmpl, err = template.New("base").Parse(defaultTemplate)
} else {
tmpl, err = template.ParseFiles(emailNotifier.Template)
}
renderedTemplate, err = renderTemplate(e, emailNotifier.Template, defaultTemplate)

if err != nil {
log.Println("Template error, unable to send email notification: ", err)
success = false
continue
}

var body bytes.Buffer
if err := tmpl.Execute(&body, e); err != nil {
log.Println("Template error, unable to send email notification: ", err)
success = false
continue
}

msg := fmt.Sprintf(`From: "%s" <%s>
To: %s
Subject: %s is %s
Expand All @@ -161,7 +127,7 @@ Content-Type: text/html; charset="UTF-8";
strings.Join(emailNotifier.Receivers, ", "),
e.ClusterName,
e.SystemStatus,
body.String())
renderedTemplate)

addr := fmt.Sprintf("%s:%d", emailNotifier.Url, emailNotifier.Port)
auth := smtp.PlainAuth("", emailNotifier.Username, emailNotifier.Password, emailNotifier.Url)
Expand Down
48 changes: 48 additions & 0 deletions notifier/templating.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package notifier

import (
"bytes"
"html/template"
)

type TemplateData struct {
ClusterName string
SystemStatus string
FailCount int
WarnCount int
PassCount int
Nodes map[string]Messages
}

func (t TemplateData) IsCritical() bool {
return t.SystemStatus == SYSTEM_CRITICAL
}

func (t TemplateData) IsWarning() bool {
return t.SystemStatus == SYSTEM_UNSTABLE
}

func (t TemplateData) IsPassing() bool {
return t.SystemStatus == SYSTEM_HEALTHY
}

func renderTemplate(t TemplateData, templateFile string, defaultTemplate string) (string, error) {
var tmpl *template.Template
var err error
if templateFile == "" {
tmpl, err = template.New("base").Parse(defaultTemplate)
} else {
tmpl, err = template.ParseFiles(templateFile)
}

if err != nil {
return "", err
}

var body bytes.Buffer
if err := tmpl.Execute(&body, t); err != nil {
return "", err
}

return body.String(), nil
}
85 changes: 85 additions & 0 deletions notifier/templating_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package notifier

import (
"io/ioutil"
"os"
"testing"
)

func TestRenderTemplateWithInvalidFile(t *testing.T) {
templateData := TemplateData{
ClusterName: "some cluster",
SystemStatus: "some status",
FailCount: 1,
WarnCount: 2,
PassCount: 3,
Nodes: make(map[string]Messages),
}

renderedTemplate, err := renderTemplate(templateData, "some-file-that-does-not-exist", "")

if err == nil {
t.Errorf("Expected error but rendered something: %s", renderedTemplate)
} else if err.Error() != "open some-file-that-does-not-exist: no such file or directory" {
t.Errorf("Expected error in opening file but got: %s", err.Error())
}
}

func TestRenderTemplate(t *testing.T) {
tmpfile, err := templateFile("{{ .SystemStatus }} - {{ .FailCount }}")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up

templateData := TemplateData{
ClusterName: "some cluster",
SystemStatus: "some status",
FailCount: 1,
WarnCount: 2,
PassCount: 3,
Nodes: make(map[string]Messages),
}

renderedTemplate, err := renderTemplate(templateData, tmpfile.Name(), "")

if err != nil {
t.Errorf("Rendering failed: %v", err)
} else if renderedTemplate != "some status - 1" {
t.Errorf("Expected 'temporary file' but was '%v'", renderedTemplate)
}
}

func TestRenderDefaultTemplate(t *testing.T) {
templateData := TemplateData{
ClusterName: "some cluster",
SystemStatus: "some status",
FailCount: 1,
WarnCount: 2,
PassCount: 3,
Nodes: make(map[string]Messages),
}

renderedTemplate, err := renderTemplate(templateData, "", "{{ .SystemStatus }} - {{ .FailCount }}")

if err != nil {
t.Errorf("Rendering failed: %v", err)
} else if renderedTemplate != "some status - 1" {
t.Errorf("Expected 'temporary file' but was '%v'", renderedTemplate)
}
}

func templateFile(content string) (*os.File, error) {
tmpfile, err := ioutil.TempFile("", "consulAlertsTest")
if err != nil {
return nil, err
}

if _, err := tmpfile.Write([]byte(content)); err != nil {
return nil, err
}
if err := tmpfile.Close(); err != nil {
return nil, err
}
return tmpfile, nil
}

0 comments on commit 36c9060

Please sign in to comment.