mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-14 11:58:04 +00:00
feat(alerting): Add overrides for Ntfy provider (#918)
* Add overrides to Ntfy alert provider * Update alerting/provider/ntfy/ntfy.go --------- Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
parent
29cbff6774
commit
778019590d
3 changed files with 206 additions and 24 deletions
27
README.md
27
README.md
|
@ -988,7 +988,7 @@ endpoints:
|
|||
|
||||
#### Configuring Ntfy alerts
|
||||
| Parameter | Description | Default |
|
||||
|:---------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------|:------------------|
|
||||
|:-------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------|:------------------|
|
||||
| `alerting.ntfy` | Configuration for alerts of type `ntfy` | `{}` |
|
||||
| `alerting.ntfy.topic` | Topic at which the alert will be sent | Required `""` |
|
||||
| `alerting.ntfy.url` | The URL of the target server | `https://ntfy.sh` |
|
||||
|
@ -999,6 +999,14 @@ endpoints:
|
|||
| `alerting.ntfy.disable-firebase` | Whether message push delivery via firebase should be disabled. [ntfy.sh defaults to enabled](https://docs.ntfy.sh/publish/#disable-firebase) | `false` |
|
||||
| `alerting.ntfy.disable-cache` | Whether server side message caching should be disabled. [ntfy.sh defaults to enabled](https://docs.ntfy.sh/publish/#message-caching) | `false` |
|
||||
| `alerting.ntfy.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert) | N/A |
|
||||
| `alerting.ntfy.overrides` | List of overrides that may be prioritized over the default configuration | `[]` |
|
||||
| `alerting.ntfy.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` |
|
||||
| `alerting.ntfy.overrides[].topic` | Topic at which the alert will be sent | `""` |
|
||||
| `alerting.ntfy.overrides[].url` | The URL of the target server | `""` |
|
||||
| `alerting.ntfy.overrides[].priority` | The priority of the alert | `0` |
|
||||
| `alerting.ntfy.overrides[].token` | Access token for restricted topics | `""` |
|
||||
| `alerting.ntfy.overrides[].email` | E-mail address for additional e-mail notifications | `""` |
|
||||
| `alerting.ntfy.overrides[].click` | Website opened when notification is clicked | `""` |
|
||||
|
||||
[ntfy](https://github.com/binwiederhier/ntfy) is an amazing project that allows you to subscribe to desktop
|
||||
and mobile notifications, making it an awesome addition to Gatus.
|
||||
|
@ -1013,6 +1021,13 @@ alerting:
|
|||
default-alert:
|
||||
failure-threshold: 3
|
||||
send-on-resolved: true
|
||||
# You can also add group-specific to keys, which will
|
||||
# override the to key above for the specified groups
|
||||
overrides:
|
||||
- group: "other"
|
||||
topic: "gatus-other-test-topic"
|
||||
priority: 4
|
||||
click: "https://example.com"
|
||||
|
||||
endpoints:
|
||||
- name: website
|
||||
|
@ -1024,6 +1039,16 @@ endpoints:
|
|||
- "[RESPONSE_TIME] < 300"
|
||||
alerts:
|
||||
- type: ntfy
|
||||
- name: other example
|
||||
group: other
|
||||
interval: 30m
|
||||
url: "https://example.com"
|
||||
conditions:
|
||||
- "[STATUS] == 200"
|
||||
- "[BODY].status == UP"
|
||||
alerts:
|
||||
- type: ntfy
|
||||
description: example
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
const (
|
||||
DefaultURL = "https://ntfy.sh"
|
||||
DefaultPriority = 3
|
||||
TokenPrefix = "tk_"
|
||||
)
|
||||
|
||||
// AlertProvider is the configuration necessary for sending an alert using Slack
|
||||
|
@ -32,6 +33,20 @@ 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"`
|
||||
Topic string `yaml:"topic"`
|
||||
URL string `yaml:"url"`
|
||||
Priority int `yaml:"priority"`
|
||||
Token string `yaml:"token"`
|
||||
Email string `yaml:"email"`
|
||||
Click string `yaml:"click"`
|
||||
}
|
||||
|
||||
// IsValid returns whether the provider's configuration is valid
|
||||
|
@ -44,21 +59,42 @@ func (provider *AlertProvider) IsValid() bool {
|
|||
}
|
||||
isTokenValid := true
|
||||
if len(provider.Token) > 0 {
|
||||
isTokenValid = strings.HasPrefix(provider.Token, "tk_")
|
||||
isTokenValid = strings.HasPrefix(provider.Token, TokenPrefix)
|
||||
}
|
||||
registeredGroups := make(map[string]bool)
|
||||
if provider.Overrides != nil {
|
||||
for _, override := range provider.Overrides {
|
||||
if len(override.Group) == 0 {
|
||||
return false
|
||||
}
|
||||
if _, ok := registeredGroups[override.Group]; ok {
|
||||
return false
|
||||
}
|
||||
if len(override.Token) > 0 && !strings.HasPrefix(override.Token, TokenPrefix) {
|
||||
return false
|
||||
}
|
||||
if override.Priority < 0 || override.Priority >= 6 {
|
||||
return false
|
||||
}
|
||||
registeredGroups[override.Group] = true
|
||||
}
|
||||
}
|
||||
|
||||
return len(provider.URL) > 0 && len(provider.Topic) > 0 && provider.Priority > 0 && provider.Priority < 6 && isTokenValid
|
||||
}
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, provider.URL, buffer)
|
||||
override := provider.getGroupOverride(ep.Group)
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(ep, alert, result, resolved, override))
|
||||
url := provider.getURL(override)
|
||||
request, err := http.NewRequest(http.MethodPost, url, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
if len(provider.Token) > 0 {
|
||||
request.Header.Set("Authorization", "Bearer "+provider.Token)
|
||||
if token := provider.getToken(override); len(token) > 0 {
|
||||
request.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
if provider.DisableFirebase {
|
||||
request.Header.Set("Firebase", "no")
|
||||
|
@ -89,7 +125,7 @@ type Body struct {
|
|||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) []byte {
|
||||
func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool, override *Override) []byte {
|
||||
var message, formattedConditionResults, tag string
|
||||
if resolved {
|
||||
tag = "white_check_mark"
|
||||
|
@ -112,13 +148,13 @@ func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *al
|
|||
}
|
||||
message += formattedConditionResults
|
||||
body, _ := json.Marshal(Body{
|
||||
Topic: provider.Topic,
|
||||
Topic: provider.getTopic(override),
|
||||
Title: "Gatus: " + ep.DisplayName(),
|
||||
Message: message,
|
||||
Tags: []string{tag},
|
||||
Priority: provider.Priority,
|
||||
Email: provider.Email,
|
||||
Click: provider.Click,
|
||||
Priority: provider.getPriority(override),
|
||||
Email: provider.getEmail(override),
|
||||
Click: provider.getClick(override),
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
@ -127,3 +163,56 @@ func (provider *AlertProvider) buildRequestBody(ep *endpoint.Endpoint, alert *al
|
|||
func (provider *AlertProvider) GetDefaultAlert() *alert.Alert {
|
||||
return provider.DefaultAlert
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getGroupOverride(group string) *Override {
|
||||
if provider.Overrides != nil {
|
||||
for _, override := range provider.Overrides {
|
||||
if group == override.Group {
|
||||
return &override
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getTopic(override *Override) string {
|
||||
if override != nil && len(override.Topic) > 0 {
|
||||
return override.Topic
|
||||
}
|
||||
return provider.Topic
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getURL(override *Override) string {
|
||||
if override != nil && len(override.URL) > 0 {
|
||||
return override.URL
|
||||
}
|
||||
return provider.URL
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getPriority(override *Override) int {
|
||||
if override != nil && override.Priority > 0 {
|
||||
return override.Priority
|
||||
}
|
||||
return provider.Priority
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getToken(override *Override) string {
|
||||
if override != nil && len(override.Token) > 0 {
|
||||
return override.Token
|
||||
}
|
||||
return provider.Token
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getEmail(override *Override) string {
|
||||
if override != nil && len(override.Email) > 0 {
|
||||
return override.Email
|
||||
}
|
||||
return provider.Email
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) getClick(override *Override) string {
|
||||
if override != nil && len(override.Click) > 0 {
|
||||
return override.Click
|
||||
}
|
||||
return provider.Click
|
||||
}
|
||||
|
|
|
@ -57,6 +57,31 @@ func TestAlertDefaultProvider_IsValid(t *testing.T) {
|
|||
provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-override-token",
|
||||
provider: AlertProvider{Topic: "example", Overrides: []Override{Override{Group: "g", Token: "xx_faketoken"}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-override-priority",
|
||||
provider: AlertProvider{Topic: "example", Overrides: []Override{Override{Group: "g", Priority: 8}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no-override-group-name",
|
||||
provider: AlertProvider{Topic: "example", Overrides: []Override{Override{}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "duplicate-override-group-names",
|
||||
provider: AlertProvider{Topic: "example", Overrides: []Override{Override{Group: "g"}, Override{Group: "g"}}},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "valid-override",
|
||||
provider: AlertProvider{Topic: "example", Overrides: []Override{Override{Group: "g1", Priority: 4, Click: "https://example.com"}, Override{Group: "g2", Topic: "Example", Token: "tk_faketoken"}}},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
|
@ -75,6 +100,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
Provider AlertProvider
|
||||
Alert alert.Alert
|
||||
Resolved bool
|
||||
Override *Override
|
||||
ExpectedBody string
|
||||
}{
|
||||
{
|
||||
|
@ -82,6 +108,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Override: nil,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1}`,
|
||||
},
|
||||
{
|
||||
|
@ -89,6 +116,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 2},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
Override: nil,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2}`,
|
||||
},
|
||||
{
|
||||
|
@ -96,6 +124,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Override: nil,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
},
|
||||
{
|
||||
|
@ -103,8 +132,17 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 2, Email: "test@example.com", Click: "example.com"},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
Override: nil,
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been resolved after passing successfully 5 time(s) in a row with the following description: description-2\n🟢 [CONNECTED] == true\n🟢 [STATUS] == 200","tags":["white_check_mark"],"priority":2,"email":"test@example.com","click":"example.com"}`,
|
||||
},
|
||||
{
|
||||
Name: "override",
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 5, Email: "test@example.com", Click: "example.com"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Override: &Override{Group: "g", Topic: "override-topic", Priority: 4, Email: "override@test.com", Click: "test.com"},
|
||||
ExpectedBody: `{"topic":"override-topic","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":4,"email":"override@test.com","click":"test.com"}`,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
|
@ -118,6 +156,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||
},
|
||||
},
|
||||
scenario.Resolved,
|
||||
scenario.Override,
|
||||
)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
|
@ -137,6 +176,7 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
Provider AlertProvider
|
||||
Alert alert.Alert
|
||||
Resolved bool
|
||||
Group string
|
||||
ExpectedBody string
|
||||
ExpectedHeaders map[string]string
|
||||
}{
|
||||
|
@ -145,16 +185,30 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com"},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "token",
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken"},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer tk_mytoken",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no firebase",
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
|
@ -166,6 +220,7 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableCache: true},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
|
@ -177,6 +232,7 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", DisableFirebase: true, DisableCache: true},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
|
@ -184,6 +240,18 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
"Cache": "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "overrides",
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1, Email: "test@example.com", Click: "example.com", Token: "tk_mytoken", Overrides: []Override{Override{Group: "other-group", URL: "https://example.com", Token: "tk_othertoken"}, Override{Group: "test-group", Token: "tk_test_token"}}},
|
||||
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
Group: "test-group",
|
||||
ExpectedBody: `{"topic":"example","title":"Gatus: test-group/endpoint-name","message":"An alert has been triggered due to having failed 3 time(s) in a row with the following description: description-1\n🔴 [CONNECTED] == true\n🔴 [STATUS] == 200","tags":["rotating_light"],"priority":1,"email":"test@example.com","click":"example.com"}`,
|
||||
ExpectedHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer tk_test_token",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
|
@ -207,7 +275,7 @@ func TestAlertProvider_Send(t *testing.T) {
|
|||
|
||||
scenario.Provider.URL = server.URL
|
||||
err := scenario.Provider.Send(
|
||||
&endpoint.Endpoint{Name: "endpoint-name"},
|
||||
&endpoint.Endpoint{Name: "endpoint-name", Group: scenario.Group},
|
||||
&scenario.Alert,
|
||||
&endpoint.Result{
|
||||
ConditionResults: []*endpoint.ConditionResult{
|
||||
|
|
Loading…
Reference in a new issue