-
Notifications
You must be signed in to change notification settings - Fork 488
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(alerts): templating for address in email alert (#2650)
* feat(alerts): templating for address in email alert * chore: go fmt * chore: go mod tidy
- Loading branch information
Showing
9 changed files
with
288 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10775,108 +10775,231 @@ stream | |
} | ||
|
||
func TestStream_AlertEmail(t *testing.T) { | ||
var script = ` | ||
stream | ||
|from() | ||
.measurement('cpu') | ||
.where(lambda: "host" == 'serverA') | ||
.groupBy('host') | ||
|window() | ||
.period(10s) | ||
.every(10s) | ||
|count('value') | ||
|alert() | ||
.id('kapacitor.{{ .Name }}.{{ index .Tags "host" }}') | ||
.details(''' | ||
var cases = []struct { | ||
name string | ||
script string | ||
expected []*smtptest.Message | ||
}{ | ||
{ | ||
name: "emails directly", | ||
script: `stream | ||
|from() | ||
.measurement('cpu') | ||
.where(lambda: "host" == 'serverA') | ||
.groupBy('host') | ||
|window() | ||
.period(10s) | ||
.every(10s) | ||
|count('value') | ||
|alert() | ||
.id('kapacitor.{{ .Name }}.{{ index .Tags "host" }}') | ||
.details(''' | ||
<b>{{.Message}}</b> | ||
Value: {{ index .Fields "count" }} | ||
<a href="http://graphs.example.com/host/{{index .Tags "host"}}">Details</a> | ||
''') | ||
.info(lambda: "count" > 6.0) | ||
.warn(lambda: "count" > 7.0) | ||
.crit(lambda: "count" > 8.0) | ||
.email('[email protected]', '[email protected]') | ||
.email() | ||
.to('[email protected]', '[email protected]') | ||
` | ||
.info(lambda: "count" > 6.0) | ||
.warn(lambda: "count" > 7.0) | ||
.crit(lambda: "count" > 8.0) | ||
.email('[email protected]', '[email protected]') | ||
.email() | ||
.to('[email protected]', '[email protected]') | ||
`, | ||
expected: []*smtptest.Message{ | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
<b>kapacitor.cpu.serverA is CRITICAL</b> | ||
Value: 10 | ||
<a href=3D"http://graphs.example.com/host/serverA">Details</a> | ||
`, | ||
}, | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
<b>kapacitor.cpu.serverA is CRITICAL</b> | ||
expMail := []*smtptest.Message{ | ||
Value: 10 | ||
<a href=3D"http://graphs.example.com/host/serverA">Details</a> | ||
`, | ||
}}, | ||
}, | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
name: "emails directly and in fields", | ||
script: `stream | ||
|from() | ||
.measurement('cpu') | ||
.where(lambda: "host" == 'serverA') | ||
.groupBy('host') | ||
|window() | ||
.period(10s) | ||
.every(10s) | ||
|count('value') | ||
|default() | ||
.field('extraemail','[email protected]') | ||
.tag('tagemail','[email protected]') | ||
|alert() | ||
.id('kapacitor.{{ .Name }}.{{ index .Tags "host" }}') | ||
.details(''' | ||
<b>{{.Message}}</b> | ||
Value: {{ index .Fields "count" }} | ||
<a href="http://graphs.example.com/host/{{index .Tags "host"}}">Details</a> | ||
''') | ||
.info(lambda: "count" > 6.0) | ||
.warn(lambda: "count" > 7.0) | ||
.crit(lambda: "count" > 8.0) | ||
.email() | ||
.to('[email protected]', '[email protected]') | ||
.toTemplates('{{ index .Fields "extraemail" }}') | ||
`, | ||
expected: []*smtptest.Message{ | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
<b>kapacitor.cpu.serverA is CRITICAL</b> | ||
Value: 10 | ||
<a href=3D"http://graphs.example.com/host/serverA">Details</a> | ||
`, | ||
}}, | ||
}, | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
name: "emails directly and in tag", | ||
script: `stream | ||
|from() | ||
.measurement('cpu') | ||
.where(lambda: "host" == 'serverA') | ||
.groupBy('host') | ||
|window() | ||
.period(10s) | ||
.every(10s) | ||
|count('value') | ||
|default() | ||
.field('extraemail','[email protected]') | ||
.tag('tagemail','[email protected]') | ||
|alert() | ||
.id('kapacitor.{{ .Name }}.{{ index .Tags "host" }}') | ||
.details(''' | ||
<b>{{.Message}}</b> | ||
Value: {{ index .Fields "count" }} | ||
<a href="http://graphs.example.com/host/{{index .Tags "host"}}">Details</a> | ||
''') | ||
.info(lambda: "count" > 6.0) | ||
.warn(lambda: "count" > 7.0) | ||
.crit(lambda: "count" > 8.0) | ||
.email() | ||
.to('[email protected]', '[email protected]') | ||
.toTemplates('{{ index .Tags "tagemail" }}') | ||
`, | ||
expected: []*smtptest.Message{ | ||
{ | ||
Header: mail.Header{ | ||
"Mime-Version": []string{"1.0"}, | ||
"Content-Type": []string{"text/html; charset=UTF-8"}, | ||
"Content-Transfer-Encoding": []string{"quoted-printable"}, | ||
"To": []string{"[email protected], [email protected], [email protected]"}, | ||
"From": []string{"[email protected]"}, | ||
"Subject": []string{"kapacitor.cpu.serverA is CRITICAL"}, | ||
}, | ||
Body: ` | ||
<b>kapacitor.cpu.serverA is CRITICAL</b> | ||
Value: 10 | ||
<a href=3D"http://graphs.example.com/host/serverA">Details</a> | ||
`, | ||
}, | ||
}, | ||
}, | ||
} | ||
for i := range cases { | ||
t.Run(cases[i].name, func(t *testing.T) { | ||
script := cases[i].script | ||
expMail := cases[i].expected | ||
smtpServer, err := smtptest.NewServer() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer func() { | ||
err := smtpServer.Close() | ||
if err != nil { | ||
t.Fatalf("error in closing smtpService %v", err) | ||
} | ||
}() | ||
|
||
smtpServer, err := smtptest.NewServer() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer smtpServer.Close() | ||
sc := smtp.Config{ | ||
Enabled: true, | ||
Host: smtpServer.Host, | ||
Port: smtpServer.Port, | ||
From: "[email protected]", | ||
} | ||
smtpService := smtp.NewService(sc, diagService.NewSMTPHandler()) | ||
if err := smtpService.Open(); err != nil { | ||
t.Fatal(err) | ||
} | ||
defer smtpService.Close() | ||
sc := smtp.Config{ | ||
Enabled: true, | ||
Host: smtpServer.Host, | ||
Port: smtpServer.Port, | ||
From: "[email protected]", | ||
} | ||
smtpService := smtp.NewService(sc, diagService.NewSMTPHandler()) | ||
if err := smtpService.Open(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
tmInit := func(tm *kapacitor.TaskMaster) { | ||
tm.SMTPService = smtpService | ||
} | ||
// make sure its closed | ||
defer func() { | ||
err := smtpService.Close() | ||
if err != nil { | ||
t.Fatalf("error in closing smtpService %v", err) | ||
} | ||
}() | ||
|
||
testStreamerNoOutput(t, "TestStream_Alert", script, 13*time.Second, tmInit) | ||
tmInit := func(tm *kapacitor.TaskMaster) { | ||
tm.SMTPService = smtpService | ||
} | ||
|
||
// Close both client and server to ensure all message are processed | ||
smtpService.Close() | ||
smtpServer.Close() | ||
testStreamerNoOutput(t, "TestStream_Alert", script, 13*time.Second, tmInit) | ||
|
||
errors := smtpServer.Errors() | ||
if got, exp := len(errors), 0; got != exp { | ||
t.Errorf("unexpected smtp server errors: %v", errors) | ||
} | ||
// Close both client and server to ensure all message are processed | ||
if err := smtpService.Close(); err != nil { | ||
t.Fatalf("error in closing smtpService %v", err) | ||
} | ||
if err := smtpServer.Close(); err != nil { | ||
t.Fatalf("error in closing smtpServer %v", err) | ||
} | ||
|
||
msgs := smtpServer.SentMessages() | ||
if got, exp := len(msgs), len(expMail); got != exp { | ||
t.Errorf("unexpected number of messages sent: got %d exp %d", got, exp) | ||
} | ||
for i, exp := range expMail { | ||
got := msgs[i] | ||
if err := exp.Compare(got); err != nil { | ||
t.Errorf("%d %s", i, err) | ||
} | ||
errors := smtpServer.Errors() | ||
if got, exp := len(errors), 0; got != exp { | ||
t.Errorf("unexpected smtp server errors: %v", errors) | ||
} | ||
|
||
msgs := smtpServer.SentMessages() | ||
if got, exp := len(msgs), len(expMail); got != exp { | ||
t.Errorf("unexpected number of messages sent: got %d exp %d", got, exp) | ||
} | ||
for i, exp := range expMail { | ||
got := msgs[i] | ||
if err := exp.Compare(got); err != nil { | ||
t.Errorf("%d %s", i, err) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
|
@@ -13672,6 +13795,7 @@ func testStreamerNoOutput( | |
duration time.Duration, | ||
tmInit func(tm *kapacitor.TaskMaster), | ||
) { | ||
t.Helper() | ||
clock, et, replayErr, tm := testStreamer(t, name, script, tmInit) | ||
defer tm.Close() | ||
err := fastForwardTask(clock, et, replayErr, tm, duration) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -796,6 +796,10 @@ type EmailHandler struct { | |
// List of email recipients. | ||
// tick:ignore | ||
ToList []string `tick:"To" json:"to"` | ||
|
||
// ToTemplatesList is the Field or Value from which to grab email addresses | ||
// tick:ignore | ||
ToTemplatesList []string `tick:"ToTemplates" json:"to-templates"` | ||
} | ||
|
||
// Define the To addresses for the email alert. | ||
|
@@ -826,6 +830,33 @@ func (h *EmailHandler) To(to ...string) *EmailHandler { | |
return h | ||
} | ||
|
||
// Define the To addresses for the email alert. | ||
// Multiple calls append to the existing list of addresses. | ||
// If empty uses the addresses from the configuration. | ||
// | ||
// Example: | ||
// |alert() | ||
// .id('{{ .Name }}') | ||
// // Email subject | ||
// .message('{{ .ID }}:{{ .Level }}') | ||
// //Email body as HTML | ||
// .details(''' | ||
//<h1>{{ .ID }}</h1> | ||
//<b>{{ .Message }}</b> | ||
//Value: {{ index .Fields "value" }} | ||
//''') | ||
// .email('[email protected]') | ||
// .toTemplates('[email protected]') | ||
// | ||
// All three email addresses will receive the alert message. | ||
// | ||
// Passing addresses to the `email` property directly or using the `email.to` property is the same. | ||
// tick:property | ||
func (h *EmailHandler) ToTemplates(to ...string) *EmailHandler { | ||
h.ToTemplatesList = append(h.ToTemplatesList, to...) | ||
return h | ||
} | ||
|
||
// Execute a command whenever an alert is triggered and pass the alert data over STDIN in JSON format. | ||
// tick:property | ||
func (n *AlertNodeData) Exec(executable string, args ...string) *ExecHandler { | ||
|
Oops, something went wrong.