Skip to content

Commit 3b515b7

Browse files
UndeadDemidovSuperQsiavashs
authored
add Mattermost integration (#4090)
* add mattermost integration --------- Signed-off-by: UndeadDemidov <[email protected]> Signed-off-by: Ben Kochie <[email protected]> Co-authored-by: Ben Kochie <[email protected]> Co-authored-by: Siavash Safi <[email protected]>
1 parent 2a00f0a commit 3b515b7

File tree

10 files changed

+857
-2
lines changed

10 files changed

+857
-2
lines changed

asset/assets_vfsdata.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
298298
for _, cfg := range receiver.RocketchatConfigs {
299299
cfg.HTTPConfig.SetDirectory(baseDir)
300300
}
301+
for _, cfg := range receiver.MattermostConfigs {
302+
cfg.HTTPConfig.SetDirectory(baseDir)
303+
}
301304
}
302305
}
303306

@@ -637,6 +640,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error {
637640
rocketchat.TokenFile = c.Global.RocketchatTokenFile
638641
}
639642
}
643+
for _, mattermost := range rcv.MattermostConfigs {
644+
if mattermost.HTTPConfig == nil {
645+
mattermost.HTTPConfig = c.Global.HTTPConfig
646+
}
647+
}
640648

641649
names[rcv.Name] = struct{}{}
642650
}
@@ -1029,6 +1037,7 @@ type Receiver struct {
10291037
MSTeamsV2Configs []*MSTeamsV2Config `yaml:"msteamsv2_configs,omitempty" json:"msteamsv2_configs,omitempty"`
10301038
JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"`
10311039
RocketchatConfigs []*RocketchatConfig `yaml:"rocketchat_configs,omitempty" json:"rocketchat_configs,omitempty"`
1040+
MattermostConfigs []*MattermostConfig `yaml:"mattermost_configs,omitempty" json:"mattermost_configs,omitempty"`
10321041
}
10331042

10341043
// UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.

config/notifiers.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ var (
211211
Description: `{{ template "jira.default.description" . }}`,
212212
Priority: `{{ template "jira.default.priority" . }}`,
213213
}
214+
215+
DefaultMattermostConfig = MattermostConfig{
216+
NotifierConfig: NotifierConfig{
217+
VSendResolved: true,
218+
},
219+
Text: `{{ template "mattermost.default.text" . }}`,
220+
}
214221
)
215222

