From a81c81e42c80aae76c1aad4df5a3f4e1185c0b92 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 21 Mar 2022 09:54:20 +0800 Subject: [PATCH] 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 * docs: update Signed-off-by: Bo-Yi Wu * Update README.md * Update README.md * Update README.md * chore: update Signed-off-by: Bo-Yi Wu * Update README.md --- README.md | 45 ++++++++--- alerting/provider/email/email.go | 33 +++++++- alerting/provider/email/email_test.go | 106 +++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 18de1ff4..873007f1 100644 --- a/README.md +++ b/README.md @@ -376,16 +376,19 @@ endpoints: #### Configuring Email alerts -| Parameter | Description | Default | -|:-------------------------------|:-------------------------------------------------------------------------------------------|:----------------------| -| `alerting.email` | Configuration for alerts of type `email` | `{}` | -| `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.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.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.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert) | N/A | +| Parameter | Description | Default | +|:---------------------------------- |:------------------------------------------------------------------------------------------ |:------------- | +| `alerting.email` | Configuration for alerts of type `email` | `{}` | +| `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.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.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.default-alert` | Default alert configuration.
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 alerting: @@ -396,6 +399,11 @@ alerting: host: "mail.example.com" port: 587 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: - name: website @@ -410,6 +418,19 @@ endpoints: enabled: true description: "healthcheck failed" 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. @@ -557,8 +578,8 @@ alerting: # You can also add group-specific integration keys, which will # override the integration key above for the specified groups overrides: - - group: "core" - integration-key: "********************************" + - group: "core" + integration-key: "********************************" endpoints: - name: website diff --git a/alerting/provider/email/email.go b/alerting/provider/email/email.go index b95f37e3..dd1ec3ae 100644 --- a/alerting/provider/email/email.go +++ b/alerting/provider/email/email.go @@ -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 *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 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 } @@ -39,7 +58,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, subject, body := provider.buildMessageSubjectAndBody(endpoint, alert, result, resolved) m := gomail.NewMessage() 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.SetBody("text/plain", body) 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 } +// 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 func (provider AlertProvider) GetDefaultAlert() *alert.Alert { return provider.DefaultAlert diff --git a/alerting/provider/email/email_test.go b/alerting/provider/email/email_test.go index bfdf8f52..42751b26 100644 --- a/alerting/provider/email/email_test.go +++ b/alerting/provider/email/email_test.go @@ -7,7 +7,7 @@ import ( "github.com/TwiN/gatus/v3/core" ) -func TestAlertProvider_IsValid(t *testing.T) { +func TestAlertDefaultProvider_IsValid(t *testing.T) { invalidProvider := AlertProvider{} if invalidProvider.IsValid() { 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) { firstDescription := "description-1" secondDescription := "description-2" @@ -77,3 +118,66 @@ func TestAlertProvider_GetDefaultAlert(t *testing.T) { 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) + } + }) + } +}