2023-10-26 05:52:43 +02:00
|
|
|
package awsses
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-02-08 02:09:45 +01:00
|
|
|
"strings"
|
|
|
|
|
2023-10-26 05:52:43 +02:00
|
|
|
"github.com/TwiN/gatus/v5/alerting/alert"
|
2024-05-10 04:56:16 +02:00
|
|
|
"github.com/TwiN/gatus/v5/config/endpoint"
|
2023-10-26 05:52:43 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
|
|
"github.com/aws/aws-sdk-go/service/ses"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
CharSet = "UTF-8"
|
|
|
|
)
|
|
|
|
|
|
|
|
// AlertProvider is the configuration necessary for sending an alert using AWS Simple Email Service
|
|
|
|
type AlertProvider struct {
|
|
|
|
AccessKeyID string `yaml:"access-key-id"`
|
|
|
|
SecretAccessKey string `yaml:"secret-access-key"`
|
|
|
|
Region string `yaml:"region"`
|
|
|
|
|
|
|
|
From string `yaml:"from"`
|
|
|
|
To string `yaml:"to"`
|
|
|
|
|
|
|
|
// 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"`
|
|
|
|
To string `yaml:"to"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsValid returns whether the provider's configuration is valid
|
|
|
|
func (provider *AlertProvider) IsValid() bool {
|
|
|
|
registeredGroups := make(map[string]bool)
|
|
|
|
if provider.Overrides != nil {
|
|
|
|
for _, override := range provider.Overrides {
|
|
|
|
if isAlreadyRegistered := registeredGroups[override.Group]; isAlreadyRegistered || override.Group == "" || len(override.To) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
registeredGroups[override.Group] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate,
|
|
|
|
// otherwise if neither are specified, then we'll fall back on IAM authentication.
|
|
|
|
return len(provider.From) > 0 && len(provider.To) > 0 &&
|
|
|
|
((len(provider.AccessKeyID) == 0 && len(provider.SecretAccessKey) == 0) || (len(provider.AccessKeyID) > 0 && len(provider.SecretAccessKey) > 0))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send an alert using the provider
|
2024-05-10 04:56:16 +02:00
|
|
|
func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) error {
|
2024-04-02 03:47:14 +02:00
|
|
|
sess, err := provider.createSession()
|
2023-10-26 05:52:43 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
svc := ses.New(sess)
|
2024-05-10 04:56:16 +02:00
|
|
|
subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved)
|
|
|
|
emails := strings.Split(provider.getToForGroup(ep.Group), ",")
|
2023-10-26 05:52:43 +02:00
|
|
|
|
|
|
|
input := &ses.SendEmailInput{
|
|
|
|
Destination: &ses.Destination{
|
|
|
|
ToAddresses: aws.StringSlice(emails),
|
|
|
|
},
|
|
|
|
Message: &ses.Message{
|
|
|
|
Body: &ses.Body{
|
|
|
|
Text: &ses.Content{
|
|
|
|
Charset: aws.String(CharSet),
|
|
|
|
Data: aws.String(body),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Subject: &ses.Content{
|
|
|
|
Charset: aws.String(CharSet),
|
|
|
|
Data: aws.String(subject),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Source: aws.String(provider.From),
|
|
|
|
}
|
|
|
|
_, err = svc.SendEmail(input)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if aerr, ok := err.(awserr.Error); ok {
|
|
|
|
switch aerr.Code() {
|
|
|
|
case ses.ErrCodeMessageRejected:
|
|
|
|
fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
|
|
|
|
case ses.ErrCodeMailFromDomainNotVerifiedException:
|
|
|
|
fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
|
|
|
|
case ses.ErrCodeConfigurationSetDoesNotExistException:
|
|
|
|
fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
|
|
|
|
default:
|
|
|
|
fmt.Println(aerr.Error())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Print the error, cast err to awserr.Error to get the Code and
|
|
|
|
// Message from an error.
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildMessageSubjectAndBody builds the message subject and body
|
2024-05-10 04:56:16 +02:00
|
|
|
func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) {
|
2024-04-11 02:46:17 +02:00
|
|
|
var subject, message string
|
2023-10-26 05:52:43 +02:00
|
|
|
if resolved {
|
2024-05-10 04:56:16 +02:00
|
|
|
subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName())
|
|
|
|
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold)
|
2023-10-26 05:52:43 +02:00
|
|
|
} else {
|
2024-05-10 04:56:16 +02:00
|
|
|
subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName())
|
|
|
|
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold)
|
2023-10-26 05:52:43 +02:00
|
|
|
}
|
2024-04-11 02:46:17 +02:00
|
|
|
var formattedConditionResults string
|
|
|
|
if len(result.ConditionResults) > 0 {
|
|
|
|
formattedConditionResults = "\n\nCondition results:\n"
|
|
|
|
for _, conditionResult := range result.ConditionResults {
|
|
|
|
var prefix string
|
|
|
|
if conditionResult.Success {
|
|
|
|
prefix = "✅"
|
|
|
|
} else {
|
|
|
|
prefix = "❌"
|
|
|
|
}
|
|
|
|
formattedConditionResults += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition)
|
2023-10-26 05:52:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
var description string
|
|
|
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
|
|
|
description = "\n\nAlert description: " + alertDescription
|
|
|
|
}
|
2024-04-11 02:46:17 +02:00
|
|
|
return subject, message + description + formattedConditionResults
|
2023-10-26 05:52:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// getToForGroup returns the appropriate email integration to for a given group
|
|
|
|
func (provider *AlertProvider) getToForGroup(group string) string {
|
|
|
|
if provider.Overrides != nil {
|
|
|
|
for _, override := range provider.Overrides {
|
|
|
|
if group == override.Group {
|
|
|
|
return override.To
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return provider.To
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDefaultAlert returns the provider's default alert configuration
|
2024-02-08 02:09:45 +01:00
|
|
|
func (provider *AlertProvider) GetDefaultAlert() *alert.Alert {
|
2023-10-26 05:52:43 +02:00
|
|
|
return provider.DefaultAlert
|
|
|
|
}
|
|
|
|
|
2024-04-02 03:47:14 +02:00
|
|
|
func (provider *AlertProvider) createSession() (*session.Session, error) {
|
2023-10-26 05:52:43 +02:00
|
|
|
config := &aws.Config{
|
|
|
|
Region: aws.String(provider.Region),
|
|
|
|
}
|
|
|
|
if len(provider.AccessKeyID) > 0 && len(provider.SecretAccessKey) > 0 {
|
|
|
|
config.Credentials = credentials.NewStaticCredentials(provider.AccessKeyID, provider.SecretAccessKey, "")
|
|
|
|
}
|
|
|
|
return session.NewSession(config)
|
|
|
|
}
|