216223
// NotifierConfig contains base options common across all notifier configurations.
@@ -1072,3 +1079,98 @@ func (c *RocketchatConfig) UnmarshalYAML(unmarshal func(any) error) error {
10721079
}
10731080
return nil
10741081
}
1082+
1083+
// MattermostPriority defines the priority for a mattermost notification.
1084+
type MattermostPriority struct {
1085+
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
1086+
RequestedAck bool `yaml:"requested_ack,omitempty" json:"requested_ack,omitempty"`
1087+
PersistentNotifications bool `yaml:"persistent_notifications,omitempty" json:"persistent_notifications,omitempty"`
1088+
}
1089+
1090+
// MattermostProps defines additional properties for a mattermost notification.
1091+
// Only 'card' property takes effect now.
1092+
type MattermostProps struct {
1093+
Card string `yaml:"card,omitempty" json:"card,omitempty"`
1094+
}
1095+
1096+
// MattermostField configures a single Mattermost field for Slack compatibility.
1097+
// See https://developers.mattermost.com/integrate/reference/message-attachments/#fields for more information.
1098+
type MattermostField struct {
1099+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
1100+
Value string `yaml:"value,omitempty" json:"value,omitempty"`
1101+
Short *bool `yaml:"short,omitempty" json:"short,omitempty"`
1102+
}
1103+
1104+
// UnmarshalYAML implements the yaml.Unmarshaler interface for MattermostField.
1105+
func (c *MattermostField) UnmarshalYAML(unmarshal func(interface{}) error) error {
1106+
type plain MattermostField
1107+
if err := unmarshal((*plain)(c)); err != nil {
1108+
return err
1109+
}
1110+
if c.Title == "" {
1111+
return errors.New("missing title in Mattermost field configuration")
1112+
}
1113+
if c.Value == "" {
1114+
return errors.New("missing value in Mattermost field configuration")
1115+
}
1116+
return nil
1117+
}
1118+
1119+
// MattermostAttachment defines an attachment for a Mattermost notification.
1120+
// See https://developers.mattermost.com/integrate/reference/message-attachments/#fields for more information.
1121+
type MattermostAttachment struct {
1122+
Fallback string `yaml:"fallback,omitempty" json:"fallback,omitempty"`
1123+
Color string `yaml:"color,omitempty" json:"color,omitempty"`
1124+
Pretext string `yaml:"pretext,omitempty" json:"pretext,omitempty"`
1125+
Text string `yaml:"text,omitempty" json:"text,omitempty"`
1126+
AuthorName string `yaml:"author_name,omitempty" json:"author_name,omitempty"`
1127+
AuthorLink string `yaml:"author_link,omitempty" json:"author_link,omitempty"`
1128+
AuthorIcon string `yaml:"author_icon,omitempty" json:"author_icon,omitempty"`
1129+
Title string `yaml:"title,omitempty" json:"title,omitempty"`
1130+
TitleLink string `yaml:"title_link,omitempty" json:"title_link,omitempty"`
1131+
Fields []*MattermostField `yaml:"fields,omitempty" json:"fields,omitempty"`
1132+
ThumbURL string `yaml:"thumb_url,omitempty" json:"thumb_url,omitempty"`
1133+
Footer string `yaml:"footer,omitempty" json:"footer,omitempty"`
1134+
FooterIcon string `yaml:"footer_icon,omitempty" json:"footer_icon,omitempty"`
1135+
ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"`
1136+
}
1137+
1138+
// MattermostConfig configures notifications via Mattermost.
1139+
// See https://developers.mattermost.com/integrate/webhooks/incoming/ for more information.
1140+
type MattermostConfig struct {
1141+
NotifierConfig `yaml:",inline" json:",inline"`
1142+
1143+
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
1144+
WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"`
1145+
WebhookURLFile string `yaml:"webhook_url_file,omitempty" json:"webhook_url_file,omitempty"`
1146+
1147+
Channel string `yaml:"channel,omitempty" json:"channel,omitempty"`
1148+
Username string `yaml:"username,omitempty" json:"username,omitempty"`
1149+
1150+
Text string `yaml:"text,omitempty" json:"text,omitempty"`
1151+
IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"`
1152+
IconEmoji string `yaml:"icon_emoji,omitempty" json:"icon_emoji,omitempty"`
1153+
Attachments []*MattermostAttachment `yaml:"attachments,omitempty" json:"attachments,omitempty"`
1154+
Type string `yaml:"type,omitempty" json:"type,omitempty"`
1155+
Props *MattermostProps `yaml:"props,omitempty" json:"props,omitempty"`
1156+
Priority *MattermostPriority `yaml:"priority,omitempty" json:"priority,omitempty"`
1157+
}
1158+
1159+
// UnmarshalYAML implements the yaml.Unmarshaler interface.
1160+
func (c *MattermostConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
1161+
*c = DefaultMattermostConfig
1162+
type plain MattermostConfig
1163+
if err := unmarshal((*plain)(c)); err != nil {
1164+
return err
1165+
}
1166+
1167+
if c.WebhookURL == nil && c.WebhookURLFile == "" {
1168+
return errors.New("one of webhook_url or webhook_url_file must be configured")
1169+
}
1170+
1171+
if c.WebhookURL != nil && len(c.WebhookURLFile) > 0 {
1172+
return errors.New("at most one of webhook_url & webhook_url_file must be configured")
1173+
}
1174+
1175+
return nil
1176+
}

