feat(alerting): Add Pushover provider (#405)

* Add a new Pushover provider (#129)
- Adds new provider named Pushover with corresponding tests
- Adds Pushover as a provider to the configuration and adjusts test accordingly
- Adds Pushover to alerting_test.go, provider.go and type.go
- Updates the readme with configuration details

* Correct import order

* Fix some missing pushover references

* Apply suggestions from code review

* Rename application-key to application-token for Pushover

---------

Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
Kevin Woblick 2023-01-29 23:32:16 +01:00 committed by GitHub
parent d75180c341
commit 21f62f362f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 454 additions and 8 deletions

View File

@ -10,7 +10,7 @@
Gatus is a developer-oriented health dashboard that gives you the ability to monitor your services using HTTP, ICMP, TCP, and even DNS Gatus is a developer-oriented health dashboard that gives you the ability to monitor your services using HTTP, ICMP, TCP, and even DNS
queries as well as evaluate the result of said queries by using a list of conditions on values like the status code, queries as well as evaluate the result of said queries by using a list of conditions on values like the status code,
the response time, the certificate expiration, the body and many others. The icing on top is that each of these health the response time, the certificate expiration, the body and many others. The icing on top is that each of these health
checks can be paired with alerting via Slack, PagerDuty, Discord, Twilio and more. checks can be paired with alerting via Slack, PagerDuty, Pushover, Discord, Twilio and more.
I personally deploy it in my Kubernetes cluster and let it monitor the status of my I personally deploy it in my Kubernetes cluster and let it monitor the status of my
core applications: https://status.twin.sh/ core applications: https://status.twin.sh/
@ -56,6 +56,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
- [Configuring Ntfy alerts](#configuring-ntfy-alerts) - [Configuring Ntfy alerts](#configuring-ntfy-alerts)
- [Configuring Opsgenie alerts](#configuring-opsgenie-alerts) - [Configuring Opsgenie alerts](#configuring-opsgenie-alerts)
- [Configuring PagerDuty alerts](#configuring-pagerduty-alerts) - [Configuring PagerDuty alerts](#configuring-pagerduty-alerts)
- [Configuring Pushover alerts](#configuring-pushover-alerts)
- [Configuring Slack alerts](#configuring-slack-alerts) - [Configuring Slack alerts](#configuring-slack-alerts)
- [Configuring Teams alerts](#configuring-teams-alerts) - [Configuring Teams alerts](#configuring-teams-alerts)
- [Configuring Telegram alerts](#configuring-telegram-alerts) - [Configuring Telegram alerts](#configuring-telegram-alerts)
@ -411,6 +412,7 @@ ignored.
| `alerting.ntfy` | Configuration for alerts of type `ntfy`. <br />See [Configuring Ntfy alerts](#configuring-ntfy-alerts). | `{}` | | `alerting.ntfy` | Configuration for alerts of type `ntfy`. <br />See [Configuring Ntfy alerts](#configuring-ntfy-alerts). | `{}` |
| `alerting.opsgenie` | Configuration for alerts of type `opsgenie`. <br />See [Configuring Opsgenie alerts](#configuring-opsgenie-alerts). | `{}` | | `alerting.opsgenie` | Configuration for alerts of type `opsgenie`. <br />See [Configuring Opsgenie alerts](#configuring-opsgenie-alerts). | `{}` |
| `alerting.pagerduty` | Configuration for alerts of type `pagerduty`. <br />See [Configuring PagerDuty alerts](#configuring-pagerduty-alerts). | `{}` | | `alerting.pagerduty` | Configuration for alerts of type `pagerduty`. <br />See [Configuring PagerDuty alerts](#configuring-pagerduty-alerts). | `{}` |
| `alerting.pushover` | Configuration for alerts of type `pushover`. <br />See [Configuring Pushover alerts](#configuring-pushover-alerts). | `{}` |
| `alerting.slack` | Configuration for alerts of type `slack`. <br />See [Configuring Slack alerts](#configuring-slack-alerts). | `{}` | | `alerting.slack` | Configuration for alerts of type `slack`. <br />See [Configuring Slack alerts](#configuring-slack-alerts). | `{}` |
| `alerting.teams` | Configuration for alerts of type `teams`. <br />See [Configuring Teams alerts](#configuring-teams-alerts). | `{}` | | `alerting.teams` | Configuration for alerts of type `teams`. <br />See [Configuring Teams alerts](#configuring-teams-alerts). | `{}` |
| `alerting.telegram` | Configuration for alerts of type `telegram`. <br />See [Configuring Telegram alerts](#configuring-telegram-alerts). | `{}` | | `alerting.telegram` | Configuration for alerts of type `telegram`. <br />See [Configuring Telegram alerts](#configuring-telegram-alerts). | `{}` |
@ -791,6 +793,38 @@ endpoints:
``` ```
#### Configuring Pushover alerts
| Parameter | Description | Default |
|:---------------------------------------|:-------------------------------------------------------------------------------------------|:-----------------------------|
| `alerting.pushover` | Configuration for alerts of type `pushover` | `{}` |
| `alerting.pushover.application-token` | Pushover Application token | `""` |
| `alerting.pushover.user-key` | User or group key | `""` |
| `alerting.pushover.title` | A fixed title for all messages sent via Pushover | Name of your App in Pushover |
| `alerting.pushover.priority` | Priority of all messages, ranging from -2 (very low) to 2 (Emergency) | `0` |
| `alerting.pushover.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert) | N/A |
```yaml
alerting:
pushover:
application-token: "******************************"
user-key: "******************************"
endpoints:
- name: website
url: "https://twin.sh/health"
interval: 30s
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
- "[RESPONSE_TIME] < 300"
alerts:
- type: pushover
failure-threshold: 3
success-threshold: 5
send-on-resolved: true
description: "healthcheck failed"
```
#### Configuring Slack alerts #### Configuring Slack alerts
| Parameter | Description | Default | | Parameter | Description | Default |
|:------------------------------------------|:-------------------------------------------------------------------------------------------|:--------------| |:------------------------------------------|:-------------------------------------------------------------------------------------------|:--------------|

View File

@ -38,6 +38,9 @@ const (
// TypePagerDuty is the Type for the pagerduty alerting provider // TypePagerDuty is the Type for the pagerduty alerting provider
TypePagerDuty Type = "pagerduty" TypePagerDuty Type = "pagerduty"
// TypePushover is the Type for the pushover alerting provider
TypePushover Type = "pushover"
// TypeSlack is the Type for the slack alerting provider // TypeSlack is the Type for the slack alerting provider
TypeSlack Type = "slack" TypeSlack Type = "slack"

View File

@ -18,6 +18,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/ntfy" "github.com/TwiN/gatus/v5/alerting/provider/ntfy"
"github.com/TwiN/gatus/v5/alerting/provider/opsgenie" "github.com/TwiN/gatus/v5/alerting/provider/opsgenie"
"github.com/TwiN/gatus/v5/alerting/provider/pagerduty" "github.com/TwiN/gatus/v5/alerting/provider/pagerduty"
"github.com/TwiN/gatus/v5/alerting/provider/pushover"
"github.com/TwiN/gatus/v5/alerting/provider/slack" "github.com/TwiN/gatus/v5/alerting/provider/slack"
"github.com/TwiN/gatus/v5/alerting/provider/teams" "github.com/TwiN/gatus/v5/alerting/provider/teams"
"github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/telegram"
@ -59,6 +60,9 @@ type Config struct {
// PagerDuty is the configuration for the pagerduty alerting provider // PagerDuty is the configuration for the pagerduty alerting provider
PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty,omitempty"` PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty,omitempty"`
// Pushover is the configuration for the pushover alerting provider
Pushover *pushover.AlertProvider `yaml:"pushover,omitempty"`
// Slack is the configuration for the slack alerting provider // Slack is the configuration for the slack alerting provider
Slack *slack.AlertProvider `yaml:"slack,omitempty"` Slack *slack.AlertProvider `yaml:"slack,omitempty"`

View File

@ -13,6 +13,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/ntfy" "github.com/TwiN/gatus/v5/alerting/provider/ntfy"
"github.com/TwiN/gatus/v5/alerting/provider/opsgenie" "github.com/TwiN/gatus/v5/alerting/provider/opsgenie"
"github.com/TwiN/gatus/v5/alerting/provider/pagerduty" "github.com/TwiN/gatus/v5/alerting/provider/pagerduty"
"github.com/TwiN/gatus/v5/alerting/provider/pushover"
"github.com/TwiN/gatus/v5/alerting/provider/slack" "github.com/TwiN/gatus/v5/alerting/provider/slack"
"github.com/TwiN/gatus/v5/alerting/provider/teams" "github.com/TwiN/gatus/v5/alerting/provider/teams"
"github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/telegram"
@ -67,6 +68,7 @@ var (
_ AlertProvider = (*ntfy.AlertProvider)(nil) _ AlertProvider = (*ntfy.AlertProvider)(nil)
_ AlertProvider = (*opsgenie.AlertProvider)(nil) _ AlertProvider = (*opsgenie.AlertProvider)(nil)
_ AlertProvider = (*pagerduty.AlertProvider)(nil) _ AlertProvider = (*pagerduty.AlertProvider)(nil)
_ AlertProvider = (*pushover.AlertProvider)(nil)
_ AlertProvider = (*slack.AlertProvider)(nil) _ AlertProvider = (*slack.AlertProvider)(nil)
_ AlertProvider = (*teams.AlertProvider)(nil) _ AlertProvider = (*teams.AlertProvider)(nil)
_ AlertProvider = (*telegram.AlertProvider)(nil) _ AlertProvider = (*telegram.AlertProvider)(nil)

View File

@ -0,0 +1,106 @@
package pushover
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 (
restAPIURL = "https://api.pushover.net/1/messages.json"
defaultPriority = 0
)
// AlertProvider is the configuration necessary for sending an alert using Pushover
type AlertProvider struct {
// Key used to authenticate the application sending
// See "Your Applications" on the dashboard, or add a new one: https://pushover.net/apps/build
ApplicationToken string `yaml:"application-token"`
// Key of the user or group the messages should be sent to
UserKey string `yaml:"user-key"`
// The title of your message, likely the application name
// default: the name of your application in Pushover
Title string `yaml:"title,omitempty"`
// Priority of all messages, ranging from -2 (very low) to 2 (Emergency)
// default: 0
Priority int `yaml:"priority,omitempty"`
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
DefaultAlert *alert.Alert `yaml:"default-alert,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.ApplicationToken) == 30 && len(provider.UserKey) == 30 && provider.Priority >= -2 && provider.Priority <= 2
}
// Send an alert using the provider
// Reference doc for pushover: https://pushover.net/api
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, restAPIURL, 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("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
}
return err
}
type Body struct {
Token string `json:"token"`
User string `json:"user"`
Title string `json:"title,omitempty"`
Message string `json:"message"`
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 string
if resolved {
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription())
} else {
message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription())
}
body, _ := json.Marshal(Body{
Token: provider.ApplicationToken,
User: provider.UserKey,
Title: provider.Title,
Message: message,
Priority: provider.priority(),
})
return body
}
func (provider *AlertProvider) priority() int {
if provider.Priority == 0 {
return defaultPriority
}
return provider.Priority
}
// GetDefaultAlert returns the provider's default alert configuration
func (provider AlertProvider) GetDefaultAlert() *alert.Alert {
return provider.DefaultAlert
}

View File

@ -0,0 +1,174 @@
package pushover
import (
"encoding/json"
"net/http"
"testing"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/client"
"github.com/TwiN/gatus/v5/core"
"github.com/TwiN/gatus/v5/test"
)
func TestPushoverAlertProvider_IsValid(t *testing.T) {
invalidProvider := AlertProvider{}
if invalidProvider.IsValid() {
t.Error("provider shouldn't have been valid")
}
validProvider := AlertProvider{
ApplicationToken: "aTokenWithLengthOf30characters",
UserKey: "aTokenWithLengthOf30characters",
Title: "Gatus Notification",
Priority: 1,
}
if !validProvider.IsValid() {
t.Error("provider should've been valid")
}
}
func TestPushoverAlertProvider_IsInvalid(t *testing.T) {
invalidProvider := AlertProvider{
ApplicationToken: "aTokenWithLengthOfMoreThan30characters",
UserKey: "aTokenWithLengthOfMoreThan30characters",
Priority: 5,
}
if invalidProvider.IsValid() {
t.Error("provider should've been invalid")
}
}
func TestAlertProvider_Send(t *testing.T) {
defer client.InjectHTTPClient(nil)
firstDescription := "description-1"
secondDescription := "description-2"
scenarios := []struct {
Name string
Provider AlertProvider
Alert alert.Alert
Resolved bool
MockRoundTripper test.MockRoundTripper
ExpectedError bool
}{
{
Name: "triggered",
Provider: AlertProvider{},
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: false,
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
}),
ExpectedError: false,
},
{
Name: "triggered-error",
Provider: AlertProvider{},
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: false,
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody}
}),
ExpectedError: true,
},
{
Name: "resolved",
Provider: AlertProvider{},
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: true,
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
}),
ExpectedError: false,
},
{
Name: "resolved-error",
Provider: AlertProvider{},
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: true,
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody}
}),
ExpectedError: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper})
err := scenario.Provider.Send(
&core.Endpoint{Name: "endpoint-name"},
&scenario.Alert,
&core.Result{
ConditionResults: []*core.ConditionResult{
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
},
},
scenario.Resolved,
)
if scenario.ExpectedError && err == nil {
t.Error("expected error, got none")
}
if !scenario.ExpectedError && err != nil {
t.Error("expected no error, got", err.Error())
}
})
}
}
func TestAlertProvider_buildRequestBody(t *testing.T) {
firstDescription := "description-1"
secondDescription := "description-2"
scenarios := []struct {
Name string
Provider AlertProvider
Alert alert.Alert
Resolved bool
ExpectedBody string
}{
{
Name: "triggered",
Provider: AlertProvider{ApplicationToken: "TokenWithLengthOf30Characters1", UserKey: "TokenWithLengthOf30Characters4"},
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: false,
ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters1\",\"user\":\"TokenWithLengthOf30Characters4\",\"message\":\"TRIGGERED: endpoint-name - description-1\",\"priority\":0}",
},
{
Name: "resolved",
Provider: AlertProvider{ApplicationToken: "TokenWithLengthOf30Characters2", UserKey: "TokenWithLengthOf30Characters5", Title: "Gatus Notifications", Priority: 2},
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: true,
ExpectedBody: "{\"token\":\"TokenWithLengthOf30Characters2\",\"user\":\"TokenWithLengthOf30Characters5\",\"title\":\"Gatus Notifications\",\"message\":\"RESOLVED: endpoint-name - description-2\",\"priority\":2}",
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
body := scenario.Provider.buildRequestBody(
&core.Endpoint{Name: "endpoint-name"},
&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([]byte(body), &out); err != nil {
t.Error("expected body to be valid JSON, got error:", err.Error())
}
})
}
}
func TestAlertProvider_GetDefaultAlert(t *testing.T) {
if (AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil {
t.Error("expected default alert to be not nil")
}
if (AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil {
t.Error("expected default alert to be nil")
}
}

View File

@ -352,6 +352,7 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E
alert.TypeNtfy, alert.TypeNtfy,
alert.TypeOpsgenie, alert.TypeOpsgenie,
alert.TypePagerDuty, alert.TypePagerDuty,
alert.TypePushover,
alert.TypeSlack, alert.TypeSlack,
alert.TypeTeams, alert.TypeTeams,
alert.TypeTelegram, alert.TypeTelegram,

View File

@ -22,6 +22,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/ntfy" "github.com/TwiN/gatus/v5/alerting/provider/ntfy"
"github.com/TwiN/gatus/v5/alerting/provider/opsgenie" "github.com/TwiN/gatus/v5/alerting/provider/opsgenie"
"github.com/TwiN/gatus/v5/alerting/provider/pagerduty" "github.com/TwiN/gatus/v5/alerting/provider/pagerduty"
"github.com/TwiN/gatus/v5/alerting/provider/pushover"
"github.com/TwiN/gatus/v5/alerting/provider/slack" "github.com/TwiN/gatus/v5/alerting/provider/slack"
"github.com/TwiN/gatus/v5/alerting/provider/teams" "github.com/TwiN/gatus/v5/alerting/provider/teams"
"github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/telegram"
@ -684,6 +685,9 @@ alerting:
webhook-url: "http://example.org" webhook-url: "http://example.org"
pagerduty: pagerduty:
integration-key: "00000000000000000000000000000000" integration-key: "00000000000000000000000000000000"
pushover:
application-token: "000000000000000000000000000000"
user-key: "000000000000000000000000000000"
mattermost: mattermost:
webhook-url: "http://example.com" webhook-url: "http://example.com"
client: client:
@ -723,6 +727,7 @@ endpoints:
failure-threshold: 12 failure-threshold: 12
success-threshold: 15 success-threshold: 15
- type: teams - type: teams
- type: pushover
conditions: conditions:
- "[STATUS] == 200" - "[STATUS] == 200"
`)) `))
@ -749,8 +754,8 @@ endpoints:
if config.Endpoints[0].Interval != 60*time.Second { if config.Endpoints[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
} }
if len(config.Endpoints[0].Alerts) != 8 { if len(config.Endpoints[0].Alerts) != 9 {
t.Fatal("There should've been 8 alerts configured") t.Fatal("There should've been 9 alerts configured")
} }
if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack { if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack {
@ -850,6 +855,13 @@ endpoints:
if config.Endpoints[0].Alerts[7].SuccessThreshold != 2 { if config.Endpoints[0].Alerts[7].SuccessThreshold != 2 {
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold) t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold)
} }
if config.Endpoints[0].Alerts[8].Type != alert.TypePushover {
t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePushover, config.Endpoints[0].Alerts[8].Type)
}
if !config.Endpoints[0].Alerts[8].IsEnabled() {
t.Error("The alert should've been enabled")
}
} }
func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlert(t *testing.T) { func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlert(t *testing.T) {
@ -874,6 +886,14 @@ alerting:
description: default description description: default description
failure-threshold: 7 failure-threshold: 7
success-threshold: 5 success-threshold: 5
pushover:
application-token: "000000000000000000000000000000"
user-key: "000000000000000000000000000000"
default-alert:
enabled: true
description: default description
failure-threshold: 5
success-threshold: 3
mattermost: mattermost:
webhook-url: "http://example.com" webhook-url: "http://example.com"
default-alert: default-alert:
@ -917,6 +937,7 @@ endpoints:
- type: telegram - type: telegram
- type: twilio - type: twilio
- type: teams - type: teams
- type: pushover
conditions: conditions:
- "[STATUS] == 200" - "[STATUS] == 200"
`)) `))
@ -953,6 +974,19 @@ endpoints:
t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.IntegrationKey) t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.IntegrationKey)
} }
if config.Alerting.Pushover == nil || !config.Alerting.Pushover.IsValid() {
t.Fatal("Pushover alerting config should've been valid")
}
if config.Alerting.Pushover.GetDefaultAlert() == nil {
t.Fatal("Pushover.GetDefaultAlert() shouldn't have returned nil")
}
if config.Alerting.Pushover.ApplicationToken != "000000000000000000000000000000" {
t.Errorf("Pushover application token should've been %s, but was %s", "000000000000000000000000000000", config.Alerting.Pushover.ApplicationToken)
}
if config.Alerting.Pushover.UserKey != "000000000000000000000000000000" {
t.Errorf("Pushover user key should've been %s, but was %s", "000000000000000000000000000000", config.Alerting.Pushover.UserKey)
}
if config.Alerting.Mattermost == nil || !config.Alerting.Mattermost.IsValid() { if config.Alerting.Mattermost == nil || !config.Alerting.Mattermost.IsValid() {
t.Fatal("Mattermost alerting config should've been valid") t.Fatal("Mattermost alerting config should've been valid")
} }
@ -1026,8 +1060,8 @@ endpoints:
if config.Endpoints[0].Interval != 60*time.Second { if config.Endpoints[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
} }
if len(config.Endpoints[0].Alerts) != 8 { if len(config.Endpoints[0].Alerts) != 9 {
t.Fatal("There should've been 8 alerts configured") t.Fatal("There should've been 9 alerts configured")
} }
if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack { if config.Endpoints[0].Alerts[0].Type != alert.TypeSlack {
@ -1131,6 +1165,19 @@ endpoints:
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold) t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[7].SuccessThreshold)
} }
if config.Endpoints[0].Alerts[8].Type != alert.TypePushover {
t.Errorf("The type of the alert should've been %s, but it was %s", alert.TypePushover, config.Endpoints[0].Alerts[8].Type)
}
if !config.Endpoints[0].Alerts[8].IsEnabled() {
t.Error("The alert should've been enabled")
}
if config.Endpoints[0].Alerts[8].FailureThreshold != 5 {
t.Errorf("The default failure threshold of the alert should've been %d, but it was %d", 3, config.Endpoints[0].Alerts[8].FailureThreshold)
}
if config.Endpoints[0].Alerts[8].SuccessThreshold != 3 {
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Endpoints[0].Alerts[8].SuccessThreshold)
}
} }
func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlertAndMultipleAlertsOfSameTypeWithOverriddenParameters(t *testing.T) { func TestParseAndValidateConfigBytesWithAlertingAndDefaultAlertAndMultipleAlertsOfSameTypeWithOverriddenParameters(t *testing.T) {
@ -1238,6 +1285,32 @@ endpoints:
t.Fatal("PagerDuty alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called") t.Fatal("PagerDuty alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called")
} }
} }
func TestParseAndValidateConfigBytesWithInvalidPushoverAlertingConfig(t *testing.T) {
config, err := parseAndValidateConfigBytes([]byte(`
alerting:
pushover:
application-token: "INVALID_TOKEN"
endpoints:
- name: website
url: https://twin.sh/health
alerts:
- type: pushover
conditions:
- "[STATUS] == 200"
`))
if err != nil {
t.Error("expected no error, got", err.Error())
}
if config == nil {
t.Fatal("Config shouldn't have been nil")
}
if config.Alerting == nil {
t.Fatal("config.Alerting shouldn't have been nil")
}
if config.Alerting.Pushover != nil {
t.Fatal("Pushover alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called")
}
}
func TestParseAndValidateConfigBytesWithCustomAlertingConfig(t *testing.T) { func TestParseAndValidateConfigBytesWithCustomAlertingConfig(t *testing.T) {
config, err := parseAndValidateConfigBytes([]byte(` config, err := parseAndValidateConfigBytes([]byte(`
@ -1266,7 +1339,7 @@ endpoints:
t.Fatal("config.Alerting shouldn't have been nil") t.Fatal("config.Alerting shouldn't have been nil")
} }
if config.Alerting.Custom == nil { if config.Alerting.Custom == nil {
t.Fatal("PagerDuty alerting config shouldn't have been nil") t.Fatal("Custom alerting config shouldn't have been nil")
} }
if !config.Alerting.Custom.IsValid() { if !config.Alerting.Custom.IsValid() {
t.Fatal("Custom alerting config should've been valid") t.Fatal("Custom alerting config should've been valid")
@ -1311,7 +1384,7 @@ endpoints:
t.Fatal("config.Alerting shouldn't have been nil") t.Fatal("config.Alerting shouldn't have been nil")
} }
if config.Alerting.Custom == nil { if config.Alerting.Custom == nil {
t.Fatal("PagerDuty alerting config shouldn't have been nil") t.Fatal("Custom alerting config shouldn't have been nil")
} }
if !config.Alerting.Custom.IsValid() { if !config.Alerting.Custom.IsValid() {
t.Fatal("Custom alerting config should've been valid") t.Fatal("Custom alerting config should've been valid")
@ -1351,7 +1424,7 @@ endpoints:
t.Fatal("config.Alerting shouldn't have been nil") t.Fatal("config.Alerting shouldn't have been nil")
} }
if config.Alerting.Custom == nil { if config.Alerting.Custom == nil {
t.Fatal("PagerDuty alerting config shouldn't have been nil") t.Fatal("Custom alerting config shouldn't have been nil")
} }
if !config.Alerting.Custom.IsValid() { if !config.Alerting.Custom.IsValid() {
t.Fatal("Custom alerting config should've been valid") t.Fatal("Custom alerting config should've been valid")
@ -1481,6 +1554,7 @@ func TestGetAlertingProviderByAlertType(t *testing.T) {
Ntfy: &ntfy.AlertProvider{}, Ntfy: &ntfy.AlertProvider{},
Opsgenie: &opsgenie.AlertProvider{}, Opsgenie: &opsgenie.AlertProvider{},
PagerDuty: &pagerduty.AlertProvider{}, PagerDuty: &pagerduty.AlertProvider{},
Pushover: &pushover.AlertProvider{},
Slack: &slack.AlertProvider{}, Slack: &slack.AlertProvider{},
Telegram: &telegram.AlertProvider{}, Telegram: &telegram.AlertProvider{},
Twilio: &twilio.AlertProvider{}, Twilio: &twilio.AlertProvider{},
@ -1501,6 +1575,7 @@ func TestGetAlertingProviderByAlertType(t *testing.T) {
{alertType: alert.TypeNtfy, expected: alertingConfig.Ntfy}, {alertType: alert.TypeNtfy, expected: alertingConfig.Ntfy},
{alertType: alert.TypeOpsgenie, expected: alertingConfig.Opsgenie}, {alertType: alert.TypeOpsgenie, expected: alertingConfig.Opsgenie},
{alertType: alert.TypePagerDuty, expected: alertingConfig.PagerDuty}, {alertType: alert.TypePagerDuty, expected: alertingConfig.PagerDuty},
{alertType: alert.TypePushover, expected: alertingConfig.Pushover},
{alertType: alert.TypeSlack, expected: alertingConfig.Slack}, {alertType: alert.TypeSlack, expected: alertingConfig.Slack},
{alertType: alert.TypeTelegram, expected: alertingConfig.Telegram}, {alertType: alert.TypeTelegram, expected: alertingConfig.Telegram},
{alertType: alert.TypeTwilio, expected: alertingConfig.Twilio}, {alertType: alert.TypeTwilio, expected: alertingConfig.Twilio},

View File

@ -13,6 +13,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/mattermost" "github.com/TwiN/gatus/v5/alerting/provider/mattermost"
"github.com/TwiN/gatus/v5/alerting/provider/messagebird" "github.com/TwiN/gatus/v5/alerting/provider/messagebird"
"github.com/TwiN/gatus/v5/alerting/provider/pagerduty" "github.com/TwiN/gatus/v5/alerting/provider/pagerduty"
"github.com/TwiN/gatus/v5/alerting/provider/pushover"
"github.com/TwiN/gatus/v5/alerting/provider/slack" "github.com/TwiN/gatus/v5/alerting/provider/slack"
"github.com/TwiN/gatus/v5/alerting/provider/teams" "github.com/TwiN/gatus/v5/alerting/provider/teams"
"github.com/TwiN/gatus/v5/alerting/provider/telegram" "github.com/TwiN/gatus/v5/alerting/provider/telegram"
@ -203,6 +204,42 @@ func TestHandleAlertingWhenTriggeredAlertIsResolvedPagerDuty(t *testing.T) {
verify(t, endpoint, 0, 1, false, "The alert should've been resolved") verify(t, endpoint, 0, 1, false, "The alert should've been resolved")
} }
func TestHandleAlertingWhenTriggeredAlertIsResolvedPushover(t *testing.T) {
_ = os.Setenv("MOCK_ALERT_PROVIDER", "true")
defer os.Clearenv()
cfg := &config.Config{
Debug: true,
Alerting: &alerting.Config{
Pushover: &pushover.AlertProvider{
ApplicationToken: "000000000000000000000000000000",
UserKey: "000000000000000000000000000000",
},
},
}
enabled := true
endpoint := &core.Endpoint{
URL: "https://example.com",
Alerts: []*alert.Alert{
{
Type: alert.TypePushover,
Enabled: &enabled,
FailureThreshold: 1,
SuccessThreshold: 1,
SendOnResolved: &enabled,
Triggered: false,
},
},
NumberOfFailuresInARow: 0,
}
HandleAlerting(endpoint, &core.Result{Success: false}, cfg.Alerting, cfg.Debug)
verify(t, endpoint, 1, 0, true, "")
HandleAlerting(endpoint, &core.Result{Success: true}, cfg.Alerting, cfg.Debug)
verify(t, endpoint, 0, 1, false, "The alert should've been resolved")
}
func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) { func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) {
_ = os.Setenv("MOCK_ALERT_PROVIDER", "true") _ = os.Setenv("MOCK_ALERT_PROVIDER", "true")
defer os.Clearenv() defer os.Clearenv()
@ -273,6 +310,16 @@ func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) {
}, },
}, },
}, },
{
Name: "pushover",
AlertType: alert.TypePushover,
AlertingConfig: &alerting.Config{
Pushover: &pushover.AlertProvider{
ApplicationToken: "000000000000000000000000000000",
UserKey: "000000000000000000000000000000",
},
},
},
{ {
Name: "slack", Name: "slack",
AlertType: alert.TypeSlack, AlertType: alert.TypeSlack,