diff --git a/config/notifiers.go b/config/notifiers.go index 0b941345..16fd6347 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -247,13 +247,16 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error } // SlackAction configures a single Slack action that is sent with each notification. -// Each action must contain a type, text, and url. -// See https://api.slack.com/docs/message-attachments#action_fields for more information. +// See https://api.slack.com/docs/message-attachments#action_fields and https://api.slack.com/docs/message-buttons +// for more information. type SlackAction struct { - Type string `yaml:"type,omitempty" json:"type,omitempty"` - Text string `yaml:"text,omitempty" json:"text,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` - Style string `yaml:"style,omitempty" json:"style,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + Text string `yaml:"text,omitempty" json:"text,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` + Style string `yaml:"style,omitempty" json:"style,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Value string `yaml:"value,omitempty" json:"value,omitempty"` + ConfirmField *SlackConfirmationField `yaml:"confirm,omitempty" json:"confirm,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for SlackAction. @@ -266,10 +269,39 @@ func (c *SlackAction) UnmarshalYAML(unmarshal func(interface{}) error) error { return fmt.Errorf("missing type in Slack action configuration") } if c.Text == "" { - return fmt.Errorf("missing value in Slack text configuration") + return fmt.Errorf("missing text in Slack action configuration") } - if c.URL == "" { - return fmt.Errorf("missing value in Slack url configuration") + if c.URL != "" { + // Clear all message action fields. + c.Name = "" + c.Value = "" + c.ConfirmField = nil + } else if c.Name != "" { + c.URL = "" + } else { + return fmt.Errorf("missing name or url in Slack action configuration") + } + return nil +} + +// SlackConfirmationField protect users from destructive actions or particularly distinguished decisions +// by asking them to confirm their button click one more time. +// See https://api.slack.com/docs/interactive-message-field-guide#confirmation_fields for more information. +type SlackConfirmationField struct { + Text string `yaml:"text,omitempty" json:"text,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + OkText string `yaml:"ok_text,omitempty" json:"ok_text,omitempty"` + DismissText string `yaml:"dismiss_text,omitempty" json:"dismiss_text,omitempty"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for SlackConfirmationField. +func (c *SlackConfirmationField) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain SlackConfirmationField + if err := unmarshal((*plain)(c)); err != nil { + return err + } + if c.Text == "" { + return fmt.Errorf("missing text in Slack confirmation configuration") } return nil } diff --git a/config/notifiers_test.go b/config/notifiers_test.go index 18dd0b40..a8e37bab 100644 --- a/config/notifiers_test.go +++ b/config/notifiers_test.go @@ -435,6 +435,89 @@ fields: } } +func TestSlackActionsValidation(t *testing.T) { + in := ` +actions: +- type: button + text: hello + url: https://localhost + style: danger +- type: button + text: hello + name: something + style: default + confirm: + title: please confirm + text: are you sure? + ok_text: yes + dismiss_text: no +` + expected := []*SlackAction{ + { + Type: "button", + Text: "hello", + URL: "https://localhost", + Style: "danger", + }, + { + Type: "button", + Text: "hello", + Name: "something", + Style: "default", + ConfirmField: &SlackConfirmationField{ + Title: "please confirm", + Text: "are you sure?", + OkText: "yes", + DismissText: "no", + }, + }, + } + + var cfg SlackConfig + err := yaml.UnmarshalStrict([]byte(in), &cfg) + if err != nil { + t.Fatalf("\nerror returned when none expected, error:\n%v", err) + } + + for index, action := range cfg.Actions { + exp := expected[index] + if action.Type != exp.Type { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Type, action.Type) + } + if action.Text != exp.Text { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Text, action.Text) + } + if action.URL != exp.URL { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.URL, action.URL) + } + if action.Style != exp.Style { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Style, action.Style) + } + if action.Name != exp.Name { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Name, action.Name) + } + if action.Value != exp.Value { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Value, action.Value) + } + if action.ConfirmField != nil && exp.ConfirmField == nil || action.ConfirmField == nil && exp.ConfirmField != nil { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField, action.ConfirmField) + } else if action.ConfirmField != nil && exp.ConfirmField != nil { + if action.ConfirmField.Title != exp.ConfirmField.Title { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.Title, action.ConfirmField.Title) + } + if action.ConfirmField.Text != exp.ConfirmField.Text { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.Text, action.ConfirmField.Text) + } + if action.ConfirmField.OkText != exp.ConfirmField.OkText { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.OkText, action.ConfirmField.OkText) + } + if action.ConfirmField.DismissText != exp.ConfirmField.DismissText { + t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.DismissText, action.ConfirmField.DismissText) + } + } + } +} + func newBoolPointer(b bool) *bool { return &b } diff --git a/notify/impl.go b/notify/impl.go index 8fad63fb..c018a73d 100644 --- a/notify/impl.go +++ b/notify/impl.go @@ -770,12 +770,25 @@ func (n *Slack) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { if numActions > 0 { var actions = make([]config.SlackAction, numActions) for index, action := range n.conf.Actions { - actions[index] = config.SlackAction{ + slackAction := config.SlackAction{ Type: tmplText(action.Type), Text: tmplText(action.Text), URL: tmplText(action.URL), Style: tmplText(action.Style), + Name: tmplText(action.Name), + Value: tmplText(action.Value), } + + if action.ConfirmField != nil { + slackAction.ConfirmField = &config.SlackConfirmationField{ + Title: tmplText(action.ConfirmField.Title), + Text: tmplText(action.ConfirmField.Text), + OkText: tmplText(action.ConfirmField.OkText), + DismissText: tmplText(action.ConfirmField.DismissText), + } + } + + actions[index] = slackAction } attachment.Actions = actions }