Skip to content

Commit bef57ab

Browse files
emanlodoviceTheMeier
authored andcommitted
Return error when TmplText errors in sns notifier (prometheus#3879)
* Return error when TmplText errors in sns notifier Signed-off-by: Emmanuel Lodovice <[email protected]> * Use fmt.Errorf instead of errors.Wrap Signed-off-by: Emmanuel Lodovice <[email protected]> --------- Signed-off-by: Emmanuel Lodovice <[email protected]>
1 parent 7fc1516 commit bef57ab

File tree

2 files changed

+135
-8
lines changed

2 files changed

+135
-8
lines changed

notify/sns/sns.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ func New(c *config.SNSConfig, t *template.Template, l log.Logger, httpOpts ...co
6363

6464
func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, error) {
6565
var (
66-
err error
67-
data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger)
68-
tmpl = notify.TmplText(n.tmpl, data, &err)
66+
tmplErr error
67+
data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger)
68+
tmpl = notify.TmplText(n.tmpl, data, &tmplErr)
6969
)
7070

71-
client, err := n.createSNSClient(tmpl)
71+
client, err := n.createSNSClient(tmpl, &tmplErr)
7272
if err != nil {
7373
var e awserr.RequestFailure
7474
if errors.As(err, &e) {
@@ -77,7 +77,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
7777
return true, err
7878
}
7979

80-
publishInput, err := n.createPublishInput(ctx, tmpl)
80+
publishInput, err := n.createPublishInput(ctx, tmpl, &tmplErr)
8181
if err != nil {
8282
return true, err
8383
}
@@ -99,7 +99,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
9999
return false, nil
100100
}
101101

102-
func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
102+
func (n *Notifier) createSNSClient(tmpl func(string) string, tmplErr *error) (*sns.SNS, error) {
103103
var creds *credentials.Credentials
104104
// If there are provided sigV4 credentials we want to use those to create a session.
105105
if n.conf.Sigv4.AccessKey != "" && n.conf.Sigv4.SecretKey != "" {
@@ -115,6 +115,9 @@ func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
115115
if err != nil {
116116
return nil, err
117117
}
118+
if *tmplErr != nil {
119+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'api_url' template: %w", *tmplErr))
120+
}
118121

119122
if n.conf.Sigv4.RoleARN != "" {
120123
var stsSess *session.Session
@@ -144,13 +147,19 @@ func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
144147
return client, nil
145148
}
146149

147-
func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) string) (*sns.PublishInput, error) {
150+
func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) string, tmplErr *error) (*sns.PublishInput, error) {
148151
publishInput := &sns.PublishInput{}
149152
messageAttributes := n.createMessageAttributes(tmpl)
153+
if *tmplErr != nil {
154+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'attributes' template: %w", *tmplErr))
155+
}
150156
// Max message size for a message in a SNS publish request is 256KB, except for SMS messages where the limit is 1600 characters/runes.
151157
messageSizeLimit := 256 * 1024
152158
if n.conf.TopicARN != "" {
153159
topicARN := tmpl(n.conf.TopicARN)
160+
if *tmplErr != nil {
161+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'topic_arn' template: %w", *tmplErr))
162+
}
154163
publishInput.SetTopicArn(topicARN)
155164
// If we are using a topic ARN, it could be a FIFO topic specified by the topic's suffix ".fifo".
156165
if strings.HasSuffix(topicARN, ".fifo") {
@@ -165,14 +174,24 @@ func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) str
165174
}
166175
if n.conf.PhoneNumber != "" {
167176
publishInput.SetPhoneNumber(tmpl(n.conf.PhoneNumber))
177+
if *tmplErr != nil {
178+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'phone_number' template: %w", *tmplErr))
179+
}
168180
// If we have an SMS message, we need to truncate to 1600 characters/runes.
169181
messageSizeLimit = 1600
170182
}
171183
if n.conf.TargetARN != "" {
172184
publishInput.SetTargetArn(tmpl(n.conf.TargetARN))
185+
if *tmplErr != nil {
186+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'target_arn' template: %w", *tmplErr))
187+
}
173188
}
174189

175-
messageToSend, isTrunc, err := validateAndTruncateMessage(tmpl(n.conf.Message), messageSizeLimit)
190+
tmplMessage := tmpl(n.conf.Message)
191+
if *tmplErr != nil {
192+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'message' template: %w", *tmplErr))
193+
}
194+
messageToSend, isTrunc, err := validateAndTruncateMessage(tmplMessage, messageSizeLimit)
176195
if err != nil {
177196
return nil, err
178197
}
@@ -186,6 +205,9 @@ func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) str
186205

