feat(alert): Add group-specific to email list (#264)

* feat(alert): Add group-specific to email list

Add group-specific to list for email alert

https://github.com/TwiN/gatus/issues/96

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* docs: update

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* Update README.md

* Update README.md

* Update README.md

* chore: update

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* Update README.md
This commit is contained in:
Bo-Yi Wu 2022-03-21 09:54:20 +08:00 committed by GitHub
parent bec2820969
commit a81c81e42c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 14 deletions

View File

@ -376,16 +376,19 @@ endpoints:
#### Configuring Email alerts #### Configuring Email alerts
| Parameter | Description | Default | | Parameter | Description | Default |
|:-------------------------------|:-------------------------------------------------------------------------------------------|:----------------------| |:---------------------------------- |:------------------------------------------------------------------------------------------ |:------------- |
| `alerting.email` | Configuration for alerts of type `email` | `{}` | | `alerting.email` | Configuration for alerts of type `email` | `{}` |
| `alerting.email.from` | Email used to send the alert | Required `""` | | `alerting.email.from` | Email used to send the alert | Required `""` |
| `alerting.email.username` | Username of the SMTP server used to send the alert. If empty, uses `alerting.email.from`. | `""` | | `alerting.email.username` | Username of the SMTP server used to send the alert. If empty, uses `alerting.email.from`. | `""` |
| `alerting.email.password` | Password of the SMTP server used to send the alert | Required `""` | | `alerting.email.password` | Password of the SMTP server used to send the alert | Required `""` |
| `alerting.email.host` | Host of the mail server (e.g. `smtp.gmail.com`) | Required `""` | | `alerting.email.host` | Host of the mail server (e.g. `smtp.gmail.com`) | Required `""` |
| `alerting.email.port` | Port the mail server is listening to (e.g. `587`) | Required `0` | | `alerting.email.port` | Port the mail server is listening to (e.g. `587`) | Required `0` |
| `alerting.email.to` | Email(s) to send the alerts to | Required `""` | | `alerting.email.to` | Email(s) to send the alerts to | Required `""` |
| `alerting.email.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert) | N/A | | `alerting.email.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert) | N/A |
| `alerting.email.overrides` | List of overrides that may be prioritized over the default configuration | `[]` |
| `alerting.email.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` |
| `alerting.email.overrides[].to` | Email(s) to send the alerts to | `""` |
```yaml ```yaml
alerting: alerting:
@ -396,6 +399,11 @@ alerting:
host: "mail.example.com" host: "mail.example.com"
port: 587 port: 587
to: "recipient1@example.com,recipient2@example.com" to: "recipient1@example.com,recipient2@example.com"
# You can also add group-specific to keys, which will
# override the to key above for the specified groups
overrides:
- group: "core"
to: "recipient3@example.com,recipient4@example.com"
endpoints: endpoints:
- name: website - name: website
@ -410,6 +418,19 @@ endpoints:
enabled: true enabled: true
description: "healthcheck failed" description: "healthcheck failed"
send-on-resolved: true send-on-resolved: true
- name: back-end
group: core
url: "https://example.org/"
interval: 5m
conditions:
- "[STATUS] == 200"
- "[CERTIFICATE_EXPIRATION] > 48h"
alerts:
- type: email
enabled: true
description: "healthcheck failed"
send-on-resolved: true
``` ```
**NOTE:** Some mail servers are painfully slow. **NOTE:** Some mail servers are painfully slow.
@ -557,8 +578,8 @@ alerting:
# You can also add group-specific integration keys, which will # You can also add group-specific integration keys, which will
# override the integration key above for the specified groups # override the integration key above for the specified groups
overrides: overrides:
- group: "core" - group: "core"
integration-key: "********************************" integration-key: "********************************"
endpoints: endpoints:
- name: website - name: website

View File

