From c6515c4b1c12418ca45c920a709cf872ab142261 Mon Sep 17 00:00:00 2001 From: Bugra Kocabay Date: Sat, 4 Nov 2023 01:21:52 +0300 Subject: [PATCH] feat(alerting): Add gotify provider (#605) * feat(alerting): add gotify provider feature * feat(alerting): update alert message * feat(test): add tests for gotify provider * feat(docs): add documentation for gotify provider * feat(alerting): rename apptoken to token * feat(docs): update docs for apptoken renaming to token --------- Co-authored-by: Bugra Kocabay Co-authored-by: TwiN --- .github/assets/gotify-alerts.png | Bin 0 -> 43954 bytes README.md | 37 +++++++++ alerting/alert/type.go | 3 + alerting/config.go | 4 + alerting/provider/gotify/gotify.go | 105 ++++++++++++++++++++++++ alerting/provider/gotify/gotify_test.go | 105 ++++++++++++++++++++++++ config/config.go | 1 + 7 files changed, 255 insertions(+) create mode 100644 .github/assets/gotify-alerts.png create mode 100644 alerting/provider/gotify/gotify.go create mode 100644 alerting/provider/gotify/gotify_test.go diff --git a/.github/assets/gotify-alerts.png b/.github/assets/gotify-alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..a36387b879e0a197f87cf559582b8999807d1b69 GIT binary patch literal 43954 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 [Configuring GitHub alerts](#configuring-github-alerts). | `{}` | | `alerting.gitlab` | Configuration for alerts of type `gitlab`.
See [Configuring GitLab alerts](#configuring-gitlab-alerts). | `{}` | | `alerting.googlechat` | Configuration for alerts of type `googlechat`.
See [Configuring Google Chat alerts](#configuring-google-chat-alerts). | `{}` | +| `alerting.gotify` | Configuration for alerts of type `gotify`.
See [Configuring Gotify alerts](#configuring-gotify-alerts). | `{}` | | `alerting.matrix` | Configuration for alerts of type `matrix`.
See [Configuring Matrix alerts](#configuring-matrix-alerts). | `{}` | | `alerting.mattermost` | Configuration for alerts of type `mattermost`.
See [Configuring Mattermost alerts](#configuring-mattermost-alerts). | `{}` | | `alerting.messagebird` | Configuration for alerts of type `messagebird`.
See [Configuring Messagebird alerts](#configuring-messagebird-alerts). | `{}` | @@ -638,6 +640,41 @@ endpoints: ``` +#### Configuring Gotify alerts +| Parameter | Description | Default | +|:----------------------------------------------|:--------------------------------------------------------------------------------------------|:-----------------------| +| `alerting.gotify` | Configuration for alerts of type `gotify` | `{}` | +| `alerting.gotify.server-url` | Gotify server URL | Required `""` | +| `alerting.gotify.token` | Token that is used for authentication. | Required `""` | +| `alerting.gotify.priority` | Priority of the alert according to Gotify standarts. | `5` | +| `alerting.gotify.default-alert` | Default alert configuration.
See [Setting a default alert](#setting-a-default-alert). | N/A | +| `alerting.gotify.title` | Title of the notification | `"Gatus: "` | + +```yaml +alerting: + gotify: + server-url: "https://gotify.example" + token: "**************" + +endpoints: + - name: website + url: "https://twin.sh/health" + interval: 5m + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 300" + alerts: + - type: gotify + description: "healthcheck failed" + send-on-resolved: true +``` + +Here's an example of what the notifications look like: + +![Gotify notifications](.github/assets/gotify-alerts.png) + + #### Configuring Matrix alerts | Parameter | Description | Default | |:-----------------------------------------|:-------------------------------------------------------------------------------------------|:-----------------------------------| diff --git a/alerting/alert/type.go b/alerting/alert/type.go index 72e201bc..22f7b8d6 100644 --- a/alerting/alert/type.go +++ b/alerting/alert/type.go @@ -26,6 +26,9 @@ const ( // TypeGoogleChat is the Type for the googlechat alerting provider TypeGoogleChat Type = "googlechat" + // TypeGotify is the Type for the gotify alerting provider + TypeGotify Type = "gotify" + // TypeMatrix is the Type for the matrix alerting provider TypeMatrix Type = "matrix" diff --git a/alerting/config.go b/alerting/config.go index 6b35208d..af52115d 100644 --- a/alerting/config.go +++ b/alerting/config.go @@ -14,6 +14,7 @@ import ( "github.com/TwiN/gatus/v5/alerting/provider/github" "github.com/TwiN/gatus/v5/alerting/provider/gitlab" "github.com/TwiN/gatus/v5/alerting/provider/googlechat" + "github.com/TwiN/gatus/v5/alerting/provider/gotify" "github.com/TwiN/gatus/v5/alerting/provider/matrix" "github.com/TwiN/gatus/v5/alerting/provider/mattermost" "github.com/TwiN/gatus/v5/alerting/provider/messagebird" @@ -50,6 +51,9 @@ type Config struct { // GoogleChat is the configuration for the googlechat alerting provider GoogleChat *googlechat.AlertProvider `yaml:"googlechat,omitempty"` + // Gotify is the configuration for the gotify alerting provider + Gotify *gotify.AlertProvider `yaml:"gotify,omitempty"` + // Matrix is the configuration for the matrix alerting provider Matrix *matrix.AlertProvider `yaml:"matrix,omitempty"` diff --git a/alerting/provider/gotify/gotify.go b/alerting/provider/gotify/gotify.go new file mode 100644 index 00000000..e9b1d59b --- /dev/null +++ b/alerting/provider/gotify/gotify.go @@ -0,0 +1,105 @@ +package gotify + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/client" + "github.com/TwiN/gatus/v5/core" +) + +const DefaultPriority = 5 + +// AlertProvider is the configuration necessary for sending an alert using Gotify +type AlertProvider struct { + // ServerURL is the URL of the Gotify server + ServerURL string `yaml:"server-url"` + + // Token is the token to use when sending a message to the Gotify server + Token string `yaml:"token"` + + // Priority is the priority of the message + Priority int `yaml:"priority,omitempty"` // Defaults to DefaultPriority + + // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type + DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"` + + // Title is the title of the message that will be sent + Title string `yaml:"title,omitempty"` +} + +// IsValid returns whether the provider's configuration is valid +func (provider *AlertProvider) IsValid() bool { + if provider.Priority == 0 { + provider.Priority = DefaultPriority + } + return len(provider.ServerURL) > 0 && len(provider.Token) > 0 +} + +// Send an alert using the provider +func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error { + buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved)) + request, err := http.NewRequest(http.MethodPost, provider.ServerURL+"/message?token="+provider.Token, buffer) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + response, err := client.GetHTTPClient(nil).Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode > 399 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("failed to send alert to Gotify: %s", string(body)) + } + return nil +} + +type Body struct { + Message string `json:"message"` + Title string `json:"title"` + Priority int `json:"priority"` +} + +// buildRequestBody builds the request body for the provider +func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { + var message, results string + if resolved { + message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) + } else { + message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) + } + for _, conditionResult := range result.ConditionResults { + var prefix string + if conditionResult.Success { + prefix = "✓" + } else { + prefix = "✕" + } + results += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition) + } + if len(alert.GetDescription()) > 0 { + message += " with the following description: " + alert.GetDescription() + } + message += results + title := "Gatus: " + endpoint.DisplayName() + if provider.Title != "" { + title = provider.Title + } + body, _ := json.Marshal(Body{ + Message: message, + Title: title, + Priority: provider.Priority, + }) + return body +} + +// GetDefaultAlert returns the provider's default alert configuration +func (provider AlertProvider) GetDefaultAlert() *alert.Alert { + return provider.DefaultAlert +} diff --git a/alerting/provider/gotify/gotify_test.go b/alerting/provider/gotify/gotify_test.go new file mode 100644 index 00000000..19a68b97 --- /dev/null +++ b/alerting/provider/gotify/gotify_test.go @@ -0,0 +1,105 @@ +package gotify + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/TwiN/gatus/v5/alerting/alert" + "github.com/TwiN/gatus/v5/core" +) + +func TestAlertProvider_IsValid(t *testing.T) { + scenarios := []struct { + name string + provider AlertProvider + expected bool + }{ + { + name: "valid", + provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, + expected: true, + }, + { + name: "invalid-server-url", + provider: AlertProvider{ServerURL: "", Token: "faketoken"}, + expected: false, + }, + { + name: "invalid-app-token", + provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: ""}, + expected: false, + }, + { + name: "no-priority-should-use-default-value", + provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, + expected: true, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + if scenario.provider.IsValid() != scenario.expected { + t.Errorf("expected %t, got %t", scenario.expected, scenario.provider.IsValid()) + } + }) + } +} + +func TestAlertProvider_buildRequestBody(t *testing.T) { + var ( + description = "custom-description" + //title = "custom-title" + endpoint = "custom-endpoint" + ) + scenarios := []struct { + Name string + Provider AlertProvider + Alert alert.Alert + Resolved bool + ExpectedBody string + }{ + { + Name: "triggered", + Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpoint, description), + }, + { + Name: "resolved", + Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken"}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: true, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been resolved after passing successfully 5 time(s) in a row with the following description: %s\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"title\":\"Gatus: custom-endpoint\",\"priority\":0}", endpoint, description), + }, + { + Name: "custom-title", + Provider: AlertProvider{ServerURL: "https://gotify.example.com", Token: "faketoken", Title: "custom-title"}, + Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3}, + Resolved: false, + ExpectedBody: fmt.Sprintf("{\"message\":\"An alert for `%s` has been triggered due to having failed 3 time(s) in a row with the following description: %s\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"title\":\"custom-title\",\"priority\":0}", endpoint, description), + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + body := scenario.Provider.buildRequestBody( + &core.Endpoint{Name: endpoint}, + &scenario.Alert, + &core.Result{ + ConditionResults: []*core.ConditionResult{ + {Condition: "[CONNECTED] == true", Success: scenario.Resolved}, + {Condition: "[STATUS] == 200", Success: scenario.Resolved}, + }, + }, + scenario.Resolved, + ) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) + } + out := make(map[string]interface{}) + if err := json.Unmarshal(body, &out); 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 d2824ca9..70f62f2c 100644 --- a/config/config.go +++ b/config/config.go @@ -367,6 +367,7 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E alert.TypeGitHub, alert.TypeGitLab, alert.TypeGoogleChat, + alert.TypeGotify, alert.TypeEmail, alert.TypeMatrix, alert.TypeMattermost,