187206
if n.conf.Subject != "" {
188207
publishInput.SetSubject(tmpl(n.conf.Subject))
208+
if *tmplErr != nil {
209+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, fmt.Errorf("execute 'subject' template: %w", *tmplErr))
210+
}
189211
}
190212

191213
return publishInput, nil

notify/sns/sns_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,23 @@
1414
package sns
1515

1616
import (
17+
"context"
18+
"net/url"
19+
"strings"
1720
"testing"
1821

22+
"github.com/go-kit/log"
23+
commoncfg "github.com/prometheus/common/config"
24+
"github.com/prometheus/common/sigv4"
1925
"github.com/stretchr/testify/require"
26+
27+
"github.com/prometheus/alertmanager/config"
28+
"github.com/prometheus/alertmanager/template"
29+
"github.com/prometheus/alertmanager/types"
2030
)
2131

32+
var logger = log.NewNopLogger()
33+
2234
func TestValidateAndTruncateMessage(t *testing.T) {
2335
sBuff := make([]byte, 257*1024)
2436
for i := range sBuff {
@@ -43,3 +55,96 @@ func TestValidateAndTruncateMessage(t *testing.T) {
4355
_, _, err = validateAndTruncateMessage(invalidUtf8String, 100)
4456
require.Error(t, err)
4557
}
58+
59+
func TestNotifyWithInvalidTemplate(t *testing.T) {
60+
for _, tc := range []struct {
61+
title string
62+
errMsg string
63+
updateCfg func(*config.SNSConfig)
64+
}{
65+
{
66+
title: "with invalid Attribute template",
67+
errMsg: "execute 'attributes' template",
68+
updateCfg: func(cfg *config.SNSConfig) {
69+
cfg.Attributes = map[string]string{
70+
"attribName1": "{{ template \"unknown_template\" . }}",
71+
}
72+
},
73+
},
74+
{
75+
title: "with invalid TopicArn template",
76+
errMsg: "execute 'topic_arn' template",
77+
updateCfg: func(cfg *config.SNSConfig) {
78+
cfg.TopicARN = "{{ template \"unknown_template\" . }}"
79+
},
80+
},
81+
{
82+
title: "with invalid PhoneNumber template",
83+
errMsg: "execute 'phone_number' template",
84+
updateCfg: func(cfg *config.SNSConfig) {
85+
cfg.PhoneNumber = "{{ template \"unknown_template\" . }}"
86+
},
87+
},
88+
{
89+
title: "with invalid Message template",
90+
errMsg: "execute 'message' template",
91+
updateCfg: func(cfg *config.SNSConfig) {
92+
cfg.Message = "{{ template \"unknown_template\" . }}"
93+
},
94+
},
95+
{
96+
title: "with invalid Subject template",
97+
errMsg: "execute 'subject' template",
98+
updateCfg: func(cfg *config.SNSConfig) {
99+
cfg.Subject = "{{ template \"unknown_template\" . }}"
100+
},
101+
},
102+
{
103+
title: "with invalid APIUrl template",
104+
errMsg: "execute 'api_url' template",
105+
updateCfg: func(cfg *config.SNSConfig) {
106+
cfg.APIUrl = "{{ template \"unknown_template\" . }}"
107+
},
108+
},
109+
{
110+
title: "with invalid TargetARN template",
111+
errMsg: "execute 'target_arn' template",
112+
updateCfg: func(cfg *config.SNSConfig) {
113+
cfg.TargetARN = "{{ template \"unknown_template\" . }}"
114+
},
115+
},
116+
} {
117+
tc := tc
118+
t.Run(tc.title, func(t *testing.T) {
119+
snsCfg := &config.SNSConfig{
120+
HTTPConfig: &commoncfg.HTTPClientConfig{},
121+
TopicARN: "TestTopic",
122+
Sigv4: sigv4.SigV4Config{
123+
Region: "us-west-2",
124+
},
125+
}
126+
if tc.updateCfg != nil {
127+
tc.updateCfg(snsCfg)
128+
}
129+
notifier, err := New(
130+
snsCfg,
131+
createTmpl(t),
132+
logger,
133+
)
134+
require.NoError(t, err)
135+
var alerts []*types.Alert
136+
_, err = notifier.Notify(context.Background(), alerts...)
137+
require.Error(t, err)
138+
require.True(t, strings.Contains(err.Error(), "template \"unknown_template\" not defined"))
139+
require.True(t, strings.Contains(err.Error(), tc.errMsg))
140+
})
141+
}
142+
}
143+
144+
// CreateTmpl returns a ready-to-use template.
145+
func createTmpl(t *testing.T) *template.Template {
146+
tmpl, err := template.FromGlobs([]string{})
147+
require.NoError(t, err)
148+
tmpl.ExternalURL, _ = url.Parse("http://am")
149+
return tmpl
150+
}

0 commit comments

Comments
 (0)