diff --git a/config/notifiers.go b/config/notifiers.go index 87f806aa27..5b5d90b50b 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -872,6 +872,7 @@ type MSTeamsV2Config struct { Title string `yaml:"title,omitempty" json:"title,omitempty"` Text string `yaml:"text,omitempty" json:"text,omitempty"` + Card string `yaml:"card,omitempty" json:"card,omitempty"` } func (c *MSTeamsV2Config) UnmarshalYAML(unmarshal func(interface{}) error) error { diff --git a/docs/configuration.md b/docs/configuration.md index 01b16a8868..594df1dd10 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -992,6 +992,11 @@ Microsoft Teams v2 notifications using the new message format with adaptive card # Message body template. [ text: | default = '{{ template "msteamsv2.default.text" . }}' ] +# Message body card. +# If not null, it will override title and text values (no need to configure these values) +# You can find a complete sample file template here 'examples/msteamsv2/card.tmpl' and provide '{{ template "msteams.card" . }}' in the card value to test. +[ card: ] + # The HTTP client's configuration. [ http_config: | default = global.http_config ] ``` diff --git a/examples/msteamsv2/card.tmpl b/examples/msteamsv2/card.tmpl new file mode 100644 index 0000000000..af086213a9 --- /dev/null +++ b/examples/msteamsv2/card.tmpl @@ -0,0 +1,75 @@ +{{ define "msteams.card" }} +{ + "Type": "message", + "Attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.4", + "msteams": { + "width": "Full" + }, + "body": [ + { + "type": "ColumnSet", + "style": "{{ if eq .Status "firing" }}attention{{ else if eq .Status "resolved" }}good{{ else }}warning{{ end }}", + "columns": [ + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "weight": "Bolder", + "size": "ExtraLarge", + "color": "{{ if eq .Status "firing" }}attention{{ else if eq .Status "resolved" }}good{{ else }}warning{{ end }}", + "text": "{{ if eq .Status "firing" }}đŸ”Ĩ{{ else if eq .Status "resolved" }}✅{{ else }}⚠ī¸{{ end }} Prometheus alert {{ if eq .Status "resolved" }}(Resolved){{ else if eq .Status "firing" }}(Firing){{ else if eq .Status "unknown" }}(Unknown){{ else }}(Warning){{ end }}" + }, + { + "type": "TextBlock", + "weight": "Bolder", + "size": "ExtraLarge", + "text": "{{ if eq .Status "resolved" }}(Resolved) {{ end }}{{ .CommonAnnotations.summary }}", + "wrap": true + } + ] + } + ] + }, + { + "type": "FactSet", + "facts": [ + { "title": "Status", "value": "{{ .Status }} {{ if eq .Status "firing" }}đŸ”Ĩ{{ else if eq .Status "resolved" }}✅{{ else }}⚠ī¸{{ end }}" } + {{ if .CommonLabels.alertname }}, { "title": "Alert", "value": "{{ .CommonLabels.alertname }}" }{{ end }} + {{ if .CommonLabels.instance }}, { "title": "In host", "value": "{{ .CommonLabels.instance }}" }{{ end }} + {{ if .CommonLabels.severity }}, { "title": "Severity", "value": "{{ .CommonLabels.severity }} {{ if eq .CommonLabels.severity "critical" }}❌{{ else if eq .CommonLabels.severity "error" }}❗ī¸{{ else if eq .CommonLabels.severity "warning" }}⚠ī¸{{ else if eq .CommonLabels.severity "info" }}ℹī¸{{ else }}❓{{ end }}" }{{ end }} + {{ if .CommonAnnotations.description }}, { "title": "Description", "value": "{{ .CommonAnnotations.description }}" }{{ end }} + {{- range $key, $value := .CommonLabels }} + {{- if and (ne $key "alertname") (ne $key "instance") (ne $key "severity") }} + , { "title": "{{ $key }}", "value": "{{ $value }}" } + {{- end }} + {{- end }} + {{- range $key, $value := .CommonAnnotations }} + {{- if and (ne $key "summary") (ne $key "description") }} + , { "title": "{{ $key }}", "value": "{{ $value }}" } + {{- end }} + {{- end }} + ] + } + ] + {{ if .CommonAnnotations.runbook_url }}, + "actions": [ + { + "type": "Action.OpenUrl", + "title": "View details", + "url": "{{ .CommonAnnotations.runbook_url }}" + } + ] + {{ end }} + } + } + ] +} +{{ end }} \ No newline at end of file diff --git a/notify/msteamsv2/msteamsv2.go b/notify/msteamsv2/msteamsv2.go index f013bc3bdc..f5ae9b8b04 100644 --- a/notify/msteamsv2/msteamsv2.go +++ b/notify/msteamsv2/msteamsv2.go @@ -125,6 +125,10 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) if err != nil { return false, err } + card := tmpl(n.conf.Card) + if err != nil { + return false, err + } alerts := types.Alerts(as...) color := colorGrey @@ -146,44 +150,55 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) url = strings.TrimSpace(string(content)) } - // A message as referenced in https://learn.microsoft.com/en-us/connectors/teams/?tabs=text1%2Cdotnet#request-body-schema - t := teamsMessage{ - Type: "message", - Attachments: []Attachment{ - { - ContentType: "application/vnd.microsoft.card.adaptive", - ContentURL: nil, - Content: Content{ - Schema: "http://adaptivecards.io/schemas/adaptive-card.json", - Type: "AdaptiveCard", - Version: "1.2", - Body: []Body{ - { - Type: "TextBlock", - Text: title, - Weight: "Bolder", - Size: "Medium", - Wrap: true, - Style: "heading", - Color: color, + // If the card is empty, use title and text otherwise use card. + var payload bytes.Buffer + if card == "" { + // A message as referenced in https://learn.microsoft.com/en-us/connectors/teams/?tabs=text1%2Cdotnet#request-body-schema + t := teamsMessage{ + Type: "message", + Attachments: []Attachment{ + { + ContentType: "application/vnd.microsoft.card.adaptive", + ContentURL: nil, + Content: Content{ + Schema: "http://adaptivecards.io/schemas/adaptive-card.json", + Type: "AdaptiveCard", + Version: "1.2", + Body: []Body{ + { + Type: "TextBlock", + Text: title, + Weight: "Bolder", + Size: "Medium", + Wrap: true, + Style: "heading", + Color: color, + }, + { + Type: "TextBlock", + Text: text, + }, }, - { - Type: "TextBlock", - Text: text, - Wrap: true, + Msteams: Msteams{ + Width: "full", }, }, - Msteams: Msteams{ - Width: "full", - }, }, }, - }, - } + } - var payload bytes.Buffer - if err = json.NewEncoder(&payload).Encode(t); err != nil { - return false, err + if err = json.NewEncoder(&payload).Encode(t); err != nil { + return false, err + } + } else { + // Transform card string into object + var jsonMap map[string]interface{} + json.Unmarshal([]byte(card), &jsonMap) + n.logger.Debug("jsonMap", "jsonMap", jsonMap) + + if err = json.NewEncoder(&payload).Encode(jsonMap); err != nil { + return false, err + } } resp, err := n.postJSONFunc(ctx, n.client, url, &payload)