From 24da853820ebc95156d863ffab894c06909db7a0 Mon Sep 17 00:00:00 2001 From: Jonah Date: Wed, 31 Mar 2021 01:38:34 +0200 Subject: [PATCH] Add Telegram Alerting (#102) --- .github/assets/telegram-alerts.png | Bin 0 -> 37376 bytes .gitignore | 3 +- README.md | 29 +++++++ alerting/config.go | 4 + alerting/provider/provider.go | 2 + alerting/provider/telegram/telegram.go | 52 ++++++++++++ alerting/provider/telegram/telegram_test.go | 89 ++++++++++++++++++++ config/config.go | 7 ++ config/config_test.go | 32 +++++-- core/alert.go | 3 + 10 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 .github/assets/telegram-alerts.png create mode 100644 alerting/provider/telegram/telegram.go create mode 100644 alerting/provider/telegram/telegram_test.go diff --git a/.github/assets/telegram-alerts.png b/.github/assets/telegram-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..0981c64bf5a6461e47a3cc36d9230813d9e34a81 GIT binary patch literal 37376 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 0 && len(provider.ID) > 0 +} + +// ToCustomAlertProvider converts the provider into a custom.AlertProvider +func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider { + var message, results string + if resolved { + message = fmt.Sprintf("An alert for *%s* has been resolved:\\n—\\n _healthcheck passing successfully %d time(s) in a row_\\n— ", service.Name, alert.FailureThreshold) + } else { + message = fmt.Sprintf("An alert for *%s* has been triggered:\\n—\\n _healthcheck failed %d time(s) in a row_\\n— ", service.Name, alert.FailureThreshold) + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✅" + } else { + prefix = "❌" + } + results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition) + } + var text string + if len(alert.Description) > 0 { + text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Description* \\n_%s_ \\n\\n*Condition results*\\n%s", message, alert.Description, results) + } else { + text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Condition results*\\n%s", message, results) + } + + return &custom.AlertProvider{ + URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), + Method: http.MethodPost, + Body: fmt.Sprintf(`{"chat_id": "%s", "text": "%s", "parse_mode": "MARKDOWN" }`, provider.ID, text), + Headers: map[string]string{"Content-Type": "application/json"}, + } +} diff --git a/alerting/provider/telegram/telegram_test.go b/alerting/provider/telegram/telegram_test.go new file mode 100644 index 00000000..c4e7631b --- /dev/null +++ b/alerting/provider/telegram/telegram_test.go @@ -0,0 +1,89 @@ +package telegram + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/TwinProduction/gatus/core" +) + +func TestAlertProvider_IsValid(t *testing.T) { + invalidProvider := AlertProvider{Token: "", ID: ""} + if invalidProvider.IsValid() { + t.Error("provider shouldn't have been valid") + } + validProvider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"} + if !validProvider.IsValid() { + t.Error("provider should've been valid") + } +} + +func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) { + provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"} + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true) + if customAlertProvider == nil { + t.Fatal("customAlertProvider shouldn't have been nil") + } + if !strings.Contains(customAlertProvider.Body, "resolved") { + t.Error("customAlertProvider.Body should've contained the substring resolved") + } + if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) { + t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL) + } + if customAlertProvider.Method != http.MethodPost { + t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method) + } + body := make(map[string]interface{}) + err := json.Unmarshal([]byte(customAlertProvider.Body), &body) + //_, err := json.Marshal(customAlertProvider.Body) + if err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } +} + +func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) { + provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "0123456789"} + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{Description: "Healthcheck Successful"}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "UNSUCCESSFUL_CONDITION", Success: false}}}, false) + if customAlertProvider == nil { + t.Fatal("customAlertProvider shouldn't have been nil") + } + if !strings.Contains(customAlertProvider.Body, "triggered") { + t.Error("customAlertProvider.Body should've contained the substring triggered") + } + if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) { + t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL) + } + if customAlertProvider.Method != http.MethodPost { + t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method) + } + body := make(map[string]interface{}) + err := json.Unmarshal([]byte(customAlertProvider.Body), &body) + if err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } +} + +func TestAlertProvider_ToCustomAlertProviderWithDescription(t *testing.T) { + provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "0123456789"} + customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "UNSUCCESSFUL_CONDITION", Success: false}}}, false) + if customAlertProvider == nil { + t.Fatal("customAlertProvider shouldn't have been nil") + } + if !strings.Contains(customAlertProvider.Body, "triggered") { + t.Error("customAlertProvider.Body should've contained the substring triggered") + } + if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) { + t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL) + } + if customAlertProvider.Method != http.MethodPost { + t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method) + } + body := make(map[string]interface{}) + err := json.Unmarshal([]byte(customAlertProvider.Body), &body) + if err != nil { + t.Error("expected body to be valid JSON, got error:", err.Error()) + } +} diff --git a/config/config.go b/config/config.go index 51036194..31e6c06d 100644 --- a/config/config.go +++ b/config/config.go @@ -234,6 +234,7 @@ func validateAlertingConfig(config *Config) { core.MessagebirdAlert, core.PagerDutyAlert, core.SlackAlert, + core.TelegramAlert, core.TwilioAlert, } var validProviders, invalidProviders []core.AlertType @@ -292,6 +293,12 @@ func GetAlertingProviderByAlertType(config *Config, alertType core.AlertType) pr return nil } return config.Alerting.Slack + case core.TelegramAlert: + if config.Alerting.Telegram == nil { + // Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil + return nil + } + return config.Alerting.Telegram case core.TwilioAlert: if config.Alerting.Twilio == nil { // Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil diff --git a/config/config_test.go b/config/config_test.go index 1ee72654..6912a801 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -13,6 +13,7 @@ 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/telegram" "github.com/TwinProduction/gatus/alerting/provider/twilio" "github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/k8stest" @@ -354,6 +355,9 @@ alerting: access-key: "1" originator: "31619191918" recipients: "31619191919" + telegram: + token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 + id: 0123456789 services: - name: twinnation url: https://twinnation.org/health @@ -369,6 +373,8 @@ services: - type: discord enabled: true failure-threshold: 10 + - type: telegram + enabled: true conditions: - "[STATUS] == 200" `)) @@ -394,12 +400,13 @@ services: if config.Alerting.PagerDuty == nil || !config.Alerting.PagerDuty.IsValid() { t.Fatal("PagerDuty alerting config should've been valid") } - if config.Alerting.Messagebird == nil || !config.Alerting.Messagebird.IsValid() { - t.Fatal("Messagebird alerting config should've been valid") - } if config.Alerting.PagerDuty.IntegrationKey != "00000000000000000000000000000000" { t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.IntegrationKey) } + + if config.Alerting.Messagebird == nil || !config.Alerting.Messagebird.IsValid() { + t.Fatal("Messagebird alerting config should've been valid") + } if config.Alerting.Messagebird.AccessKey != "1" { t.Errorf("Messagebird access key should've been %s, but was %s", "1", config.Alerting.Messagebird.AccessKey) } @@ -409,12 +416,20 @@ services: if config.Alerting.Messagebird.Recipients != "31619191919" { t.Errorf("Messagebird to recipients should've been %s, but was %s", "31619191919", config.Alerting.Messagebird.Recipients) } + if config.Alerting.Discord == nil || !config.Alerting.Discord.IsValid() { t.Fatal("Discord alerting config should've been valid") } if config.Alerting.Discord.WebhookURL != "http://example.org" { t.Errorf("Discord webhook should've been %s, but was %s", "http://example.org", config.Alerting.Discord.WebhookURL) } + + if config.Alerting.Telegram.Token != "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" { + t.Errorf("Telegram token should've been %s, but was %s", "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", config.Alerting.Telegram.Token) + } + if config.Alerting.Telegram.ID != "0123456789" { + t.Errorf("Telegram ID should've been %s, but was %s", "012345689", config.Alerting.Telegram.ID) + } if GetAlertingProviderByAlertType(config, core.DiscordAlert) != config.Alerting.Discord { t.Error("expected discord configuration") } @@ -428,8 +443,8 @@ services: if config.Services[0].Interval != 60*time.Second { t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) } - if len(config.Services[0].Alerts) != 4 { - t.Fatal("There should've been 4 alerts configured") + if len(config.Services[0].Alerts) != 5 { + t.Fatal("There should've been 5 alerts configured") } if config.Services[0].Alerts[0].Type != core.SlackAlert { @@ -451,9 +466,6 @@ services: if config.Services[0].Alerts[1].Description != "Healthcheck failed 7 times in a row" { t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Services[0].Alerts[1].Description) } - if config.Services[0].Alerts[1].FailureThreshold != 7 { - t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 7, config.Services[0].Alerts[1].FailureThreshold) - } if config.Services[0].Alerts[1].SuccessThreshold != 5 { t.Errorf("The success threshold of the alert should've been %d, but it was %d", 5, config.Services[0].Alerts[1].SuccessThreshold) } @@ -855,6 +867,7 @@ func TestGetAlertingProviderByAlertType(t *testing.T) { Messagebird: &messagebird.AlertProvider{}, PagerDuty: &pagerduty.AlertProvider{}, Slack: &slack.AlertProvider{}, + Telegram: &telegram.AlertProvider{}, Twilio: &twilio.AlertProvider{}, }, } @@ -876,6 +889,9 @@ func TestGetAlertingProviderByAlertType(t *testing.T) { if GetAlertingProviderByAlertType(cfg, core.SlackAlert) != cfg.Alerting.Slack { t.Error("expected Slack configuration") } + if GetAlertingProviderByAlertType(cfg, core.TelegramAlert) != cfg.Alerting.Telegram { + t.Error("expected Telegram configuration") + } if GetAlertingProviderByAlertType(cfg, core.TwilioAlert) != cfg.Alerting.Twilio { t.Error("expected Twilio configuration") } diff --git a/core/alert.go b/core/alert.go index 254c09ef..e81e02e4 100644 --- a/core/alert.go +++ b/core/alert.go @@ -58,6 +58,9 @@ const ( // SlackAlert is the AlertType for the slack alerting provider SlackAlert AlertType = "slack" + // TelegramAlert is the AlertType for the telegram alerting provider + TelegramAlert AlertType = "telegram" + // TwilioAlert is the AlertType for the twilio alerting provider TwilioAlert AlertType = "twilio" )