Skip to content

Commit 603a728

Browse files
authored
Return error when TmplText errors in sns notifier (prometheus#3531)
Signed-off-by: Emmanuel Lodovice <[email protected]>
1 parent b59669f commit 603a728

File tree

2 files changed

+136
-8
lines changed

2 files changed

+136
-8
lines changed

notify/sns/sns.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/aws/aws-sdk-go/service/sns"
3030
"github.com/go-kit/log"
3131
"github.com/go-kit/log/level"
32+
"github.com/pkg/errors"
3233
commoncfg "github.com/prometheus/common/config"
3334

3435
"github.com/prometheus/alertmanager/config"
@@ -63,12 +64,12 @@ func New(c *config.SNSConfig, t *template.Template, l log.Logger, httpOpts ...co
6364

6465
func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, error) {
6566
var (
66-
err error
67-
data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger)
68-
tmpl = notify.TmplText(n.tmpl, data, &err)
67+
tmplErr error
68+
data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger)
69+
tmpl = notify.TmplText(n.tmpl, data, &tmplErr)
6970
)
7071

71-
client, err := n.createSNSClient(tmpl)
72+
client, err := n.createSNSClient(tmpl, &tmplErr)
7273
if err != nil {
7374
var e awserr.RequestFailure
7475
if errors.As(err, &e) {
@@ -77,7 +78,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
7778
return true, err
7879
}
7980

80-
publishInput, err := n.createPublishInput(ctx, tmpl)
81+
publishInput, err := n.createPublishInput(ctx, tmpl, &tmplErr)
8182
if err != nil {
8283
return true, err
8384
}
@@ -99,7 +100,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err
99100
return false, nil
100101
}
101102

102-
func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
103+
func (n *Notifier) createSNSClient(tmpl func(string) string, tmplErr *error) (*sns.SNS, error) {
103104
var creds *credentials.Credentials
104105
// If there are provided sigV4 credentials we want to use those to create a session.
105106
if n.conf.Sigv4.AccessKey != "" && n.conf.Sigv4.SecretKey != "" {
@@ -115,6 +116,9 @@ func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
115116
if err != nil {
116117
return nil, err
117118
}
119+
if *tmplErr != nil {
120+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'api_url' template"))
121+
}
118122

119123
if n.conf.Sigv4.RoleARN != "" {
120124
var stsSess *session.Session
@@ -144,13 +148,19 @@ func (n *Notifier) createSNSClient(tmpl func(string) string) (*sns.SNS, error) {
144148
return client, nil
145149
}
146150

147-
func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) string) (*sns.PublishInput, error) {
151+
func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) string, tmplErr *error) (*sns.PublishInput, error) {
148152
publishInput := &sns.PublishInput{}
149153
messageAttributes := n.createMessageAttributes(tmpl)
154+
if *tmplErr != nil {
155+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'attributes' template"))
156+
}
150157
// Max message size for a message in a SNS publish request is 256KB, except for SMS messages where the limit is 1600 characters/runes.
151158
messageSizeLimit := 256 * 1024
152159
if n.conf.TopicARN != "" {
153160
topicARN := tmpl(n.conf.TopicARN)
161+
if *tmplErr != nil {
162+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'topic_arn' template"))
163+
}
154164
publishInput.SetTopicArn(topicARN)
155165
// If we are using a topic ARN, it could be a FIFO topic specified by the topic's suffix ".fifo".
156166
if strings.HasSuffix(topicARN, ".fifo") {
@@ -165,14 +175,24 @@ func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) str
165175
}
166176
if n.conf.PhoneNumber != "" {
167177
publishInput.SetPhoneNumber(tmpl(n.conf.PhoneNumber))
178+
if *tmplErr != nil {
179+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'phone_number' template"))
180+
}
168181
// If we have an SMS message, we need to truncate to 1600 characters/runes.
169182
messageSizeLimit = 1600
170183
}
171184
if n.conf.TargetARN != "" {
172185
publishInput.SetTargetArn(tmpl(n.conf.TargetARN))
186+
if *tmplErr != nil {
187+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'target_arn' template"))
188+
}
173189
}
174190

175-
messageToSend, isTrunc, err := validateAndTruncateMessage(tmpl(n.conf.Message), messageSizeLimit)
191+
tmplMessage := tmpl(n.conf.Message)
192+
if *tmplErr != nil {
193+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'message' template"))
194+
}
195+
messageToSend, isTrunc, err := validateAndTruncateMessage(tmplMessage, messageSizeLimit)
176196
if err != nil {
177197
return nil, err
178198
}
@@ -186,6 +206,9 @@ func (n *Notifier) createPublishInput(ctx context.Context, tmpl func(string) str
186206

187207
if n.conf.Subject != "" {
188208
publishInput.SetSubject(tmpl(n.conf.Subject))
209+
if *tmplErr != nil {
210+
return nil, notify.NewErrorWithReason(notify.ClientErrorReason, errors.Wrap(*tmplErr, "execute 'subject' template"))
211+
}
189212
}
190213

191214
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)