alertmanager icon indicating copy to clipboard operation
alertmanager copied to clipboard

Adaptive Cards Support

Open 6fears7 opened this issue 1 year ago • 3 comments

To continue #3503.

Credit to @aw1cks provided the original code, I slightly tweaked it.

Notables from that feature request:

  • MessageCards is deprecated, MSTeams integration should support AdaptiveCards
  • The original proposal suggested that by switching the payload format to adaptive web cards, the responsibility for creating valid adaptive cards is entirely upon the user's template. Additionally, perhaps this could be a config option.
  • I rest the case with the Alertmanager team for the option of a config option versus hard cutover.
  • I have two template examples. One is raw key/value, the other cleans things up.

Firing

Template 1 (Fancy)

image

Template 2 (Key/ Value)

image

Resolved

Template 1 (Fancy)

image

Template 2 (Key/ Value)

image

Template 1 (Fancy)

Label / Annotations View:

image

Silences:

Template 1 (Fancy)

Uses .CommonLabels

image

Click to view key/value Template
{{ define "msteams.ddefault.text" }}
{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.2",
    "padding": "None",
    "msteams": {
        "width": "Full"
    },
    "body": [
        {
            "type": "Container",
            "id": "alert-msg",
            "padding": "Default",
            "items": [
                {
                    "type": "TextBlock",
                    "id": "alert-summary-title",
                    "text": "[{{- if gt (len .Alerts.Firing) 0 }}FIRING: {{ .Alerts.Firing | len }}] 🔥 {{end}} {{- if gt (len .Alerts.Resolved) 0 }}RESOLVED: {{ .Alerts.Resolved | len }}] ✅ {{end}} {{ with index .Alerts 0 -}}{{ .Labels.alertname }}{{ end }}",
                    "weight": "Bolder",
                    "color": "{{- if gt (len .Alerts.Firing) 0 }}Attention{{end}}{{- if gt (len .Alerts.Resolved) 0 }}Good{{end}}",
                    "size": "ExtraLarge",
                    "horizontalAlignment": "Left"
                },
                {{ if gt (len .Alerts.Firing) 0 }}
                {{ range $index, $alert := .Alerts.Firing }}
                {
                    "type": "TextBlock",
                    "id": "alert-{{ $index }}-label-title",
                    "text": "Labels",
                    "weight": "Bolder",
                    "size": "Medium",
                    "horizontalAlignment": "Left"
                },
                {
                    "type": "FactSet",
                    "facts": [
                        {{ range $key, $value := $alert.Labels}}
                        {
                            "title": "{{ $key }}",
                            "value": "{{ $value }}"
                        },
                        {{ end }}
                        {
                            "type": "TextBlock",
                            "id": "alert-{{ $index }}-annotations-title",
                            "text": "Annotations",
                            "weight": "Bolder",
                            "size": "Medium",
                            "horizontalAlignment": "Left"
                        },
                        {{ range $key, $value := $alert.Annotations}}
                        {
                            "title": "{{ $key }}",
                            "value": "{{ $value }}"
                        },
                        {{ end }}
                        {}
                    ]
                },
                {{ end }}
                {{ end }}
                {{ if gt (len .Alerts.Resolved) 0 }}
                {{ range $index, $alert := .Alerts.Resolved }}
                {
                    "type": "TextBlock",
                    "id": "alert-{{ $index }}-label-title",
                    "text": "Labels",
                    "weight": "Bolder",
                    "size": "Medium",
                    "horizontalAlignment": "Left"
                },
                {
                    "type": "FactSet",
                    "facts": [
                        {{ range $key, $value := $alert.Labels}}
                        {
                            "title": "{{ $key }}",
                            "value": "{{ $value }}"
                        },
                        {{ end }}
                        {
                            "type": "TextBlock",
                            "id": "alert-{{ $index }}-annotations-title",
                            "text": "Annotations",
                            "weight": "Bolder",
                            "size": "Medium",
                            "horizontalAlignment": "Left"
                        },
                        {{ range $key, $value := $alert.Annotations}}
                        {
                            "title": "{{ $key }}",
                            "value": "{{ $value }}"
                        },
                        {{ end }}
                        {}
                    ]
                },
                {{ end }}
                {{ end }}
                {{ with index .Alerts 0 }}
                {
                    "type": "TextBlock",
                    "id": "alert-annotations-title",
                    "text": "Source: {{ .GeneratorURL }}",
                    "weight": "Bolder",
                    "size": "Medium",
                    "horizontalAlignment": "Left"
                }
                {{ end }}
            ]
        }
    ]
}
{{ end }}
Click to view fancy Template
{{ define "new.text" }}
{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.2",
    "padding": "None",
    "msteams": {
        "width": "Full"
    },
    
    "body": [
        {
            "type": "Container",
            "id": "alert-msg",
            "padding": "Default",
            "items": [
                {
                    "type": "TextBlock",
                    "id": "alert-summary-title",
                    "text": "[{{- if gt (len .Alerts.Firing) 0 }}FIRING: {{ .Alerts.Firing | len }}] 🔥 {{end}} {{- if gt (len .Alerts.Resolved) 0 }}RESOLVED: {{ .Alerts.Resolved | len }}] ✅ {{end}} {{ with index .Alerts 0 -}}{{ .Labels.alertname }}{{ end }}",
                    "weight": "Bolder",
                    "color": "{{- if gt (len .Alerts.Firing) 0 }}Attention{{end}}{{- if gt (len .Alerts.Resolved) 0 }}Good{{end}}",
                    "size": "ExtraLarge",
                    "horizontalAlignment": "Left"
                },
                {
                    "type": "Container",
                    "id": "alert-summary-container",
                    "padding": "None",
                    "items": [
                        {
                            "type": "TextBlock",
                            "id": "alert-summary-description",
                            "text": "{{ .Alerts.Firing | len }} alerts are firing",
                            "wrap": true
                        },
                         {
                            "type": "ActionSet",
                            "id": "alert-silence-action",
                            "actions": [
                                {
                                    "type": "Action.OpenUrl",
                                    "id": "silence",
                                    "title": "Silence",
                                    "url": "{{ .ExternalURL }}/#/silences/new?filter=%7B
                                    {{- range $key, $value := .CommonLabels }}
                                    {{- if eq $key "alertname" }}{{ $key }}%3D%22{{ reReplaceAll "\\\\" "" $value }}%22{{ end }}
                                    {{- end }}
                                    {{- range $key, $value := .CommonLabels }}
                                    {{- if ne $key "alertname" }}%2C{{ $key }}%3D%22{{ reReplaceAll "\\\\" "" $value }}%22{{ end }}
                                    {{- end -}}%7D"
                                },
                                {{ with $alert := index .Alerts 0}}
                                {
                                    "type": "Action.OpenUrl",
                                    "id": "prom",
                                    "title": "View in Prometheus",
                                    "url": "{{ .GeneratorURL }}"
                                }
                                
                                {{ end }}      
                            
                            ]
                        }

                    ]
                },
                 {{ range $index, $alert := .Alerts.Firing }}
                {
                    "type": "Container",
                    "id": "{{ $index }}-alerts-container",
                    "isVisible": true,
                    "padding": "None",
                    "items": [

                        {
                            "type": "Container",
                            "id": "alert-{{ $index }}-msg-container",
                            "padding": "None",
                            "separator": true,
                            "items": [
                                {
                                    "type": "TextBlock",
                                    "id": "alert-{{ $index }}-summary",
                                    "text": "{{ $alert.Labels.alertname}}",
                                    "wrap": true,
                                    "size": "Medium",
                                    "weight": "Bolder"
                                },
                                {
                                    "type": "TextBlock",
                                    "id": "alert-{{ $index }}-description",
                                    "text": "{{ $alert.Annotations.description }}",
                                    "wrap": true,
                                    "weight": "Lighter",
                                    "size": "Small"
                                },
                                {
                                    "type": "ActionSet",
                                    "id": "alert-{{ $index }}-actions",
                                    "actions": [
                                        {
                                            "type": "Action.ShowCard",
                                            "title": "View Labels",
                                            "card": {
                                                "type": "AdaptiveCard",
                                                "body": [
                                                    {
                                                    "type": "TextBlock",
                                                    "wrap": true,
                                                    "text": "{{ $alert.Labels }}"
                                                    }
                                                ]
                                            }
                                        },
                                          {
                                            "type": "Action.ShowCard",
                                            "title": "View Annotations",
                                            "card": {
                                                "type": "AdaptiveCard",
                                                "body": [
                                                    {
                                                    "type": "TextBlock",
                                                    "wrap": true,
                                                    "text": "{{ $alert.Annotations }}"
                                                    }
                                                ]
                                            }
                                        }
                
                                    ]
                                },

                                {
                                    "type": "Container",
                                    "id": "alert-{{ $index }}-backup-labels-container",
                                    "padding": "None",
                                    "isVisible": false,
                                    "items": [
                                        {
                                            "type": "FactSet",
                                            "id": "alert-{{ $index }}-backup-labels-factset",
                                            "facts": []
                                        }

                                    ]
                                }
                            ]
                        }
                    ]
                },
                {{ end }}
                {{ range $index, $alert := .Alerts.Resolved }}
                {
                    "type": "Container",
                    "id": "{{ $index }}-alerts-container",
                    "isVisible": true,
                    "padding": "None",
                    "items": [

                        {
                            "type": "Container",
                            "id": "alert-{{ $index }}-msg-container",
                            "padding": "None",
                            "separator": true,
                            "items": [
                                {
                                    "type": "TextBlock",
                                    "id": "alert-{{ $index }}-summary",
                                    "text": "{{ $alert.Labels.alertname}}",
                                    "wrap": true,
                                    "size": "Medium",
                                    "weight": "Bolder"
                                },
                                {
                                    "type": "TextBlock",
                                    "id": "alert-{{ $index }}-description",
                                    "text": "{{ $alert.Annotations.description }}",
                                    "wrap": true,
                                    "weight": "Lighter",
                                    "size": "Small"
                                },
                                {
                                    "type": "ActionSet",
                                    "id": "alert-{{ $index }}-actions",
                                    "actions": [
                                        {
                                            "type": "Action.ShowCard",
                                            "title": "View Labels",
                                            "card": {
                                                "type": "AdaptiveCard",
                                                "body": [
                                                    {
                                                    "type": "TextBlock",
                                                    "wrap": true,
                                                    "text": "{{ $alert.Labels }}"
                                                    }
                                                ]
                                            }
                                        },
                                          {
                                            "type": "Action.ShowCard",
                                            "title": "View Annotations",
                                            "card": {
                                                "type": "AdaptiveCard",
                                                "body": [
                                                    {
                                                    "type": "TextBlock",
                                                    "wrap": true,
                                                    "text": "{{ $alert.Annotations }}"
                                                    }
                                                ]
                                            }
                                        }
                
                                    ]
                                },

                                {
                                    "type": "Container",
                                    "id": "alert-{{ $index }}-backup-labels-container",
                                    "padding": "None",
                                    "isVisible": false,
                                    "items": [
                                        {
                                            "type": "FactSet",
                                            "id": "alert-{{ $index }}-backup-labels-factset",
                                            "facts": []
                                        }

                                    ]
                                }
                            ]
                        }
                    ]
                },
                {{ end }}
                {
                    "type": "Container",
                    "id": "backup-alerts-container",
                    "isVisible": false,
                    "padding": "None",
                    "items": [
                        {
                            "type": "Container",
                            "id": "alert-catch-msg-container",
                            "padding": "None",
                            "separator": false,
                            "items": []
                        }
                    ]
                }
            ]
        }

    ]
}
{{ end }}
receivers:
  - name: sandbox
    msteams_configs:
      -  webhook_url: "YOUR_WEBHOOK_HERE"
         text: '{{ template "new.text" . }}'

