11package awsses
22
33import (
4+ "errors"
45 "fmt"
56 "strings"
67
78 "github.com/TwiN/gatus/v5/alerting/alert"
89 "github.com/TwiN/gatus/v5/config/endpoint"
10+ "github.com/TwiN/logr"
911 "github.com/aws/aws-sdk-go/aws"
1012 "github.com/aws/aws-sdk-go/aws/awserr"
1113 "github.com/aws/aws-sdk-go/aws/credentials"
1214 "github.com/aws/aws-sdk-go/aws/session"
1315 "github.com/aws/aws-sdk-go/service/ses"
16+ "gopkg.in/yaml.v3"
1417)
1518
1619const (
1720 CharSet = "UTF-8"
1821)
1922
20- // AlertProvider is the configuration necessary for sending an alert using AWS Simple Email Service
21- type AlertProvider struct {
23+ var (
24+ ErrDuplicateGroupOverride = errors .New ("duplicate group override" )
25+ ErrMissingFromOrToFields = errors .New ("from and to fields are required" )
26+ ErrInvalidAWSAuthConfig = errors .New ("either both or neither of access-key-id and secret-access-key must be specified" )
27+ )
28+
29+ type Config struct {
2230 AccessKeyID string `yaml:"access-key-id"`
2331 SecretAccessKey string `yaml:"secret-access-key"`
2432 Region string `yaml:"region"`
2533
2634 From string `yaml:"from"`
2735 To string `yaml:"to"`
36+ }
37+
38+ func (cfg * Config ) Validate () error {
39+ if len (cfg .From ) == 0 || len (cfg .To ) == 0 {
40+ return ErrMissingFromOrToFields
41+ }
42+ if ! ((len (cfg .AccessKeyID ) == 0 && len (cfg .SecretAccessKey ) == 0 ) || (len (cfg .AccessKeyID ) > 0 && len (cfg .SecretAccessKey ) > 0 )) {
43+ // if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate,
44+ // otherwise if neither are specified, then we'll fall back on IAM authentication.
45+ return ErrInvalidAWSAuthConfig
46+ }
47+ return nil
48+ }
49+
50+ func (cfg * Config ) Merge (override * Config ) {
51+ if len (override .AccessKeyID ) > 0 {
52+ cfg .AccessKeyID = override .AccessKeyID
53+ }
54+ if len (override .SecretAccessKey ) > 0 {
55+ cfg .SecretAccessKey = override .SecretAccessKey
56+ }
57+ if len (override .Region ) > 0 {
58+ cfg .Region = override .Region
59+ }
60+ if len (override .From ) > 0 {
61+ cfg .From = override .From
62+ }
63+ if len (override .To ) > 0 {
64+ cfg .To = override .To
65+ }
66+ }
67+
68+ // AlertProvider is the configuration necessary for sending an alert using AWS Simple Email Service
69+ type AlertProvider struct {
70+ DefaultConfig Config `yaml:",inline"`
2871
2972 // DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
3073 DefaultAlert * alert.Alert `yaml:"default-alert,omitempty"`
@@ -35,36 +78,37 @@ type AlertProvider struct {
3578
3679// Override is a case under which the default integration is overridden
3780type Override struct {
38- Group string `yaml:"group"`
39- To string `yaml:"to "`
81+ Group string `yaml:"group"`
82+ Config `yaml:",inline "`
4083}
4184
42- // IsValid returns whether the provider's configuration is valid
43- func (provider * AlertProvider ) IsValid () bool {
85+ // Validate the provider's configuration
86+ func (provider * AlertProvider ) Validate () error {
4487 registeredGroups := make (map [string ]bool )
4588 if provider .Overrides != nil {
4689 for _ , override := range provider .Overrides {
4790 if isAlreadyRegistered := registeredGroups [override .Group ]; isAlreadyRegistered || override .Group == "" || len (override .To ) == 0 {
48- return false
91+ return ErrDuplicateGroupOverride
4992 }
5093 registeredGroups [override .Group ] = true
5194 }
5295 }
53- // if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate,
54- // otherwise if neither are specified, then we'll fall back on IAM authentication.
55- return len (provider .From ) > 0 && len (provider .To ) > 0 &&
56- ((len (provider .AccessKeyID ) == 0 && len (provider .SecretAccessKey ) == 0 ) || (len (provider .AccessKeyID ) > 0 && len (provider .SecretAccessKey ) > 0 ))
96+ return provider .DefaultConfig .Validate ()
5797}
5898
5999// Send an alert using the provider
60100func (provider * AlertProvider ) Send (ep * endpoint.Endpoint , alert * alert.Alert , result * endpoint.Result , resolved bool ) error {
61- sess , err := provider .createSession ( )
101+ cfg , err := provider .GetConfig ( ep . Group , alert )
62102 if err != nil {
63103 return err
64104 }
65- svc := ses .New (sess )
105+ awsSession , err := provider .createSession (cfg )
106+ if err != nil {
107+ return err
108+ }
109+ svc := ses .New (awsSession )
66110 subject , body := provider .buildMessageSubjectAndBody (ep , alert , result , resolved )
67- emails := strings .Split (provider . getToForGroup ( ep . Group ) , "," )
111+ emails := strings .Split (cfg . To , "," )
68112
69113 input := & ses.SendEmailInput {
70114 Destination : & ses.Destination {
@@ -82,33 +126,41 @@ func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, r
82126 Data : aws .String (subject ),
83127 },
84128 },
85- Source : aws .String (provider .From ),
129+ Source : aws .String (cfg .From ),
86130 }
87- _ , err = svc .SendEmail (input )
88-
89- if err != nil {
131+ if _ , err = svc .SendEmail (input ); err != nil {
90132 if aerr , ok := err .(awserr.Error ); ok {
91133 switch aerr .Code () {
92134 case ses .ErrCodeMessageRejected :
93- fmt . Println (ses .ErrCodeMessageRejected , aerr .Error ())
135+ logr . Error (ses .ErrCodeMessageRejected + ": " + aerr .Error ())
94136 case ses .ErrCodeMailFromDomainNotVerifiedException :
95- fmt . Println (ses .ErrCodeMailFromDomainNotVerifiedException , aerr .Error ())
137+ logr . Error (ses .ErrCodeMailFromDomainNotVerifiedException + ": " + aerr .Error ())
96138 case ses .ErrCodeConfigurationSetDoesNotExistException :
97- fmt . Println (ses .ErrCodeConfigurationSetDoesNotExistException , aerr .Error ())
139+ logr . Error (ses .ErrCodeConfigurationSetDoesNotExistException + ": " + aerr .Error ())
98140 default :
99- fmt . Println (aerr .Error ())
141+ logr . Error (aerr .Error ())
100142 }
101143 } else {
102144 // Print the error, cast err to awserr.Error to get the Code and
103145 // Message from an error.
104- fmt . Println (err .Error ())
146+ logr . Error (err .Error ())
105147 }
106148
107149 return err
108150 }
109151 return nil
110152}
111153
154+ func (provider * AlertProvider ) createSession (cfg * Config ) (* session.Session , error ) {
155+ awsConfig := & aws.Config {
156+ Region : aws .String (cfg .Region ),
157+ }
158+ if len (cfg .AccessKeyID ) > 0 && len (cfg .SecretAccessKey ) > 0 {
159+ awsConfig .Credentials = credentials .NewStaticCredentials (cfg .AccessKeyID , cfg .SecretAccessKey , "" )
160+ }
161+ return session .NewSession (awsConfig )
162+ }
163+
112164// buildMessageSubjectAndBody builds the message subject and body
113165func (provider * AlertProvider ) buildMessageSubjectAndBody (ep * endpoint.Endpoint , alert * alert.Alert , result * endpoint.Result , resolved bool ) (string , string ) {
114166 var subject , message string
@@ -139,29 +191,38 @@ func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint,
139191 return subject , message + description + formattedConditionResults
140192}
141193
142- // getToForGroup returns the appropriate email integration to for a given group
143- func (provider * AlertProvider ) getToForGroup (group string ) string {
194+ // GetDefaultAlert returns the provider's default alert configuration
195+ func (provider * AlertProvider ) GetDefaultAlert () * alert.Alert {
196+ return provider .DefaultAlert
197+ }
198+
199+ // GetConfig returns the configuration for the provider with the overrides applied
200+ func (provider * AlertProvider ) GetConfig (group string , alert * alert.Alert ) (* Config , error ) {
201+ cfg := provider .DefaultConfig
202+ // Handle group overrides
144203 if provider .Overrides != nil {
145204 for _ , override := range provider .Overrides {
146205 if group == override .Group {
147- return override .To
206+ cfg .Merge (& override .Config )
207+ break
148208 }
149209 }
150210 }
151- return provider .To
152- }
153-
154- // GetDefaultAlert returns the provider's default alert configuration
155- func (provider * AlertProvider ) GetDefaultAlert () * alert.Alert {
156- return provider .DefaultAlert
211+ // Handle alert overrides
212+ if len (alert .ProviderOverride ) != 0 {
213+ overrideConfig := Config {}
214+ if err := yaml .Unmarshal (alert .ProviderOverrideAsBytes (), & overrideConfig ); err != nil {
215+ return nil , err
216+ }
217+ cfg .Merge (& overrideConfig )
218+ }
219+ // Validate the configuration
220+ err := cfg .Validate ()
221+ return & cfg , err
157222}
158223
159- func (provider * AlertProvider ) createSession () (* session.Session , error ) {
160- config := & aws.Config {
161- Region : aws .String (provider .Region ),
162- }
163- if len (provider .AccessKeyID ) > 0 && len (provider .SecretAccessKey ) > 0 {
164- config .Credentials = credentials .NewStaticCredentials (provider .AccessKeyID , provider .SecretAccessKey , "" )
165- }
166- return session .NewSession (config )
224+ // ValidateOverrides validates the alert's provider override and, if present, the group override
225+ func (provider * AlertProvider ) ValidateOverrides (group string , alert * alert.Alert ) error {
226+ _ , err := provider .GetConfig (group , alert )
227+ return err
167228}
0 commit comments