From 2074697efacb335c0e889897574224cb740ae8a4 Mon Sep 17 00:00:00 2001 From: TwinProduction Date: Thu, 29 Jul 2021 19:54:40 -0400 Subject: [PATCH] Improve alerting tests --- README.md | 84 +++++++++---------- alerting/alert/type.go | 6 +- alerting/config.go | 20 ++--- alerting/provider/discord/discord.go | 8 +- alerting/provider/discord/discord_test.go | 6 +- alerting/provider/mattermost/mattermost.go | 8 +- .../provider/mattermost/mattermost_test.go | 6 +- alerting/provider/provider.go | 4 +- alerting/provider/slack/slack.go | 8 +- alerting/provider/slack/slack_test.go | 6 +- alerting/provider/teams/teams.go | 14 ++-- alerting/provider/teams/teams_test.go | 6 +- config/config.go | 2 +- 13 files changed, 104 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index a297ef10..4505c84e 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,10 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: slack enabled: true @@ -342,10 +346,6 @@ services: failure-threshold: 5 description: "healthcheck failed 5 times in a row" send-on-resolved: true - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` Here's an example of what the notifications look like: @@ -363,15 +363,15 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: discord enabled: true description: "healthcheck failed" send-on-resolved: true - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` @@ -390,6 +390,10 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: pagerduty enabled: true @@ -397,10 +401,6 @@ services: success-threshold: 5 send-on-resolved: true description: "healthcheck failed" - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` @@ -417,16 +417,16 @@ services: - name: twinnation interval: 30s url: "https://twinnation.org/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: twilio enabled: true failure-threshold: 5 send-on-resolved: true description: "healthcheck failed" - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` @@ -442,15 +442,15 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: mattermost enabled: true description: "healthcheck failed" send-on-resolved: true - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` Here's an example of what the notifications look like: @@ -459,9 +459,7 @@ Here's an example of what the notifications look like: #### Configuring Messagebird alerts - Example of sending **SMS** text message alert using Messagebird: - ```yaml alerting: messagebird: @@ -472,16 +470,16 @@ services: - name: twinnation interval: 30s url: "https://twinnation.org/health" + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: messagebird enabled: true failure-threshold: 3 send-on-resolved: true description: "healthcheck failed" - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` #### Configuring Teams alerts @@ -494,15 +492,15 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: teams enabled: true description: "healthcheck failed" send-on-resolved: true - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` Here's an example of what the notifications look like: @@ -520,13 +518,13 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" alerts: - type: telegram enabled: true send-on-resolved: true - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" ``` Here's an example of what the notifications look like: @@ -564,6 +562,10 @@ services: - name: twinnation url: "https://twinnation.org/health" interval: 30s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" alerts: - type: custom enabled: true @@ -571,10 +573,6 @@ services: success-threshold: 3 send-on-resolved: true description: "healthcheck failed" - conditions: - - "[STATUS] == 200" - - "[BODY].status == UP" - - "[RESPONSE_TIME] < 300" ``` Note that you can customize the resolved values for the `[ALERT_TRIGGERED_OR_RESOLVED]` placeholder like so: @@ -612,17 +610,17 @@ As a result, your service configuration looks a lot tidier: services: - name: example url: "https://example.org" - alerts: - - type: slack conditions: - "[STATUS] == 200" + alerts: + - type: slack - name: other-example url: "https://example.com" - alerts: - - type: slack conditions: - "[STATUS] == 200" + alerts: + - type: slack ``` It also allows you to do things like this: @@ -630,6 +628,8 @@ It also allows you to do things like this: services: - name: twinnation url: "https://twinnation.org/health" + conditions: + - "[STATUS] == 200" alerts: - type: slack failure-threshold: 5 @@ -637,8 +637,6 @@ services: failure-threshold: 10 - type: slack failure-threshold: 15 - conditions: - - "[STATUS] == 200" ``` diff --git a/alerting/alert/type.go b/alerting/alert/type.go index b62ef423..434accbb 100644 --- a/alerting/alert/type.go +++ b/alerting/alert/type.go @@ -23,12 +23,12 @@ const ( // TypeSlack is the Type for the slack alerting provider TypeSlack Type = "slack" + // TypeTeams is the Type for the teams alerting provider + TypeTeams Type = "teams" + // TypeTelegram is the Type for the telegram alerting provider TypeTelegram Type = "telegram" // TypeTwilio is the Type for the twilio alerting provider TypeTwilio Type = "twilio" - - // Teams is the Type for the teams alerting provider - TypeTeams Type = "teams" ) diff --git a/alerting/config.go b/alerting/config.go index 38390e44..19651f62 100644 --- a/alerting/config.go +++ b/alerting/config.go @@ -9,9 +9,9 @@ import ( "github.com/TwinProduction/gatus/alerting/provider/messagebird" "github.com/TwinProduction/gatus/alerting/provider/pagerduty" "github.com/TwinProduction/gatus/alerting/provider/slack" + "github.com/TwinProduction/gatus/alerting/provider/teams" "github.com/TwinProduction/gatus/alerting/provider/telegram" "github.com/TwinProduction/gatus/alerting/provider/twilio" - "github.com/TwinProduction/gatus/alerting/provider/teams" ) // Config is the configuration for alerting providers @@ -34,14 +34,14 @@ type Config struct { // Slack is the configuration for the slack alerting provider Slack *slack.AlertProvider `yaml:"slack"` + // Teams is the configuration for the teams alerting provider + Teams *teams.AlertProvider `yaml:"teams"` + // Telegram is the configuration for the telegram alerting provider Telegram *telegram.AlertProvider `yaml:"telegram"` // Twilio is the configuration for the twilio alerting provider Twilio *twilio.AlertProvider `yaml:"twilio"` - - // Teams is the configuration for the teams alerting provider - Teams *teams.AlertProvider `yaml:"teams"` } // GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type @@ -83,6 +83,12 @@ func (config Config) GetAlertingProviderByAlertType(alertType alert.Type) provid return nil } return config.Slack + case alert.TypeTeams: + if config.Teams == nil { + // Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil + return nil + } + return config.Teams case alert.TypeTelegram: if config.Telegram == nil { // Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil @@ -95,12 +101,6 @@ func (config Config) GetAlertingProviderByAlertType(alertType alert.Type) provid return nil } return config.Twilio - case alert.TypeTeams: - if config.Teams == nil { - // Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil - return nil - } - return config.Teams } return nil } diff --git a/alerting/provider/discord/discord.go b/alerting/provider/discord/discord.go index ec93cab1..003654ac 100644 --- a/alerting/provider/discord/discord.go +++ b/alerting/provider/discord/discord.go @@ -42,6 +42,10 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler } results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition) } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\\n> " + alertDescription + } return &custom.AlertProvider{ URL: provider.WebhookURL, Method: http.MethodPost, @@ -50,7 +54,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler "embeds": [ { "title": ":helmet_with_white_cross: Gatus", - "description": "%s:\n> %s", + "description": "%s%s", "color": %d, "fields": [ { @@ -61,7 +65,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler ] } ] -}`, message, alert.GetDescription(), colorCode, results), +}`, message, description, colorCode, results), Headers: map[string]string{"Content-Type": "application/json"}, } } diff --git a/alerting/provider/discord/discord_test.go b/alerting/provider/discord/discord_test.go index 9243e890..dc410288 100644 --- a/alerting/provider/discord/discord_test.go +++ b/alerting/provider/discord/discord_test.go @@ -23,7 +23,8 @@ func TestAlertProvider_IsValid(t *testing.T) { func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { provider := AlertProvider{WebhookURL: "http://example.com"} - customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &alert.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) + alertDescription := "test" + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{Name: "svc"}, &alert.Alert{Description: &alertDescription}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) if customAlertProvider == nil { t.Fatal("customAlertProvider shouldn't have been nil") } @@ -41,6 +42,9 @@ func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { if err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } + if expected := "An alert for **svc** has been resolved after passing successfully 0 time(s) in a row:\n> test"; expected != body["embeds"].([]interface{})[0].(map[string]interface{})["description"] { + t.Errorf("expected $.embeds[0].description to be %s, got %s", expected, body["embeds"].([]interface{})[0].(map[string]interface{})["description"]) + } } func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) { diff --git a/alerting/provider/mattermost/mattermost.go b/alerting/provider/mattermost/mattermost.go index 5538ba3f..c61c2272 100644 --- a/alerting/provider/mattermost/mattermost.go +++ b/alerting/provider/mattermost/mattermost.go @@ -57,6 +57,10 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler } results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition) } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\\n> " + alertDescription + } return &custom.AlertProvider{ URL: provider.WebhookURL, Method: http.MethodPost, @@ -69,7 +73,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler { "title": ":rescue_worker_helmet: Gatus", "fallback": "Gatus - %s", - "text": "%s:\n> %s", + "text": "%s%s", "short": false, "color": "%s", "fields": [ @@ -86,7 +90,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler ] } ] -}`, message, message, alert.GetDescription(), color, service.URL, results), +}`, message, message, description, color, service.URL, results), Headers: map[string]string{"Content-Type": "application/json"}, } } diff --git a/alerting/provider/mattermost/mattermost_test.go b/alerting/provider/mattermost/mattermost_test.go index b4df04c5..772b6c0f 100644 --- a/alerting/provider/mattermost/mattermost_test.go +++ b/alerting/provider/mattermost/mattermost_test.go @@ -23,7 +23,8 @@ func TestAlertProvider_IsValid(t *testing.T) { func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { provider := AlertProvider{WebhookURL: "http://example.org"} - customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &alert.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) + alertDescription := "test" + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{Name: "svc"}, &alert.Alert{Description: &alertDescription}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) if customAlertProvider == nil { t.Fatal("customAlertProvider shouldn't have been nil") } @@ -41,6 +42,9 @@ func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { if err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } + if expected := "An alert for *svc* has been resolved after passing successfully 0 time(s) in a row:\n> test"; expected != body["attachments"].([]interface{})[0].(map[string]interface{})["text"] { + t.Errorf("expected $.attachments[0].description to be %s, got %s", expected, body["attachments"].([]interface{})[0].(map[string]interface{})["text"]) + } } func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) { diff --git a/alerting/provider/provider.go b/alerting/provider/provider.go index 5b26cba6..d276ce61 100644 --- a/alerting/provider/provider.go +++ b/alerting/provider/provider.go @@ -8,9 +8,9 @@ import ( "github.com/TwinProduction/gatus/alerting/provider/messagebird" "github.com/TwinProduction/gatus/alerting/provider/pagerduty" "github.com/TwinProduction/gatus/alerting/provider/slack" + "github.com/TwinProduction/gatus/alerting/provider/teams" "github.com/TwinProduction/gatus/alerting/provider/telegram" "github.com/TwinProduction/gatus/alerting/provider/twilio" - "github.com/TwinProduction/gatus/alerting/provider/teams" "github.com/TwinProduction/gatus/core" ) @@ -56,7 +56,7 @@ var ( _ AlertProvider = (*messagebird.AlertProvider)(nil) _ AlertProvider = (*pagerduty.AlertProvider)(nil) _ AlertProvider = (*slack.AlertProvider)(nil) + _ AlertProvider = (*teams.AlertProvider)(nil) _ AlertProvider = (*telegram.AlertProvider)(nil) _ AlertProvider = (*twilio.AlertProvider)(nil) - _ AlertProvider = (*teams.AlertProvider)(nil) ) diff --git a/alerting/provider/slack/slack.go b/alerting/provider/slack/slack.go index 4b3bcf99..1ed7fb62 100644 --- a/alerting/provider/slack/slack.go +++ b/alerting/provider/slack/slack.go @@ -41,6 +41,10 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler } results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition) } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\\n> " + alertDescription + } return &custom.AlertProvider{ URL: provider.WebhookURL, Method: http.MethodPost, @@ -49,7 +53,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler "attachments": [ { "title": ":helmet_with_white_cross: Gatus", - "text": "%s:\n> %s", + "text": "%s%s", "short": false, "color": "%s", "fields": [ @@ -61,7 +65,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler ] } ] -}`, message, alert.GetDescription(), color, results), +}`, message, description, color, results), Headers: map[string]string{"Content-Type": "application/json"}, } } diff --git a/alerting/provider/slack/slack_test.go b/alerting/provider/slack/slack_test.go index 798cf8b4..2efa78d2 100644 --- a/alerting/provider/slack/slack_test.go +++ b/alerting/provider/slack/slack_test.go @@ -23,7 +23,8 @@ func TestAlertProvider_IsValid(t *testing.T) { func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { provider := AlertProvider{WebhookURL: "http://example.com"} - customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &alert.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) + alertDescription := "test" + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{Name: "svc"}, &alert.Alert{Description: &alertDescription}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) if customAlertProvider == nil { t.Fatal("customAlertProvider shouldn't have been nil") } @@ -41,6 +42,9 @@ func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { if err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } + if expected := "An alert for *svc* has been resolved after passing successfully 0 time(s) in a row:\n> test"; expected != body["attachments"].([]interface{})[0].(map[string]interface{})["text"] { + t.Errorf("expected $.attachments[0].description to be %s, got %s", expected, body["attachments"].([]interface{})[0].(map[string]interface{})["text"]) + } } func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) { diff --git a/alerting/provider/teams/teams.go b/alerting/provider/teams/teams.go index 7774c90c..61ee3feb 100644 --- a/alerting/provider/teams/teams.go +++ b/alerting/provider/teams/teams.go @@ -37,21 +37,25 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler for _, conditionResult := range result.ConditionResults { var prefix string if conditionResult.Success { - prefix = "✅" + prefix = "✅" } else { prefix = "❌" } results += fmt.Sprintf("%s - `%s`
", prefix, conditionResult.Condition) } + var description string + if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { + description = ":\\n> " + alertDescription + } return &custom.AlertProvider{ - URL: provider.WebhookURL, - Method: http.MethodPost, + URL: provider.WebhookURL, + Method: http.MethodPost, Body: fmt.Sprintf(`{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": "%s", "title": "🚨 Gatus", - "text": "%s:\n> %s", + "text": "%s%s", "sections": [ { "activityTitle": "URL", @@ -62,7 +66,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler "text": "%s" } ] -}`, color, message, alert.GetDescription(), service.URL, results), +}`, color, message, description, service.URL, results), Headers: map[string]string{"Content-Type": "application/json"}, } } diff --git a/alerting/provider/teams/teams_test.go b/alerting/provider/teams/teams_test.go index 1bf8b4d3..b7400d3d 100644 --- a/alerting/provider/teams/teams_test.go +++ b/alerting/provider/teams/teams_test.go @@ -23,7 +23,8 @@ func TestAlertProvider_IsValid(t *testing.T) { func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { provider := AlertProvider{WebhookURL: "http://example.org"} - customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &alert.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) + alertDescription := "test" + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{Name: "svc"}, &alert.Alert{Description: &alertDescription}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) if customAlertProvider == nil { t.Fatal("customAlertProvider shouldn't have been nil") } @@ -41,6 +42,9 @@ func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { if err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } + if expected := "An alert for *svc* has been resolved after passing successfully 0 time(s) in a row:\n> test"; expected != body["text"] { + t.Errorf("expected $.text to be %s, got %s", expected, body["text"]) + } } func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) { diff --git a/config/config.go b/config/config.go index 620fb91b..f8370675 100644 --- a/config/config.go +++ b/config/config.go @@ -270,9 +270,9 @@ func validateAlertingConfig(alertingConfig *alerting.Config, services []*core.Se alert.TypeMessagebird, alert.TypePagerDuty, alert.TypeSlack, + alert.TypeTeams, alert.TypeTelegram, alert.TypeTwilio, - alert.TypeTeams, } var validProviders, invalidProviders []alert.Type for _, alertType := range alertTypes {