Is there an appetite for this?

6fears7 avatar Apr 03 '24 23:04 6fears7

Thanks for making this MR @6fears7 ! Happy to assist on this.

aw1cks avatar Apr 04 '24 09:04 aw1cks

@aw1cks, Thank you for the initial contribution / idea. I actually would love your assistance on this as I'm strapped for free time currently. If you can address the CI test failures / implement the test cases, that would be awesome. Until a maintainer says otherwise, perhaps it would be best to just write this as a hard cut to AdaptiveCards versus offering support for both.

The original template file for msteams seemed to be using internal markdown to submit the alert data and is the cause for the test failure since it's expecting JSON format.

Feel free to ping me as needed.

6fears7 avatar Apr 04 '24 15:04 6fears7

Thanks for opening a PR for this! I think support for Adaptive Cards would be great feature to have in Alertmanager.

However, I do think there are a couple of problems with this specific implementation:

  1. Anyone who wants to customize their Microsoft Teams notifications will have to write a template with the contents of the JSON as you have done in default.tmpl. This is really complicated and very error prone. For example, it will be very easy to create invalid JSON from a Go template, especially when using loops and if statements.
  2. All other integrations we support allow customization of fields without having to know the specific data format that is expected. In this case, the schema for Adaptive Cards. This is very different from how all other integrations work.

My suggestion would be to try and come up with an abstraction for Adaptive Cards in YAML. An example of this is the draft PR I opened for Slack Block Kit (see https://github.com/prometheus/alertmanager/pull/3381). I am interested to see what you can come up with for Microsoft Teams.

grobinson-grafana avatar Apr 05 '24 23:04 grobinson-grafana