config/notifiers_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,3 +1113,112 @@ parse_mode: invalid
11131113
func newBoolPointer(b bool) *bool {
11141114
return &b
11151115
}
1116+
1117+
func TestMattermostField_UnmarshalYAML(t *testing.T) {
1118+
mf := []struct {
1119+
name string
1120+
in string
1121+
expected error
1122+
}{
1123+
{
1124+
name: "with title, value and short - it succeeds",
1125+
in: `
1126+
title: some title
1127+
value: some value
1128+
short: true
1129+
`,
1130+
},
1131+
{
1132+
name: "with title and value - it succeeds",
1133+
in: `
1134+
title: some title
1135+
value: some value
1136+
`,
1137+
},
1138+
{
1139+
name: "with no value - it fails",
1140+
in: `
1141+
title: some title
1142+
`,
1143+
expected: errors.New("missing value in Mattermost field configuration"),
1144+
},
1145+
{
1146+
name: "with no title - it fails",
1147+
in: `
1148+
value: some value
1149+
`,
1150+
expected: errors.New("missing title in Mattermost field configuration"),
1151+
},
1152+
}
1153+
1154+
for _, tt := range mf {
1155+
t.Run(tt.name, func(t *testing.T) {
1156+
var cfg MattermostField
1157+
err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)
1158+
1159+
require.Equal(t, tt.expected, err)
1160+
})
1161+
}
1162+
}
1163+
1164+
func TestMattermostConfig_UnmarshalYAML(t *testing.T) {
1165+
mc := []struct {
1166+
name string
1167+
in string
1168+
expected error
1169+
}{
1170+
{
1171+
name: "with url and text - it succeeds",
1172+
in: `
1173+
webhook_url: http://some.url
1174+
channel: some_channel
1175+
username: some_username
1176+
text: some text
1177+
`,
1178+
},
1179+
{
1180+
name: "with url_file, attachments and props - it succeeds",
1181+
in: `
1182+
webhook_url_file: /some/url.file
1183+
channel: some_channel
1184+
username: some_username
1185+
attachments:
1186+
- text: some text
1187+
props:
1188+
card: some text
1189+
`,
1190+
},
1191+
{
1192+
name: "with url and url_file - it fails",
1193+
in: `
1194+
webhook_url: http://some.url
1195+
webhook_url_file: /some/url.file
1196+
channel: some_channel
1197+
username: some_username
1198+
attachments:
1199+
- text: some text
1200+
`,
1201+
expected: errors.New("at most one of webhook_url & webhook_url_file must be configured"),
1202+
},
1203+
{
1204+
name: "with text and attachments - it succeeds",
1205+
in: `
1206+
webhook_url: http://some.url
1207+
channel: some_channel
1208+
username: some_username
1209+
text: some text
1210+
attachments:
1211+
- text: some text
1212+
`,
1213+
},
1214+
}
1215+
1216+
for _, tt := range mc {
1217+
t.Run(tt.name, func(t *testing.T) {
1218+
var cfg MattermostConfig
1219+
err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)
1220+
1221+
require.Equal(t, tt.expected, err)
1222+
})
1223+
}
1224+
}

config/receiver/receiver.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/prometheus/alertmanager/notify/email"
2626
"github.com/prometheus/alertmanager/notify/incidentio"
2727
"github.com/prometheus/alertmanager/notify/jira"
28+
"github.com/prometheus/alertmanager/notify/mattermost"
2829
"github.com/prometheus/alertmanager/notify/msteams"
2930
"github.com/prometheus/alertmanager/notify/msteamsv2"
3031
"github.com/prometheus/alertmanager/notify/opsgenie"
@@ -113,6 +114,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg
113114
for i, c := range nc.RocketchatConfigs {
114115
add("rocketchat", i, c, func(l *slog.Logger) (notify.Notifier, error) { return rocketchat.New(c, tmpl, l, httpOpts...) })
115116
}
117+
for i, c := range nc.MattermostConfigs {
118+
add("mattermost", i, c, func(l *slog.Logger) (notify.Notifier, error) { return mattermost.New(c, tmpl, l, httpOpts...) })
119+
}
116120

117121
if errs.Len() > 0 {
118122
return nil, &errs

0 commit comments

Comments
 (0)