@ -21,10 +21,29 @@ type AlertProvider struct {
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
// Overrides is a list of Override that may be prioritized over the default configuration
Overrides []Override `yaml:"overrides,omitempty"`
}
// Override is a case under which the default integration is overridden
type Override struct {
Group string `yaml:"group"`
To string `yaml:"to"`
} }
// IsValid returns whether the provider's configuration is valid // IsValid returns whether the provider's configuration is valid
func (provider *AlertProvider) IsValid() bool { func (provider *AlertProvider) IsValid() bool {
registeredGroups := make(map[string]bool)
if provider.Overrides != nil {
for _, override := range provider.Overrides {
if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.To) == 0 {
return false
}
registeredGroups[override.Group] = true
}
}
return len(provider.From) > 0 && len(provider.Password) > 0 && len(provider.Host) > 0 && len(provider.To) > 0 && provider.Port > 0 && provider.Port < math.MaxUint16 return len(provider.From) > 0 && len(provider.Password) > 0 && len(provider.Host) > 0 && len(provider.To) > 0 && provider.Port > 0 && provider.Port < math.MaxUint16
} }
@ -39,7 +58,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
subject, body := provider.buildMessageSubjectAndBody(endpoint, alert, result, resolved) subject, body := provider.buildMessageSubjectAndBody(endpoint, alert, result, resolved)
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", provider.From) m.SetHeader("From", provider.From)
m.SetHeader("To", strings.Split(provider.To, ",")...) m.SetHeader("To", strings.Split(provider.getToForGroup(endpoint.Group), ",")...)
m.SetHeader("Subject", subject) m.SetHeader("Subject", subject)
m.SetBody("text/plain", body) m.SetBody("text/plain", body)
d := gomail.NewDialer(provider.Host, provider.Port, username, provider.Password) d := gomail.NewDialer(provider.Host, provider.Port, username, provider.Password)
@ -72,6 +91,18 @@ func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoin
return subject, message + description + "\n\nCondition results:\n" + results return subject, message + description + "\n\nCondition results:\n" + results
} }
// getToForGroup returns the appropriate email integration to for a given group
func (provider *AlertProvider) getToForGroup(group string) string {
if provider.Overrides != nil {
for _, override := range provider.Overrides {
if group == override.Group {
return override.To
}
}
}
return provider.To
}
// GetDefaultAlert returns the provider's default alert configuration // GetDefaultAlert returns the provider's default alert configuration
func (provider AlertProvider) GetDefaultAlert() *alert.Alert { func (provider AlertProvider) GetDefaultAlert() *alert.Alert {
return provider.DefaultAlert return provider.DefaultAlert

View File

@ -7,7 +7,7 @@ import (
"github.com/TwiN/gatus/v3/core" "github.com/TwiN/gatus/v3/core"
) )
func TestAlertProvider_IsValid(t *testing.T) { func TestAlertDefaultProvider_IsValid(t *testing.T) {
invalidProvider := AlertProvider{} invalidProvider := AlertProvider{}
if invalidProvider.IsValid() { if invalidProvider.IsValid() {
t.Error("provider shouldn't have been valid") t.Error("provider shouldn't have been valid")
@ -18,6 +18,47 @@ func TestAlertProvider_IsValid(t *testing.T) {
} }
} }
func TestAlertProvider_IsValidWithOverride(t *testing.T) {
providerWithInvalidOverrideGroup := AlertProvider{
Overrides: []Override{
{
To: "to@example.com",
Group: "",
},
},
}
if providerWithInvalidOverrideGroup.IsValid() {
t.Error("provider Group shouldn't have been valid")
}
providerWithInvalidOverrideTo := AlertProvider{
Overrides: []Override{
{
To: "",
Group: "group",
},
},
}
if providerWithInvalidOverrideTo.IsValid() {
t.Error("provider integration key shouldn't have been valid")
}
providerWithValidOverride := AlertProvider{
From: "from@example.com",
Password: "password",
Host: "smtp.gmail.com",
Port: 587,
To: "to@example.com",
Overrides: []Override{
{
To: "to@example.com",
Group: "group",
},
},
}
if !providerWithValidOverride.IsValid() {
t.Error("provider should've been valid")
}
}
func TestAlertProvider_buildRequestBody(t *testing.T) { func TestAlertProvider_buildRequestBody(t *testing.T) {
firstDescription := "description-1" firstDescription := "description-1"
secondDescription := "description-2" secondDescription := "description-2"
@ -77,3 +118,66 @@ func TestAlertProvider_GetDefaultAlert(t *testing.T) {
t.Error("expected default alert to be nil") t.Error("expected default alert to be nil")
} }
} }
func TestAlertProvider_getToForGroup(t *testing.T) {
tests := []struct {
Name string
Provider AlertProvider
InputGroup string
ExpectedOutput string
}{
{
Name: "provider-no-override-specify-no-group-should-default",
Provider: AlertProvider{
To: "to@example.com",
Overrides: nil,
},
InputGroup: "",
ExpectedOutput: "to@example.com",
},
{
Name: "provider-no-override-specify-group-should-default",
Provider: AlertProvider{
To: "to@example.com",
Overrides: nil,
},
InputGroup: "group",
ExpectedOutput: "to@example.com",
},
{
Name: "provider-with-override-specify-no-group-should-default",
Provider: AlertProvider{
To: "to@example.com",
Overrides: []Override{
{
Group: "group",
To: "to01@example.com",
},
},
},
InputGroup: "",
ExpectedOutput: "to@example.com",
},
{
Name: "provider-with-override-specify-group-should-override",
Provider: AlertProvider{
To: "to@example.com",
Overrides: []Override{
{
Group: "group",
To: "to01@example.com",
},
},
},
InputGroup: "group",
ExpectedOutput: "to01@example.com",
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if got := tt.Provider.getToForGroup(tt.InputGroup); got != tt.ExpectedOutput {
t.Errorf("AlertProvider.getToForGroup() = %v, want %v", got, tt.ExpectedOutput)
}
})
}
}