mirror of
https://github.com/TwiN/gatus.git
synced 2025-01-24 14:58:41 +01:00
Opsgenie Alert Provider (#214)
* ✨ opsgenie alert provider * ✅ add unit tests * ✏️ typofix * 📝 update readme * ✨ add details * ✨ use group to previne colisions * ✏️ typofix * ✏️ typofix
This commit is contained in:
parent
b5a26caa08
commit
7353fad809
22
README.md
22
README.md
@ -311,8 +311,10 @@ ignored.
|
|||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:-----------------------|:---------------------------------------------------------------------------------------------------------------------- |:-------|
|
|:-----------------------|:---------------------------------------------------------------------------------------------------------------------- |:-------|
|
||||||
| `alerting.discord` | Configuration for alerts of type `discord`. <br />See [Configuring Discord alerts](#configuring-discord-alerts). | `{}` |
|
| `alerting.discord` | Configuration for alerts of type `discord`. <br />See [Configuring Discord alerts](#configuring-discord-alerts). | `{}` |
|
||||||
| `alerting.mattermost` | Configuration for alerts of type `mattermost`. <br />See [Configuring Mattermost alerts](#configuring-mattermost-alerts). | `{}` |
|
| `alerting.email` | Configuration for alerts of type `email`. <br />See [Configuring Email alerts](#configuring-email-alerts). | `{}` |
|
||||||
|
| `alerting.mattermost` | Configuration for alerts of type `mattermost`. <br />See [Configuring Mattermost alerts](#configuring-mattermost-alerts). | `{}` |
|
||||||
| `alerting.messagebird` | Configuration for alerts of type `messagebird`. <br />See [Configuring Messagebird alerts](#configuring-messagebird-alerts). | `{}` |
|
| `alerting.messagebird` | Configuration for alerts of type `messagebird`. <br />See [Configuring Messagebird alerts](#configuring-messagebird-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.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). | `{}` |
|
||||||
@ -455,6 +457,24 @@ endpoints:
|
|||||||
description: "healthcheck failed"
|
description: "healthcheck failed"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Configuring Opsgenie alerts
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|:------------------------------------------------------ |:----------------------------------------------------------------------------- |:-------------------- |
|
||||||
|
| `alerting.opsgenie` | Configuration for alerts of type `opsgenie` | `{}` |
|
||||||
|
| `alerting.opsgenie.api-key` | Opsgenie API Key | Required `""` |
|
||||||
|
| `alerting.opsgenie.priority` | Priority level of the alert. | `P1` |
|
||||||
|
| `alerting.opsgenie.source` | Source field of the alert. | `gatus` |
|
||||||
|
| `alerting.opsgenie.entity-prefix` | Entity field prefix. | `gatus-` |
|
||||||
|
| `alerting.opsgenie.alias-prefix` | Alias field prefix. | `gatus-healthcheck-` |
|
||||||
|
| `alerting.opsgenie.tags` | Tags of alert. | `[]` |
|
||||||
|
|
||||||
|
Opsgenie provider will automatically open and close alerts.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
alerting:
|
||||||
|
opsgenie:
|
||||||
|
api-key: "00000000-0000-0000-0000-000000000000"
|
||||||
|
```
|
||||||
|
|
||||||
#### Configuring PagerDuty alerts
|
#### Configuring PagerDuty alerts
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|
@ -34,4 +34,7 @@ const (
|
|||||||
|
|
||||||
// TypeTwilio is the Type for the twilio alerting provider
|
// TypeTwilio is the Type for the twilio alerting provider
|
||||||
TypeTwilio Type = "twilio"
|
TypeTwilio Type = "twilio"
|
||||||
|
|
||||||
|
// TypeOpsgenie is the Type for the opsgenie alerting provider
|
||||||
|
TypeOpsgenie Type = "opsgenie"
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/TwiN/gatus/v3/alerting/provider/email"
|
"github.com/TwiN/gatus/v3/alerting/provider/email"
|
||||||
"github.com/TwiN/gatus/v3/alerting/provider/mattermost"
|
"github.com/TwiN/gatus/v3/alerting/provider/mattermost"
|
||||||
"github.com/TwiN/gatus/v3/alerting/provider/messagebird"
|
"github.com/TwiN/gatus/v3/alerting/provider/messagebird"
|
||||||
|
"github.com/TwiN/gatus/v3/alerting/provider/opsgenie"
|
||||||
"github.com/TwiN/gatus/v3/alerting/provider/pagerduty"
|
"github.com/TwiN/gatus/v3/alerting/provider/pagerduty"
|
||||||
"github.com/TwiN/gatus/v3/alerting/provider/slack"
|
"github.com/TwiN/gatus/v3/alerting/provider/slack"
|
||||||
"github.com/TwiN/gatus/v3/alerting/provider/teams"
|
"github.com/TwiN/gatus/v3/alerting/provider/teams"
|
||||||
@ -46,6 +47,9 @@ type Config struct {
|
|||||||
|
|
||||||
// Twilio is the configuration for the twilio alerting provider
|
// Twilio is the configuration for the twilio alerting provider
|
||||||
Twilio *twilio.AlertProvider `yaml:"twilio,omitempty"`
|
Twilio *twilio.AlertProvider `yaml:"twilio,omitempty"`
|
||||||
|
|
||||||
|
// Opsgenie is the configuration for the opsgenie alerting provider
|
||||||
|
Opsgenie *opsgenie.AlertProvider `yaml:"opsgenie,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type
|
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type
|
||||||
@ -81,6 +85,12 @@ func (config Config) GetAlertingProviderByAlertType(alertType alert.Type) provid
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return config.Messagebird
|
return config.Messagebird
|
||||||
|
case alert.TypeOpsgenie:
|
||||||
|
if config.Opsgenie == nil {
|
||||||
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return config.Opsgenie
|
||||||
case alert.TypePagerDuty:
|
case alert.TypePagerDuty:
|
||||||
if config.PagerDuty == nil {
|
if config.PagerDuty == nil {
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
|
267
alerting/provider/opsgenie/opsgenie.go
Normal file
267
alerting/provider/opsgenie/opsgenie.go
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TwiN/gatus/v3/alerting/alert"
|
||||||
|
"github.com/TwiN/gatus/v3/client"
|
||||||
|
"github.com/TwiN/gatus/v3/core"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
restAPI = "https://api.opsgenie.com/v2/alerts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type opsgenieAlertCreateRequest struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Priority string `json:"priority"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Entity string `json:"entity"`
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Details map[string]string `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type opsgenieAlertCloseRequest struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Note string `json:"note"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertProvider struct {
|
||||||
|
APIKey string `yaml:"api-key"`
|
||||||
|
|
||||||
|
//Priority define priority to be used in opsgenie alert payload
|
||||||
|
// defaults: P1
|
||||||
|
Priority string `yaml:"priority"`
|
||||||
|
|
||||||
|
//Source define source to be used in opsgenie alert payload
|
||||||
|
// defaults: gatus
|
||||||
|
Source string `yaml:"source"`
|
||||||
|
|
||||||
|
//EntityPrefix is a prefix to be used in entity argument in opsgenie alert payload
|
||||||
|
// defaults: gatus-
|
||||||
|
EntityPrefix string `yaml:"entity-prefix"`
|
||||||
|
|
||||||
|
//AliasPrefix is a prefix to be used in alias argument in opsgenie alert payload
|
||||||
|
// defaults: gatus-healthcheck-
|
||||||
|
AliasPrefix string `yaml:"alias-prefix"`
|
||||||
|
|
||||||
|
//tags define tags to be used in opsgenie alert payload
|
||||||
|
// defaults: []
|
||||||
|
Tags []string `yaml:"tags"`
|
||||||
|
|
||||||
|
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
|
||||||
|
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) IsValid() bool {
|
||||||
|
return len(provider.APIKey) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an alert using the provider
|
||||||
|
//
|
||||||
|
// Relevant: https://docs.opsgenie.com/docs/alert-api
|
||||||
|
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||||
|
err := provider.createAlert(endpoint, alert, result, resolved)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolved {
|
||||||
|
err = provider.closeAlert(endpoint, alert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if alert.IsSendingOnResolved() {
|
||||||
|
if resolved {
|
||||||
|
// The alert has been resolved and there's no error, so we can clear the alert's ResolveKey
|
||||||
|
alert.ResolveKey = ""
|
||||||
|
} else {
|
||||||
|
alert.ResolveKey = provider.alias(buildKey(endpoint))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) createAlert(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||||
|
payload := provider.buildCreateRequestBody(endpoint, alert, result, resolved)
|
||||||
|
|
||||||
|
_, err := provider.sendRequest(restAPI, http.MethodPost, payload)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) closeAlert(endpoint *core.Endpoint, alert *alert.Alert) error {
|
||||||
|
payload := provider.buildCloseRequestBody(endpoint, alert)
|
||||||
|
url := restAPI + "/" + provider.alias(buildKey(endpoint)) + "/close?identifierType=alias"
|
||||||
|
|
||||||
|
_, err := provider.sendRequest(url, http.MethodPost, payload)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) sendRequest(url, method string, payload interface{}) (*http.Response, error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(payload)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to build alert payload: %v", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(method, url, bytes.NewBuffer(body))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
request.Header.Set("Authorization", "GenieKey "+provider.APIKey)
|
||||||
|
|
||||||
|
res, err := client.GetHTTPClient(nil).Do(request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode > 399 {
|
||||||
|
rBody, _ := io.ReadAll(res.Body)
|
||||||
|
return nil, fmt.Errorf("call to provider alert returned status code %d: %s", res.StatusCode, string(rBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) opsgenieAlertCreateRequest {
|
||||||
|
var message, description, results string
|
||||||
|
|
||||||
|
if resolved {
|
||||||
|
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription())
|
||||||
|
description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.Name, alert.SuccessThreshold)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("%s - %s", endpoint.Name, alert.GetDescription())
|
||||||
|
description = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.Name, alert.FailureThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if endpoint.Group != "" {
|
||||||
|
message = fmt.Sprintf("[%s] %s", endpoint.Group, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, conditionResult := range result.ConditionResults {
|
||||||
|
var prefix string
|
||||||
|
if conditionResult.Success {
|
||||||
|
prefix = "▣"
|
||||||
|
} else {
|
||||||
|
prefix = "▢"
|
||||||
|
}
|
||||||
|
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
description = description + "\n" + results
|
||||||
|
|
||||||
|
key := buildKey(endpoint)
|
||||||
|
details := map[string]string{
|
||||||
|
"endpoint:url": endpoint.URL,
|
||||||
|
"endpoint:group": endpoint.Group,
|
||||||
|
"result:hostname": result.Hostname,
|
||||||
|
"result:ip": result.IP,
|
||||||
|
"result:dns_code": result.DNSRCode,
|
||||||
|
"result:errors": strings.Join(result.Errors, ","),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range details {
|
||||||
|
if v == "" {
|
||||||
|
delete(details, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.HTTPStatus > 0 {
|
||||||
|
details["result:http_status"] = strconv.Itoa(result.HTTPStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opsgenieAlertCreateRequest{
|
||||||
|
Message: message,
|
||||||
|
Description: description,
|
||||||
|
Source: provider.source(),
|
||||||
|
Priority: provider.priority(),
|
||||||
|
Alias: provider.alias(key),
|
||||||
|
Entity: provider.entity(key),
|
||||||
|
Tags: provider.Tags,
|
||||||
|
Details: details,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) buildCloseRequestBody(endpoint *core.Endpoint, alert *alert.Alert) opsgenieAlertCloseRequest {
|
||||||
|
return opsgenieAlertCloseRequest{
|
||||||
|
Source: buildKey(endpoint),
|
||||||
|
Note: fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) source() string {
|
||||||
|
source := provider.Source
|
||||||
|
|
||||||
|
if source == "" {
|
||||||
|
return "gatus"
|
||||||
|
}
|
||||||
|
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) alias(key string) string {
|
||||||
|
alias := provider.AliasPrefix
|
||||||
|
|
||||||
|
if alias == "" {
|
||||||
|
alias = "gatus-healthcheck-"
|
||||||
|
}
|
||||||
|
|
||||||
|
return alias + key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) entity(key string) string {
|
||||||
|
alias := provider.EntityPrefix
|
||||||
|
if alias == "" {
|
||||||
|
alias = "gatus-"
|
||||||
|
}
|
||||||
|
|
||||||
|
return alias + key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AlertProvider) priority() string {
|
||||||
|
priority := provider.Priority
|
||||||
|
|
||||||
|
if priority == "" {
|
||||||
|
return "P1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return priority
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider AlertProvider) GetDefaultAlert() *alert.Alert {
|
||||||
|
return provider.DefaultAlert
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKey(endpoint *core.Endpoint) string {
|
||||||
|
name := toKebabCase(endpoint.Name)
|
||||||
|
|
||||||
|
if endpoint.Group == "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
return toKebabCase(endpoint.Group) + "-" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func toKebabCase(val string) string {
|
||||||
|
return strings.ToLower(strings.ReplaceAll(val, " ", "-"))
|
||||||
|
}
|
331
alerting/provider/opsgenie/opsgenie_test.go
Normal file
331
alerting/provider/opsgenie/opsgenie_test.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package opsgenie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TwiN/gatus/v3/alerting/alert"
|
||||||
|
"github.com/TwiN/gatus/v3/client"
|
||||||
|
"github.com/TwiN/gatus/v3/core"
|
||||||
|
"github.com/TwiN/gatus/v3/test"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertProvider_IsValid(t *testing.T) {
|
||||||
|
invalidProvider := AlertProvider{APIKey: ""}
|
||||||
|
if invalidProvider.IsValid() {
|
||||||
|
t.Error("provider shouldn't have been valid")
|
||||||
|
}
|
||||||
|
validProvider := AlertProvider{APIKey: "00000000-0000-0000-0000-000000000000"}
|
||||||
|
if !validProvider.IsValid() {
|
||||||
|
t.Error("provider should've been valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_Send(t *testing.T) {
|
||||||
|
defer client.InjectHTTPClient(nil)
|
||||||
|
|
||||||
|
description := "my bad alert description"
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
Name string
|
||||||
|
Provider AlertProvider
|
||||||
|
Alert alert.Alert
|
||||||
|
Resolved bool
|
||||||
|
MockRoundTripper test.MockRoundTripper
|
||||||
|
ExpectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "triggered",
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &description, SuccessThreshold: 1, FailureThreshold: 1},
|
||||||
|
Resolved: false,
|
||||||
|
ExpectedError: false,
|
||||||
|
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "triggered-error",
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: false,
|
||||||
|
ExpectedError: true,
|
||||||
|
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
|
||||||
|
return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "resolved",
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: true,
|
||||||
|
ExpectedError: false,
|
||||||
|
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "resolved-error",
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &description, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: true,
|
||||||
|
ExpectedError: true,
|
||||||
|
MockRoundTripper: test.MockRoundTripper(func(r *http.Request) *http.Response {
|
||||||
|
return &http.Response{StatusCode: http.StatusInternalServerError, Body: http.NoBody}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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_buildCreateRequestBody(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
description := "alert description"
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
Name string
|
||||||
|
Provider *AlertProvider
|
||||||
|
Alert *alert.Alert
|
||||||
|
Endpoint *core.Endpoint
|
||||||
|
Result *core.Result
|
||||||
|
Resolved bool
|
||||||
|
want opsgenieAlertCreateRequest
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "missing all params (unresolved)",
|
||||||
|
Provider: &AlertProvider{},
|
||||||
|
Alert: &alert.Alert{},
|
||||||
|
Endpoint: &core.Endpoint{},
|
||||||
|
Result: &core.Result{},
|
||||||
|
Resolved: false,
|
||||||
|
want: opsgenieAlertCreateRequest{
|
||||||
|
Message: " - ",
|
||||||
|
Priority: "P1",
|
||||||
|
Source: "gatus",
|
||||||
|
Entity: "gatus-",
|
||||||
|
Alias: "gatus-healthcheck-",
|
||||||
|
Description: "An alert for ** has been triggered due to having failed 0 time(s) in a row\n",
|
||||||
|
Tags: nil,
|
||||||
|
Details: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "missing all params (resolved)",
|
||||||
|
Provider: &AlertProvider{},
|
||||||
|
Alert: &alert.Alert{},
|
||||||
|
Endpoint: &core.Endpoint{},
|
||||||
|
Result: &core.Result{},
|
||||||
|
Resolved: true,
|
||||||
|
want: opsgenieAlertCreateRequest{
|
||||||
|
Message: "RESOLVED: - ",
|
||||||
|
Priority: "P1",
|
||||||
|
Source: "gatus",
|
||||||
|
Entity: "gatus-",
|
||||||
|
Alias: "gatus-healthcheck-",
|
||||||
|
Description: "An alert for ** has been resolved after passing successfully 0 time(s) in a row\n",
|
||||||
|
Tags: nil,
|
||||||
|
Details: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with default options (unresolved)",
|
||||||
|
Provider: &AlertProvider{},
|
||||||
|
Alert: &alert.Alert{
|
||||||
|
Description: &description,
|
||||||
|
FailureThreshold: 3,
|
||||||
|
},
|
||||||
|
Endpoint: &core.Endpoint{
|
||||||
|
Name: "my supper app",
|
||||||
|
},
|
||||||
|
Result: &core.Result{
|
||||||
|
ConditionResults: []*core.ConditionResult{
|
||||||
|
{
|
||||||
|
Condition: "[STATUS] == 200",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: "[BODY] == OK",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resolved: false,
|
||||||
|
want: opsgenieAlertCreateRequest{
|
||||||
|
Message: "my supper app - " + description,
|
||||||
|
Priority: "P1",
|
||||||
|
Source: "gatus",
|
||||||
|
Entity: "gatus-my-supper-app",
|
||||||
|
Alias: "gatus-healthcheck-my-supper-app",
|
||||||
|
Description: "An alert for *my supper app* has been triggered due to having failed 3 time(s) in a row\n" +
|
||||||
|
"▣ - `[STATUS] == 200`\n" +
|
||||||
|
"▢ - `[BODY] == OK`\n",
|
||||||
|
Tags: nil,
|
||||||
|
Details: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with custom options (resolved)",
|
||||||
|
Provider: &AlertProvider{
|
||||||
|
Priority: "P5",
|
||||||
|
EntityPrefix: "oompa-",
|
||||||
|
AliasPrefix: "loompa-",
|
||||||
|
Source: "gatus-hc",
|
||||||
|
Tags: []string{"do-ba-dee-doo"},
|
||||||
|
},
|
||||||
|
Alert: &alert.Alert{
|
||||||
|
Description: &description,
|
||||||
|
SuccessThreshold: 4,
|
||||||
|
},
|
||||||
|
Endpoint: &core.Endpoint{
|
||||||
|
Name: "my mega app",
|
||||||
|
},
|
||||||
|
Result: &core.Result{
|
||||||
|
ConditionResults: []*core.ConditionResult{
|
||||||
|
{
|
||||||
|
Condition: "[STATUS] == 200",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resolved: true,
|
||||||
|
want: opsgenieAlertCreateRequest{
|
||||||
|
Message: "RESOLVED: my mega app - " + description,
|
||||||
|
Priority: "P5",
|
||||||
|
Source: "gatus-hc",
|
||||||
|
Entity: "oompa-my-mega-app",
|
||||||
|
Alias: "loompa-my-mega-app",
|
||||||
|
Description: "An alert for *my mega app* has been resolved after passing successfully 4 time(s) in a row\n" +
|
||||||
|
"▣ - `[STATUS] == 200`\n",
|
||||||
|
Tags: []string{"do-ba-dee-doo"},
|
||||||
|
Details: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "with default options and details (unresolved)",
|
||||||
|
Provider: &AlertProvider{
|
||||||
|
Tags: []string{"foo"},
|
||||||
|
},
|
||||||
|
Alert: &alert.Alert{
|
||||||
|
Description: &description,
|
||||||
|
FailureThreshold: 6,
|
||||||
|
},
|
||||||
|
Endpoint: &core.Endpoint{
|
||||||
|
Name: "my app",
|
||||||
|
Group: "end game",
|
||||||
|
URL: "https://my.go/app",
|
||||||
|
},
|
||||||
|
Result: &core.Result{
|
||||||
|
HTTPStatus: 400,
|
||||||
|
Hostname: "my.go",
|
||||||
|
Errors: []string{"error 01", "error 02"},
|
||||||
|
Success: false,
|
||||||
|
ConditionResults: []*core.ConditionResult{
|
||||||
|
{
|
||||||
|
Condition: "[STATUS] == 200",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resolved: false,
|
||||||
|
want: opsgenieAlertCreateRequest{
|
||||||
|
Message: "[end game] my app - " + description,
|
||||||
|
Priority: "P1",
|
||||||
|
Source: "gatus",
|
||||||
|
Entity: "gatus-end-game-my-app",
|
||||||
|
Alias: "gatus-healthcheck-end-game-my-app",
|
||||||
|
Description: "An alert for *my app* has been triggered due to having failed 6 time(s) in a row\n" +
|
||||||
|
"▢ - `[STATUS] == 200`\n",
|
||||||
|
Tags: []string{"foo"},
|
||||||
|
Details: map[string]string{
|
||||||
|
"endpoint:url": "https://my.go/app",
|
||||||
|
"endpoint:group": "end game",
|
||||||
|
"result:hostname": "my.go",
|
||||||
|
"result:errors": "error 01,error 02",
|
||||||
|
"result:http_status": "400",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
actual := scenario
|
||||||
|
t.Run(actual.Name, func(t *testing.T) {
|
||||||
|
if got := actual.Provider.buildCreateRequestBody(actual.Endpoint, actual.Alert, actual.Result, actual.Resolved); !reflect.DeepEqual(got, actual.want) {
|
||||||
|
t.Errorf("buildCreateRequestBody() = %v, want %v", got, actual.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_buildCloseRequestBody(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
description := "alert description"
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
Name string
|
||||||
|
Provider *AlertProvider
|
||||||
|
Alert *alert.Alert
|
||||||
|
Endpoint *core.Endpoint
|
||||||
|
want opsgenieAlertCloseRequest
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "Missing all values",
|
||||||
|
Provider: &AlertProvider{},
|
||||||
|
Alert: &alert.Alert{},
|
||||||
|
Endpoint: &core.Endpoint{},
|
||||||
|
want: opsgenieAlertCloseRequest{
|
||||||
|
Source: "",
|
||||||
|
Note: "RESOLVED: - ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "Basic values",
|
||||||
|
Provider: &AlertProvider{},
|
||||||
|
Alert: &alert.Alert{
|
||||||
|
Description: &description,
|
||||||
|
},
|
||||||
|
Endpoint: &core.Endpoint{
|
||||||
|
Name: "endpoint name",
|
||||||
|
},
|
||||||
|
want: opsgenieAlertCloseRequest{
|
||||||
|
Source: "endpoint-name",
|
||||||
|
Note: "RESOLVED: endpoint name - alert description",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
actual := scenario
|
||||||
|
t.Run(actual.Name, func(t *testing.T) {
|
||||||
|
if got := actual.Provider.buildCloseRequestBody(actual.Endpoint, actual.Alert); !reflect.DeepEqual(got, actual.want) {
|
||||||
|
t.Errorf("buildCloseRequestBody() = %v, want %v", got, actual.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user