Skip to content

Commit cecfe5b

Browse files
taharahstuartnelson3
authored andcommitted
Validate Slack field config and only allow the necessary input (#1334)
Signed-off-by: Trevor Wood <[email protected]>
1 parent cfde256 commit cecfe5b

File tree

3 files changed

+175
-31
lines changed

3 files changed

+175
-31
lines changed

config/notifiers.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,31 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
220220
return nil
221221
}
222222

223+
// SlackField configures a single Slack field that is sent with each notification.
224+
// Each field must contain a title, value, and optionally, a boolean value to indicate if the field
225+
// is short enough to be displayed next to other fields designated as short.
226+
// See https://api.slack.com/docs/message-attachments#fields for more information.
227+
type SlackField struct {
228+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
229+
Value string `yaml:"value,omitempty" json:"value,omitempty"`
230+
Short *bool `yaml:"short,omitempty" json:"short,omitempty"`
231+
}
232+
233+
// UnmarshalYAML implements the yaml.Unmarshaler interface for SlackField.
234+
func (c *SlackField) UnmarshalYAML(unmarshal func(interface{}) error) error {
235+
type plain SlackField
236+
if err := unmarshal((*plain)(c)); err != nil {
237+
return err
238+
}
239+
if c.Title == "" {
240+
return fmt.Errorf("missing title in Slack field configuration")
241+
}
242+
if c.Value == "" {
243+
return fmt.Errorf("missing value in Slack field configuration")
244+
}
245+
return nil
246+
}
247+
223248
// SlackConfig configures notifications via Slack.
224249
type SlackConfig struct {
225250
NotifierConfig `yaml:",inline" json:",inline"`
@@ -233,17 +258,17 @@ type SlackConfig struct {
233258
Username string `yaml:"username,omitempty" json:"username,omitempty"`
234259
Color string `yaml:"color,omitempty" json:"color,omitempty"`
235260

236-
Title string `yaml:"title,omitempty" json:"title,omitempty"`
237-
TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"`
238-
Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"`
239-
Text string `yaml:"text,omitempty" json:"text,omitempty"`
240-
Fields []map[string]string `yaml:"fields,omitempty" json:"fields,omitempty"`
241-
ShortFields bool `yaml:"short_fields,omitempty" json:"short_fields,omitempty"`
242-
Footer string `yaml:"footer,omitempty" json:"footer,omitempty"`
243-
Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"`
244-
IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"`
245-
IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"`
246-
LinkNames bool `yaml:"link_names,omitempty" json:"link_names,omitempty"`
261+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
262+
TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"`
263+
Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"`
264+
Text string `yaml:"text,omitempty" json:"text,omitempty"`
265+
Fields []*SlackField `yaml:"fields,omitempty" json:"fields,omitempty"`
266+
ShortFields bool `yaml:"short_fields,omitempty" json:"short_fields,omitempty"`
267+
Footer string `yaml:"footer,omitempty" json:"footer,omitempty"`
268+
Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"`
269+
IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"`
270+
IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"`
271+
LinkNames bool `yaml:"link_names,omitempty" json:"link_names,omitempty"`
247272
}
248273

249274
// UnmarshalYAML implements the yaml.Unmarshaler interface.

config/notifiers_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,120 @@ token: ''
253253
t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
254254
}
255255
}
256+
257+
func TestSlackFieldConfigValidation(t *testing.T) {
258+
var tests = []struct {
259+
in string
260+
expected string
261+
}{
262+
{
263+
in: `
264+
fields:
265+
- title: first
266+
value: hello
267+
- title: second
268+
`,
269+
expected: "missing value in Slack field configuration",
270+
},
271+
{
272+
in: `
273+
fields:
274+
- title: first
275+
value: hello
276+
short: true
277+
- value: world
278+
short: true
279+
`,
280+
expected: "missing title in Slack field configuration",
281+
},
282+
{
283+
in: `
284+
fields:
285+
- title: first
286+
value: hello
287+
short: true
288+
- title: second
289+
value: world
290+
`,
291+
expected: "",
292+
},
293+
}
294+
295+
for _, rt := range tests {
296+
var cfg SlackConfig
297+
err := yaml.UnmarshalStrict([]byte(rt.in), &cfg)
298+
299+
// Check if an error occurred when it was NOT expected to.
300+
if rt.expected == "" && err != nil {
301+
t.Fatalf("\nerror returned when none expected, error:\n%v", err)
302+
}
303+
// Check that an error occurred if one was expected to.
304+
if rt.expected != "" && err == nil {
305+
t.Fatalf("\nno error returned, expected:\n%v", rt.expected)
306+
}
307+
// Check that the error that occurred was what was expected.
308+
if err != nil && err.Error() != rt.expected {
309+
t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected, err.Error())
310+
}
311+
}
312+
}
313+
314+
func TestSlackFieldConfigUnmarshalling(t *testing.T) {
315+
in := `
316+
fields:
317+
- title: first
318+
value: hello
319+
short: true
320+
- title: second
321+
value: world
322+
- title: third
323+
value: slack field test
324+
short: false
325+
`
326+
expected := []*SlackField{
327+
&SlackField{
328+
Title: "first",
329+
Value: "hello",
330+
Short: newBoolPointer(true),
331+
},
332+
&SlackField{
333+
Title: "second",
334+
Value: "world",
335+
Short: nil,
336+
},
337+
&SlackField{
338+
Title: "third",
339+
Value: "slack field test",
340+
Short: newBoolPointer(false),
341+
},
342+
}
343+
344+
var cfg SlackConfig
345+
err := yaml.UnmarshalStrict([]byte(in), &cfg)
346+
if err != nil {
347+
t.Fatalf("\nerror returned when none expected, error:\n%v", err)
348+
}
349+
350+
for index, field := range cfg.Fields {
351+
exp := expected[index]
352+
if field.Title != exp.Title {
353+
t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Title, field.Title)
354+
}
355+
if field.Value != exp.Value {
356+
t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Value, field.Value)
357+
}
358+
if exp.Short == nil && field.Short != nil {
359+
t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Short, *field.Short)
360+
}
361+
if exp.Short != nil && field.Short == nil {
362+
t.Errorf("\nexpected:\n%v\ngot:\n%v", *exp.Short, field.Short)
363+
}
364+
if exp.Short != nil && *exp.Short != *field.Short {
365+
t.Errorf("\nexpected:\n%v\ngot:\n%v", *exp.Short, *field.Short)
366+
}
367+
}
368+
}
369+
370+
func newBoolPointer(b bool) *bool {
371+
return &b
372+
}

