Merge pull request #51 from olimpias/add-messagebird-alerting-provider

Add Messagebird alerting
This commit is contained in:
Chris C 2020-11-24 18:58:28 -05:00 committed by GitHub
commit 66946e20f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 193 additions and 15 deletions

View File

@ -27,6 +27,7 @@ core applications: https://status.twinnation.org/
- [Configuring PagerDuty alerts](#configuring-pagerduty-alerts) - [Configuring PagerDuty alerts](#configuring-pagerduty-alerts)
- [Configuring Twilio alerts](#configuring-twilio-alerts) - [Configuring Twilio alerts](#configuring-twilio-alerts)
- [Configuring Mattermost alerts](#configuring-mattermost-alerts) - [Configuring Mattermost alerts](#configuring-mattermost-alerts)
- [Configuring Messagebird alerts](#configuring-messagebird-alerts)
- [Configuring custom alerts](#configuring-custom-alerts) - [Configuring custom alerts](#configuring-custom-alerts)
- [Kubernetes (ALPHA)](#kubernetes-alpha) - [Kubernetes (ALPHA)](#kubernetes-alpha)
- [Auto Discovery](#auto-discovery) - [Auto Discovery](#auto-discovery)
@ -50,7 +51,7 @@ The main features of Gatus are:
- **Highly flexible health check conditions**: While checking the response status may be enough for some use cases, Gatus goes much further and allows you to add conditions on the response time, the response body and even the IP address. - **Highly flexible health check conditions**: While checking the response status may be enough for some use cases, Gatus goes much further and allows you to add conditions on the response time, the response body and even the IP address.
- **Ability to use Gatus for user acceptance tests**: Thanks to the point above, you can leverage this application to create automated user acceptance tests. - **Ability to use Gatus for user acceptance tests**: Thanks to the point above, you can leverage this application to create automated user acceptance tests.
- **Very easy to configure**: Not only is the configuration designed to be as readable as possible, it's also extremely easy to add a new service or a new endpoint to monitor. - **Very easy to configure**: Not only is the configuration designed to be as readable as possible, it's also extremely easy to add a new service or a new endpoint to monitor.
- **Alerting**: While having a pretty visual dashboard is useful to keep track of the state of your application(s), you probably don't want to stare at it all day. Thus, notifications via Slack, Mattermost, PagerDuty and Twilio are supported out of the box with the ability to configure a custom alerting provider for any needs you might have, whether it be a different provider or a custom application that manages automated rollbacks. - **Alerting**: While having a pretty visual dashboard is useful to keep track of the state of your application(s), you probably don't want to stare at it all day. Thus, notifications via Slack, Mattermost, Messagebird, PagerDuty and Twilio are supported out of the box with the ability to configure a custom alerting provider for any needs you might have, whether it be a different provider or a custom application that manages automated rollbacks.
- **Metrics** - **Metrics**
- **Low resource consumption**: As with most Go applications, the resource footprint that this application requires is negligibly small. - **Low resource consumption**: As with most Go applications, the resource footprint that this application requires is negligibly small.
- **Service auto discovery in Kubernetes** (ALPHA) - **Service auto discovery in Kubernetes** (ALPHA)
@ -107,7 +108,7 @@ Note that you can also add environment variables in the configuration file (i.e.
| `services[].dns` | Configuration for a service of type DNS. See [Monitoring using DNS queries](#monitoring-using-dns-queries) | `""` | | `services[].dns` | Configuration for a service of type DNS. See [Monitoring using DNS queries](#monitoring-using-dns-queries) | `""` |
| `services[].dns.query-type` | Query type for DNS service | `""` | | `services[].dns.query-type` | Query type for DNS service | `""` |
| `services[].dns.query-name` | Query name for DNS service | `""` | | `services[].dns.query-name` | Query name for DNS service | `""` |
| `services[].alerts[].type` | Type of alert. Valid types: `slack`, `pagerduty`, `twilio`, `mattermost`, `custom` | Required `""` | | `services[].alerts[].type` | Type of alert. Valid types: `slack`, `pagerduty`, `twilio`, `mattermost`, `messagebird`, `custom` | Required `""` |
| `services[].alerts[].enabled` | Whether to enable the alert | `false` | | `services[].alerts[].enabled` | Whether to enable the alert | `false` |
| `services[].alerts[].failure-threshold` | Number of failures in a row needed before triggering the alert | `3` | | `services[].alerts[].failure-threshold` | Number of failures in a row needed before triggering the alert | `3` |
| `services[].alerts[].success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | `2` | | `services[].alerts[].success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | `2` |
@ -126,6 +127,10 @@ Note that you can also add environment variables in the configuration file (i.e.
| `alerting.mattermost` | Configuration for alerts of type `mattermost` | `{}` | | `alerting.mattermost` | Configuration for alerts of type `mattermost` | `{}` |
| `alerting.mattermost.webhook-url` | Mattermost Webhook URL | Required `""` | | `alerting.mattermost.webhook-url` | Mattermost Webhook URL | Required `""` |
| `alerting.mattermost.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` | | `alerting.mattermost.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
| `alerting.messagebird` | Settings for alerts of type `messagebird` | `{}` |
| `alerting.messagebird.access-key` | Messagebird access key | Required `""` |
| `alerting.messagebird.originator` | The sender of the message | Required `""` |
| `alerting.messagebird.recipients` | The recipients of the message | Required `""` |
| `alerting.custom` | Configuration for custom actions on failure or alerts | `{}` | | `alerting.custom` | Configuration for custom actions on failure or alerts | `{}` |
| `alerting.custom.url` | Custom alerting request url | Required `""` | | `alerting.custom.url` | Custom alerting request url | Required `""` |
| `alerting.custom.method` | Request method | `GET` | | `alerting.custom.method` | Request method | `GET` |
@ -306,6 +311,32 @@ Here's an example of what the notifications look like:
![Mattermost notifications](.github/assets/mattermost-alerts.png) ![Mattermost notifications](.github/assets/mattermost-alerts.png)
#### Configuring Messagebird alerts
Example of sending **sms** message alert by using Messagebird
```yaml
alerting:
messagebird:
access-key: "..."
originator: "31619191918"
recipients: "31619191919,31619191920"
services:
- name: twinnation
interval: 30s
url: "https://twinnation.org/health"
alerts:
- type: messagebird
enabled: true
failure-threshold: 3
send-on-resolved: true
description: "healthcheck failed 3 times in a row"
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
- "[RESPONSE_TIME] < 300"
```
#### Configuring custom alerts #### Configuring custom alerts
While they're called alerts, you can use this feature to call anything. While they're called alerts, you can use this feature to call anything.

View File

@ -2,9 +2,10 @@ package alerting
import ( import (
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/alerting/provider/pagerduty" "github.com/TwinProduction/gatus/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/alerting/provider/slack" "github.com/TwinProduction/gatus/alerting/provider/slack"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/twilio" "github.com/TwinProduction/gatus/alerting/provider/twilio"
) )
@ -16,6 +17,9 @@ type Config struct {
// Mattermost is the configuration for the mattermost alerting provider // Mattermost is the configuration for the mattermost alerting provider
Mattermost *mattermost.AlertProvider `yaml:"mattermost"` Mattermost *mattermost.AlertProvider `yaml:"mattermost"`
// Messagebird is the configuration for the messagebird alerting provider
Messagebird *messagebird.AlertProvider `yaml:"messagebird"`
// Pagerduty is the configuration for the pagerduty alerting provider // Pagerduty is the configuration for the pagerduty alerting provider
PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty"` PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty"`

View File

@ -62,7 +62,7 @@ func (provider *AlertProvider) buildRequest(serviceName, alertDescription string
} }
} }
if len(method) == 0 { if len(method) == 0 {
method = "GET" method = http.MethodGet
} }
bodyBuffer := bytes.NewBuffer([]byte(body)) bodyBuffer := bytes.NewBuffer([]byte(body))
request, _ := http.NewRequest(method, providerURL, bodyBuffer) request, _ := http.NewRequest(method, providerURL, bodyBuffer)

View File

@ -2,6 +2,8 @@ package mattermost
import ( import (
"fmt" "fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
) )
@ -39,8 +41,8 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
} }
return &custom.AlertProvider{ return &custom.AlertProvider{
URL: provider.WebhookURL, URL: provider.WebhookURL,
Method: "POST", Method: http.MethodPost,
Insecure: provider.Insecure, Insecure: provider.Insecure,
Body: fmt.Sprintf(`{ Body: fmt.Sprintf(`{
"text": "", "text": "",

View File

@ -0,0 +1,50 @@
package messagebird
import (
"fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
)
const (
restAPIURL = "https://rest.messagebird.com/messages"
)
// AlertProvider is the configuration necessary for sending an alert using Messagebird
type AlertProvider struct {
AccessKey string `yaml:"access-key"`
Originator string `yaml:"originator"`
Recipients string `yaml:"recipients"`
}
// IsValid returns whether the provider's configuration is valid
func (provider *AlertProvider) IsValid() bool {
return len(provider.AccessKey) > 0 && len(provider.Originator) > 0 && len(provider.Recipients) > 0
}
// ToCustomAlertProvider converts the provider into a custom.AlertProvider
// Reference doc for messagebird https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, _ *core.Result, resolved bool) *custom.AlertProvider {
var message string
if resolved {
message = fmt.Sprintf("RESOLVED: %s - %s", service.Name, alert.Description)
} else {
message = fmt.Sprintf("TRIGGERED: %s - %s", service.Name, alert.Description)
}
return &custom.AlertProvider{
URL: restAPIURL,
Method: http.MethodPost,
Body: fmt.Sprintf(`{
"originator": "%s",
"recipients": "%s",
"body": "%s"
}`, provider.Originator, provider.Recipients, message),
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": fmt.Sprintf("AccessKey %s", provider.AccessKey),
},
}
}

View File

@ -0,0 +1,53 @@
package messagebird
import (
"strings"
"testing"
"github.com/TwinProduction/gatus/core"
)
func TestMessagebirdAlertProvider_IsValid(t *testing.T) {
invalidProvider := AlertProvider{}
if invalidProvider.IsValid() {
t.Error("provider shouldn't have been valid")
}
validProvider := AlertProvider{
AccessKey: "1",
Originator: "1",
Recipients: "1",
}
if !validProvider.IsValid() {
t.Error("provider should've been valid")
}
}
func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) {
provider := AlertProvider{
AccessKey: "1",
Originator: "1",
Recipients: "1",
}
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{}, true)
if customAlertProvider == nil {
t.Error("customAlertProvider shouldn't have been nil")
}
if !strings.Contains(customAlertProvider.Body, "RESOLVED") {
t.Error("customAlertProvider.Body should've contained the substring RESOLVED")
}
}
func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) {
provider := AlertProvider{
AccessKey: "1",
Originator: "1",
Recipients: "1",
}
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{}, false)
if customAlertProvider == nil {
t.Error("customAlertProvider shouldn't have been nil")
}
if !strings.Contains(customAlertProvider.Body, "TRIGGERED") {
t.Error("customAlertProvider.Body should've contained the substring TRIGGERED")
}
}

View File

@ -2,6 +2,8 @@ package pagerduty
import ( import (
"fmt" "fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
) )
@ -19,7 +21,7 @@ func (provider *AlertProvider) IsValid() bool {
// ToCustomAlertProvider converts the provider into a custom.AlertProvider // ToCustomAlertProvider converts the provider into a custom.AlertProvider
// //
// relevant: https://developer.pagerduty.com/docs/events-api-v2/trigger-events/ // relevant: https://developer.pagerduty.com/docs/events-api-v2/trigger-events/
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider { func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, _ *core.Result, resolved bool) *custom.AlertProvider {
var message, eventAction, resolveKey string var message, eventAction, resolveKey string
if resolved { if resolved {
message = fmt.Sprintf("RESOLVED: %s - %s", service.Name, alert.Description) message = fmt.Sprintf("RESOLVED: %s - %s", service.Name, alert.Description)
@ -32,7 +34,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
} }
return &custom.AlertProvider{ return &custom.AlertProvider{
URL: "https://events.pagerduty.com/v2/enqueue", URL: "https://events.pagerduty.com/v2/enqueue",
Method: "POST", Method: http.MethodPost,
Body: fmt.Sprintf(`{ Body: fmt.Sprintf(`{
"routing_key": "%s", "routing_key": "%s",
"dedup_key": "%s", "dedup_key": "%s",

View File

@ -2,9 +2,10 @@ package provider
import ( import (
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
"github.com/TwinProduction/gatus/alerting/provider/pagerduty" "github.com/TwinProduction/gatus/alerting/provider/pagerduty"
"github.com/TwinProduction/gatus/alerting/provider/slack" "github.com/TwinProduction/gatus/alerting/provider/slack"
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
"github.com/TwinProduction/gatus/alerting/provider/twilio" "github.com/TwinProduction/gatus/alerting/provider/twilio"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
) )
@ -24,5 +25,6 @@ var (
_ AlertProvider = (*twilio.AlertProvider)(nil) _ AlertProvider = (*twilio.AlertProvider)(nil)
_ AlertProvider = (*slack.AlertProvider)(nil) _ AlertProvider = (*slack.AlertProvider)(nil)
_ AlertProvider = (*mattermost.AlertProvider)(nil) _ AlertProvider = (*mattermost.AlertProvider)(nil)
_ AlertProvider = (*messagebird.AlertProvider)(nil)
_ AlertProvider = (*pagerduty.AlertProvider)(nil) _ AlertProvider = (*pagerduty.AlertProvider)(nil)
) )

View File

@ -2,6 +2,7 @@ package slack
import ( import (
"fmt" "fmt"
"net/http"
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
@ -38,7 +39,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
} }
return &custom.AlertProvider{ return &custom.AlertProvider{
URL: provider.WebhookURL, URL: provider.WebhookURL,
Method: "POST", Method: http.MethodPost,
Body: fmt.Sprintf(`{ Body: fmt.Sprintf(`{
"text": "", "text": "",
"attachments": [ "attachments": [

View File

@ -3,9 +3,11 @@ package twilio
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http"
"net/url"
"github.com/TwinProduction/gatus/alerting/provider/custom" "github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
"net/url"
) )
// AlertProvider is the configuration necessary for sending an alert using Twilio // AlertProvider is the configuration necessary for sending an alert using Twilio
@ -22,7 +24,7 @@ func (provider *AlertProvider) IsValid() bool {
} }
// ToCustomAlertProvider converts the provider into a custom.AlertProvider // ToCustomAlertProvider converts the provider into a custom.AlertProvider
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider { func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, _ *core.Result, resolved bool) *custom.AlertProvider {
var message string var message string
if resolved { if resolved {
message = fmt.Sprintf("RESOLVED: %s - %s", service.Name, alert.Description) message = fmt.Sprintf("RESOLVED: %s - %s", service.Name, alert.Description)
@ -31,7 +33,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
} }
return &custom.AlertProvider{ return &custom.AlertProvider{
URL: fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", provider.SID), URL: fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", provider.SID),
Method: "POST", Method: http.MethodPost,
Body: url.Values{ Body: url.Values{
"To": {provider.To}, "To": {provider.To},
"From": {provider.From}, "From": {provider.From},

View File

@ -202,6 +202,7 @@ func validateAlertingConfig(config *Config) {
alertTypes := []core.AlertType{ alertTypes := []core.AlertType{
core.SlackAlert, core.SlackAlert,
core.MattermostAlert, core.MattermostAlert,
core.MessagebirdAlert,
core.TwilioAlert, core.TwilioAlert,
core.PagerDutyAlert, core.PagerDutyAlert,
core.CustomAlert, core.CustomAlert,
@ -238,6 +239,12 @@ func GetAlertingProviderByAlertType(config *Config, alertType core.AlertType) pr
return nil return nil
} }
return config.Alerting.Mattermost return config.Alerting.Mattermost
case core.MessagebirdAlert:
if config.Alerting.Messagebird == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Alerting.Messagebird
case core.TwilioAlert: case core.TwilioAlert:
if config.Alerting.Twilio == nil { if config.Alerting.Twilio == 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

View File

@ -366,6 +366,10 @@ alerting:
webhook-url: "http://example.com" webhook-url: "http://example.com"
pagerduty: pagerduty:
integration-key: "00000000000000000000000000000000" integration-key: "00000000000000000000000000000000"
messagebird:
access-key: "1"
originator: "31619191918"
recipients: "31619191919"
services: services:
- name: twinnation - name: twinnation
url: https://twinnation.org/health url: https://twinnation.org/health
@ -377,6 +381,8 @@ services:
failure-threshold: 7 failure-threshold: 7
success-threshold: 5 success-threshold: 5
description: "Healthcheck failed 7 times in a row" description: "Healthcheck failed 7 times in a row"
- type: messagebird
enabled: true
conditions: conditions:
- "[STATUS] == 200" - "[STATUS] == 200"
`)) `))
@ -401,9 +407,21 @@ services:
if config.Alerting.PagerDuty == nil || !config.Alerting.PagerDuty.IsValid() { if config.Alerting.PagerDuty == nil || !config.Alerting.PagerDuty.IsValid() {
t.Fatal("PagerDuty alerting config should've been valid") t.Fatal("PagerDuty alerting config should've been valid")
} }
if config.Alerting.Messagebird == nil || !config.Alerting.Messagebird.IsValid() {
t.Fatal("Messagebird alerting config should've been valid")
}
if config.Alerting.PagerDuty.IntegrationKey != "00000000000000000000000000000000" { if config.Alerting.PagerDuty.IntegrationKey != "00000000000000000000000000000000" {
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.Messagebird.AccessKey != "1" {
t.Errorf("Messagebird access key should've been %s, but was %s", "1", config.Alerting.Messagebird.AccessKey)
}
if config.Alerting.Messagebird.Originator != "31619191918" {
t.Errorf("Messagebird originator field should've been %s, but was %s", "31619191918", config.Alerting.Messagebird.Originator)
}
if config.Alerting.Messagebird.Recipients != "31619191919" {
t.Errorf("Messagebird to recipients should've been %s, but was %s", "31619191919", config.Alerting.Messagebird.Recipients)
}
if len(config.Services) != 1 { if len(config.Services) != 1 {
t.Error("There should've been 1 service") t.Error("There should've been 1 service")
} }
@ -416,8 +434,8 @@ services:
if config.Services[0].Alerts == nil { if config.Services[0].Alerts == nil {
t.Fatal("The service alerts shouldn't have been nil") t.Fatal("The service alerts shouldn't have been nil")
} }
if len(config.Services[0].Alerts) != 2 { if len(config.Services[0].Alerts) != 3 {
t.Fatal("There should've been 2 alert configured") t.Fatal("There should've been 3 alert configured")
} }
if !config.Services[0].Alerts[0].Enabled { if !config.Services[0].Alerts[0].Enabled {
t.Error("The alert should've been enabled") t.Error("The alert should've been enabled")
@ -443,6 +461,9 @@ services:
if config.Services[0].Alerts[1].Description != "Healthcheck failed 7 times in a row" { if config.Services[0].Alerts[1].Description != "Healthcheck failed 7 times in a row" {
t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Services[0].Alerts[0].Description) t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Services[0].Alerts[0].Description)
} }
if config.Services[0].Alerts[2].Type != core.MessagebirdAlert {
t.Errorf("The type of the alert should've been %s, but it was %s", core.MessagebirdAlert, config.Services[0].Alerts[1].Type)
}
} }
func TestParseAndValidateConfigBytesWithInvalidPagerDutyAlertingConfig(t *testing.T) { func TestParseAndValidateConfigBytesWithInvalidPagerDutyAlertingConfig(t *testing.T) {

View File

@ -40,6 +40,9 @@ const (
// MattermostAlert is the AlertType for the mattermost alerting provider // MattermostAlert is the AlertType for the mattermost alerting provider
MattermostAlert AlertType = "mattermost" MattermostAlert AlertType = "mattermost"
// MessagebirdAlert is the AlertType for the messagebird alerting provider
MessagebirdAlert AlertType = "messagebird"
// PagerDutyAlert is the AlertType for the pagerduty alerting provider // PagerDutyAlert is the AlertType for the pagerduty alerting provider
PagerDutyAlert AlertType = "pagerduty" PagerDutyAlert AlertType = "pagerduty"