notify/impl.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -638,25 +638,18 @@ type slackReq struct {
638638

639639
// slackAttachment is used to display a richly-formatted message block.
640640
type slackAttachment struct {
641-
Title string `json:"title,omitempty"`
642-
TitleLink string `json:"title_link,omitempty"`
643-
Pretext string `json:"pretext,omitempty"`
644-
Text string `json:"text"`
645-
Fallback string `json:"fallback"`
646-
Fields []slackAttachmentField `json:"fields"`
647-
Footer string `json:"footer"`
641+
Title string `json:"title,omitempty"`
642+
TitleLink string `json:"title_link,omitempty"`
643+
Pretext string `json:"pretext,omitempty"`
644+
Text string `json:"text"`
645+
Fallback string `json:"fallback"`
646+
Fields []config.SlackField `json:"fields,omitempty"`
647+
Footer string `json:"footer"`
648648

649649
Color string `json:"color,omitempty"`
650650
MrkdwnIn []string `json:"mrkdwn_in,omitempty"`
651651
}
652652

653-
// slackAttachmentField is displayed in a table inside the message attachment.
654-
type slackAttachmentField struct {
655-
Title string `json:"title"`
656-
Value string `json:"value"`
657-
Short bool `json:"short,omitempty"`
658-
}
659-
660653
// Notify implements the Notifier interface.
661654
func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
662655
var err error
@@ -678,12 +671,21 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
678671

679672
var numFields = len(n.conf.Fields)
680673
if numFields > 0 {
681-
var fields = make([]slackAttachmentField, numFields)
682-
for k, v := range n.conf.Fields {
683-
fields[k] = slackAttachmentField{
684-
tmplText(v["title"]),
685-
tmplText(v["value"]),
686-
n.conf.ShortFields,
674+
var fields = make([]config.SlackField, numFields)
675+
for index, field := range n.conf.Fields {
676+
// Check if short was defined for the field otherwise fallback to the global setting
677+
var short bool
678+
if field.Short != nil {
679+
short = *field.Short
680+
} else {
681+
short = n.conf.ShortFields
682+
}
683+
684+
// Rebuild the field by executing any templates and setting the new value for short
685+
fields[index] = config.SlackField{
686+
Title: tmplText(field.Title),
687+
Value: tmplText(field.Value),
688+
Short: &short,
687689
}
688690
}
689691
attachment.Fields = fields

0 commit comments

Comments
 (0)