Icnga2 webhook o365 teams channel deprecated

we use webhook notifications via json with perl script “modified version of nagios-mstreams.pl for icinga2 without env’s”.

ms teams comments by message since this week:
Action Required: O365 connectors within Teams will be deprecated and notifications from this service will stop. about the timing and how the Workflows app provides a more flexible and secure experience. If you want to continue receiving these types of messages, you can use a workflow to post messages from a webhook request.

has anyone a workflow or idea how we can send messages after ms stop?

I try to switch from icinga2-teams-notification :: Icinga Exchange to Icinga2-teams-adaptive-notifications :: Icinga Exchange and use the new Webhook URL provided by the predefined Workflow - generated by clicking on the link in the comment to the massage.

But I didn’t succeed before the weekend came.

The version used also webhook, what disabled in October.
I think we need new/other plugin with api to ms automate and workflows.

I think the version uses a message that would be compatible if used with a Workflow based webhook. So 2 things need to change message format and m365 webhook to Workflow webhook.

yay. changing APIs is always fun …

Reason was:
I create Workflow “Post to a channel when a webhook request is received” into MS Power Automate. That create a new API URL. I create new python notifications script for icinga2. The script generated a JSON for API and send data via python POST methode.

hi, would you like to share your script?

Hi all,

can you share a solution with us @lini-linux ?
It seems to be a serious issue.

teams-host-notification.py

#!/usr/bin/env python3
import requests, os, sys
import urllib3, argparse

urllib3.disable_warnings()

parser = argparse.ArgumentParser()
parser.add_argument('-4', help='hostaddress4')
parser.add_argument('-6', help='hostaddress6')
parser.add_argument('-H', help='hostaddress')
parser.add_argument('-b', help='author')
parser.add_argument('-c', help='comment')
parser.add_argument('-d', help='datetime')
parser.add_argument('-l', help='hostname')
parser.add_argument('-n', help='hostdisplayname')
parser.add_argument('-o', help='output')
parser.add_argument('-p', help='pager')
parser.add_argument('-s', help='state')
parser.add_argument('-t', help='type')
parser.add_argument('-v', help='logtosyslog')

args = parser.parse_args()

hostaddress = args.H
author = args.b
hostname = args.l
comment = args.c
hostdisplayname = args.n
state = args.s
output = args.o
boturl = args.p

icingaweburl = 'http://10.32.0.10/icingaweb2'
grafanaweburl = 'http://10.32.0.20:3000/d/A2TT33N7z'

if state == 'OK':
  color = 'good'
elif state == 'UP':
  color = 'good'
elif state == 'WARNING':
  color = 'warning'
elif state == 'DOWN':
  color = 'attention'
elif state == 'CRITICAL':
  color = 'attention'
elif state == 'UNKNOWN':
  color = 'light'
elif state == 'UNREACHABLE':
  color = 'light'
else:
  color = 'dark'


headers = {
    	'Content-Type': 'application/json;charset=utf-8',
}

json_data = {
        "type":"message",
        "attachments":[
            {
                "contentType":"application/vnd.microsoft.card.adaptive",
                "contentUrl":"null",
                "content":{
                    "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                    "type":"AdaptiveCard",
                    "version":"1.2",
                    "title":"SSC Monitoring",
                    "body":[
                        {
                          "type": "TextBlock",
                          "size": "Large",
                          "weight": "Bolder",
                          "text": hostdisplayname+" is "+state,
                          "color": color,
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Host**: "+hostaddress+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Details**: "+output+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Comment**: "+comment+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Author**: "+author+" ",
                        }
                    ],
                    "actions": [
                      {
                        "type": "Action.OpenUrl",
                        "url": icingaweburl+"/monitoring/host/show?host="+hostname+"",
                        "title": "Open in Monitoring"
                      },
                      {
                        "type": "Action.OpenUrl",
                        "url": grafanaweburl+"/icinga2?var-hostname="+hostname+"&orgId=1&fullscreen",
                        "title": "Open in Grafana"
                      }
                    ]
                }
            }
        ]
}

response = requests.post(
    boturl,
    headers=headers,
    json=json_data,
    verify=False
)

1 Like

Hi @lini-linux

it would be really cool if you would also post the teams-service-notification.py script here :eyes:

If such a thing exists.

Thank you very much!

You can copy from host and modify :wink:

teams-service-notification.py

#!/usr/bin/env python3
import requests, os, sys
import urllib3, argparse

urllib3.disable_warnings()

parser = argparse.ArgumentParser()
parser.add_argument('-4', help='hostaddress4')
parser.add_argument('-6', help='hostaddress6')
parser.add_argument('-H', help='hostaddress')
parser.add_argument('-b', help='author')
parser.add_argument('-c', help='comment')
parser.add_argument('-d', help='datetime')
parser.add_argument('-e', help='servicename')
parser.add_argument('-l', help='hostname')
parser.add_argument('-n', help='hostdisplayname')
parser.add_argument('-o', help='output')
parser.add_argument('-p', help='pager')
parser.add_argument('-s', help='state')
parser.add_argument('-t', help='type')
parser.add_argument('-u', help='servicedisplayname')
parser.add_argument('-v', help='logtosyslog')

args = parser.parse_args()

hostaddress = args.H
author = args.b
hostname = args.l
comment = args.c
hostdisplayname = args.n
state = args.s
output = args.o
servicename = args.e
boturl = args.p
servicedisplayname = args.u

icingaweburl = 'http://10.32.0.10/icingaweb2'
grafanaweburl = 'http://10.32.0.20:3000/d/A2TT33N7z'

if state == 'OK':
  color = 'good'
elif state == 'UP':
  color = 'good'
elif state == 'WARNING':
  color = 'warning'
elif state == 'DOWN':
  color = 'attention'
elif state == 'CRITICAL':
  color = 'attention'
elif state == 'UNKNOWN':
  color = 'light'
elif state == 'UNREACHABLE':
  color = 'light'
else:
  color = 'dark'


headers = {
    	'Content-Type': 'application/json;charset=utf-8',
}

json_data = {
        "type":"message",
        "attachments":[
            {
                "contentType":"application/vnd.microsoft.card.adaptive",
                "contentUrl":"null",
                "content":{
                    "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                    "type":"AdaptiveCard",
                    "version":"1.2",
                    "title":"SSC Monitoring",
                    "body":[
                        {
                          "type": "TextBlock",
                          "size": "Large",
                          "weight": "Bolder",
                          "text": hostdisplayname+": "+servicename+" is "+state,
                          "color": color,
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Host**: "+hostaddress+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Service**: "+servicename+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Details**: "+output+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Comment**: "+comment+" ",
                        },
                        {
                          "type": "TextBlock",
                          "text": "**Author**: "+author+" ",
                        }
                    ],
                    "actions": [
                      {
                        "type": "Action.OpenUrl",
                        "url": icingaweburl+"/monitoring/service/show?host="+hostname+"&service="+servicename+"",
                        "title": "Open in Monitoring"
                      },
                      {
                        "type": "Action.OpenUrl",
                        "url": grafanaweburl+"/icinga2?var-hostname="+hostname+"&orgId=1&fullscreen",
                        "title": "Open in Grafana"
                      }
                    ]
                }
            }
        ]
}

response = requests.post(
    boturl,
    headers=headers,
    json=json_data,
    verify=False
)

2 Likes

Hi @lini-linux

I got the following error:

Traceback (most recent call last):
  File "./teams-service-notification.py", line 83, in <module>
    "text": "**Host**: "+hostaddress+" ",
TypeError: must be str, not NoneType

The command was this one:

./teams-service-notification.py '-4' '192.168.0.2' '-6' '' '-b' 'USER' '-c' 'test' '-d' '2024-10-16 09:47:07 +0200' '-e' 'DUMMY TEST ADAPTIVE NOTIFICATIONS ----IGNORE-ME---' '-l' 'srv01.local' '-n' 'srv01' '-o' 'PROCS CRITICAL: 0 processes with command name 'dummy' ' '-p' 'https://WEBHOOKURL' '-s' 'CRITICAL' '-t' 'CUSTOM' '-u' 'DUMMY TEST ADAPTIVE NOTIFICATIONS ----IGNORE-ME---'

Did you guys encounter the same problem ?

/edit:

I noticed when I replace ‘-4’ with ‘-H’ it works:

./teams-service-notification.py '-H' '192.168.0.2' '-6' '' '-b' 'USER' '-c' 'test' '-d' '2024-10-16 09:47:07 +0200' '-e' 'DUMMY TEST ADAPTIVE NOTIFICATIONS ----IGNORE-ME---' '-l' 'srv01.local' '-n' 'srv01' '-o' 'PROCS CRITICAL: 0 processes with command name 'dummy' ' '-p' 'https://WEBHOOKURL' '-s' 'CRITICAL' '-t' 'CUSTOM' '-u' 'DUMMY TEST ADAPTIVE NOTIFICATIONS ----IGNORE-ME---'

Why is there no ‘-H’ argument passed to the notification command ?

I added the argument “-H” with value $host.address$ to the command definition and it works now:

Now the only problem which is left is that the message in teams looks like this:


As ou can see the message does not use the complete width. Is it possible to modify the message so it shows completely ?
For example like the old message with the old soon to be deprecated webhook:

This is how the workflow is setup:

I have changed the design of the Adaptive Card and adapted the link for IcingaDB.
With the imageurl variable you can display an image with the corresponding color (Important: The image must be accessible from the Internet for teams).
The author and comment are only displayed if you have really commented something.
With “msteams”: {“width”: “Full”} the maximum width in teams is used.
Hope this helps someone

image

image

Service

#!/usr/bin/env python3
import requests, os, sys
import urllib3, argparse
from datetime import datetime

urllib3.disable_warnings()

current_datetime = datetime.now()
current_datetime_str = current_datetime.strftime("%d.%m.%Y %H:%M:%S")

parser = argparse.ArgumentParser()
parser.add_argument('-4', help='hostaddress4')
parser.add_argument('-6', help='hostaddress6')
parser.add_argument('-H', help='hostaddress')
parser.add_argument('-b', help='author')
parser.add_argument('-c', help='comment')
parser.add_argument('-d', help='datetime')
parser.add_argument('-e', help='servicename')
parser.add_argument('-l', help='hostname')
parser.add_argument('-n', help='hostdisplayname')
parser.add_argument('-o', help='output')
parser.add_argument('-p', help='pager')
parser.add_argument('-s', help='state')
parser.add_argument('-t', help='type')
parser.add_argument('-u', help='servicedisplayname')
parser.add_argument('-v', help='logtosyslog')
parser.add_argument('-i', help='icingaweburl')
parser.add_argument('-g', help='grafanaurl')

args = parser.parse_args()

hostaddress = args.H
author = args.b
hostname = args.l
comment = args.c
hostdisplayname = args.n
state = args.s
output = args.o
servicename = args.e
boturl = args.p
servicedisplayname = args.u
notificationtype = args.t
icingaweburl = args.i
grafanaweburl = args.g

if state == 'OK':
  color = 'good'
  imageurl = "https://i.imgur.com/yLPKBlF.png"
elif state == 'UP':
  color = 'good'
  imageurl = "https://i.imgur.com/yLPKBlF.png"
elif state == 'WARNING':
  color = 'warning'
  imageurl = "https://i.imgur.com/GIv0R4o.png"
elif state == 'DOWN':
  color = 'attention'
  imageurl = "https://i.imgur.com/C2B8moA.png"
elif state == 'CRITICAL':
  color = 'attention'
  imageurl = "https://i.imgur.com/C2B8moA.png"
elif state == 'UNKNOWN':
  color = 'light'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"
elif state == 'UNREACHABLE':
  color = 'light'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"
else:
  color = 'dark'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"

# Check if type is custom
if comment or author:
    iscomment = "true"
else:
    iscomment = "false"

headers = {
        'Content-Type': 'application/json;charset=utf-8',
}

json_data = {
        "type":"message",
        "attachments":[
            {
                "contentType":"application/vnd.microsoft.card.adaptive",
                "contentUrl":"null",
                "content":{
                    "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                    "type":"AdaptiveCard",
                    "version":"1.3",
                    "title":"Monitoring",
                    "msteams": {
                        "width": "Full"
                    },
                    "body":[
                        {
                            "type": "Container",
                            "items": [
                                {
                                    "type": "TextBlock",
                                    "text": notificationtype,
                                    "weight": "bolder",
                                    "size": "medium",
                                    "isSubtle": "true",
                                    "spacing": "None"
                                },
                                {
                                    "type": "TextBlock",
                                    "text": hostdisplayname,
                                    "weight": "bolder",
                                    "size": "large",
                                    "spacing": "None"
                                },
                                {
                                    "type": "ColumnSet",
                                    "columns": [
                                        {
                                            "type": "Column",
                                            "width": "auto",
                                            "items": [
                                                {
                                                    "type": "Image",
                                                    "url": imageurl,
                                                    "altText": state,
                                                    "size": "small",
                                                    "style": "person"
                                                }
                                            ]
                                        },
                                        {
                                            "type": "Column",
                                            "width": "stretch",
                                            "items": [
                                                {
                                                    "type": "RichTextBlock",
                                                    "inlines": [
                                                        {
                                                            "type": "TextRun",
                                                            "text": servicedisplayname,
                                                            "weight": "bolder",
                                                            "wrap": "true"
                                                        },
                                                        {
                                                            "type": "TextRun",
                                                            "text": " is ",
                                                            "weight": "bolder",
                                                            "wrap": "true",
                                                            "isSubtle": "true"
                                                        },
                                                        {
                                                            "type": "TextRun",
                                                            "text": state,
                                                            "weight": "bolder",
                                                            "wrap": "true"
                                                        }
                                                    ]
                                                },
                                                {
                                                "type": "TextBlock",
                                                "spacing": "none",
                                                "text": current_datetime_str,
                                                "isSubtle": "true",
                                                "wrap": "true"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            "type": "Container",
                            "items": [
                                {
                                    "type": "RichTextBlock",
                                    "inlines": [
                                        {
                                            "type": "TextRun",
                                            "text": output
                                        }
                                    ]
                                },
                                {
                                    "type": "FactSet",
                                    "isVisible": iscomment,
                                    "facts": [
                                        {
                                            "title": "Comment:",
                                            "value": comment
                                        },
                                        {
                                            "title": "Author:",
                                            "value": author
                                        }
                                    ]
                                }
                            ]
                        }
                    ],
                    "actions": [
                      {
                        "type": "Action.OpenUrl",
                        "url": icingaweburl+"/icingadb/service?name="+servicename+"&host.name="+hostname+"",
                        "title": "View"
                      }
                    ]
                }
            }
        ]
}

response = requests.post(
    boturl,
    headers=headers,
    json=json_data,
    verify=False
)

Host

#!/usr/bin/env python3
import requests, os, sys
import urllib3, argparse
from datetime import datetime

urllib3.disable_warnings()

current_datetime = datetime.now()
current_datetime_str = current_datetime.strftime("%d.%m.%Y %H:%M:%S")

parser = argparse.ArgumentParser()
parser.add_argument('-4', help='hostaddress4')
parser.add_argument('-6', help='hostaddress6')
parser.add_argument('-H', help='hostaddress')
parser.add_argument('-b', help='author')
parser.add_argument('-c', help='comment')
parser.add_argument('-d', help='datetime')
parser.add_argument('-l', help='hostname')
parser.add_argument('-n', help='hostdisplayname')
parser.add_argument('-o', help='output')
parser.add_argument('-p', help='pager')
parser.add_argument('-s', help='state')
parser.add_argument('-t', help='type')
parser.add_argument('-v', help='logtosyslog')
parser.add_argument('-i', help='icingaweburl')
parser.add_argument('-g', help='grafanaurl')

args = parser.parse_args()

hostaddress = args.H
author = args.b
hostname = args.l
comment = args.c
hostdisplayname = args.n
state = args.s
output = args.o
boturl = args.p
notificationtype = args.t
icingaweburl = args.i
grafanaweburl = args.g

if state == 'OK':
  color = 'good'
  imageurl = "https://i.imgur.com/yLPKBlF.png"
elif state == 'UP':
  color = 'good'
  imageurl = "https://i.imgur.com/yLPKBlF.png"
elif state == 'WARNING':
  color = 'warning'
  imageurl = "https://i.imgur.com/GIv0R4o.png"
elif state == 'DOWN':
  color = 'attention'
  imageurl = "https://i.imgur.com/C2B8moA.png"
elif state == 'CRITICAL':
  color = 'attention'
  imageurl = "https://i.imgur.com/C2B8moA.png"
elif state == 'UNKNOWN':
  color = 'light'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"
elif state == 'UNREACHABLE':
  color = 'light'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"
else:
  color = 'dark'
  imageurl = "https://i.imgur.com/ALr2Cwx.png"

# Check if type is custom
if comment or author:
    iscomment = "true"
else:
    iscomment = "false"

headers = {
        'Content-Type': 'application/json;charset=utf-8',
}

json_data = {
        "type":"message",
        "attachments":[
            {
                "contentType":"application/vnd.microsoft.card.adaptive",
                "contentUrl":"null",
                "content":{
                    "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                    "type":"AdaptiveCard",
                    "version":"1.3",
                    "title":"Monitoring",
                    "msteams": {
                        "width": "Full"
                    },
                    "body":[
                        {
                            "type": "Container",
                            "items": [
                                {
                                    "type": "TextBlock",
                                    "text": notificationtype,
                                    "weight": "bolder",
                                    "size": "medium",
                                    "isSubtle": "true",
                                    "spacing": "None"
                                },
                                {
                                    "type": "ColumnSet",
                                    "columns": [
                                        {
                                            "type": "Column",
                                            "width": "auto",
                                            "items": [
                                                {
                                                    "type": "Image",
                                                    "url": imageurl,
                                                    "altText": state,
                                                    "size": "small",
                                                    "style": "person"
                                                }
                                            ]
                                        },
                                        {
                                            "type": "Column",
                                            "width": "stretch",
                                            "items": [
                                                {
                                                    "type": "RichTextBlock",
                                                    "inlines": [
                                                        {
                                                            "type": "TextRun",
                                                            "text": hostdisplayname,
                                                            "weight": "bolder",
                                                            "size": "large",
                                                            "wrap": "true"
                                                        },
                                                        {
                                                            "type": "TextRun",
                                                            "text": " is ",
                                                            "weight": "bolder",
                                                            "wrap": "true",
                                                            "isSubtle": "true"
                                                        },
                                                        {
                                                            "type": "TextRun",
                                                            "text": state,
                                                            "weight": "bolder",
                                                            "size": "large",
                                                            "wrap": "true"
                                                        }
                                                    ]
                                                },
                                                {
                                                "type": "TextBlock",
                                                "spacing": "none",
                                                "text": current_datetime_str,
                                                "isSubtle": "true",
                                                "wrap": "true"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            "type": "Container",
                            "items": [
                                {
                                    "type": "RichTextBlock",
                                    "inlines": [
                                        {
                                            "type": "TextRun",
                                            "text": output
                                        }
                                    ]
                                },
                                {
                                    "type": "FactSet",
                                    "isVisible": iscomment,
                                    "facts": [
                                        {
                                            "title": "Comment:",
                                            "value": comment
                                        },
                                        {
                                            "title": "Author:",
                                            "value": author
                                        }
                                    ]
                                }
                            ]
                        }
                    ],
                    "actions": [
                      {
                        "type": "Action.OpenUrl",
                        "url": icingaweburl+"/icingadb/host?name="+hostname+"",
                        "title": "View"
                      }
                    ]
                }
            }
        ]
}

response = requests.post(
    boturl,
    headers=headers,
    json=json_data,
    verify=False
)
2 Likes

I there !
How do you call python’s scripts when notification is sending ?
Thanks a lot

You define it in the director under commands:

image

I don’t have director installed yet du to historical instance.
but I did it with configuration and It’s working.

into commands.conf

object NotificationCommand "teams-service-notification-command" {
  command = [ "path/to/teams-service-notification.py" ]

  arguments += {
    "-H" = {
      value = "$host.address$"
      description = "Adresse de l'hôte"
    }
    "-l" = {
      value = "$host.name$"
      description = "Nom de l'hôte"
    }
    "-n" = {
      value = "$host.display_name$"
      description = "Nom affiché de l'hôte"
    }
    "-s" = {
      value = "$service.state$"
      description = "État du service"
    }
    "-o" = {
      value = "$service.output$"
      description = "Sortie du service"
    }
    "-p" = {
      value = "$user.vars.teams_webhook_url$"
      description = "URL du webhook Teams"
    }
    "-e" = {
      value = "$service.display_name$"
      description = "Nom du service"
    }
    "-t" = {
      value = "$notification.type$"
      description = "Type de notification"
    }
  }

  env = {
    NOTIFICATIONTYPE = "$notification.type$"
    SERVICEDESC = "$service.display_name$"
    SERVICESTATE = "$service.state$"
    SERVICEOUTPUT = "$service.output$"
    HOSTALIAS = "$host.display_name$"
    HOSTADDRESS = "$address$"
  }
}

object NotificationCommand "teams-notification-command" {
  command = [ "path/to/teams-host-notification.py" ]

  arguments += {
    "-H" = {
      value = "$host.address$"
      description = "Adresse de l'hôte"
    }
    "-l" = {
      value = "$host.name$"
      description = "Nom de l'hôte"
    }
    "-n" = {
      value = "$host.display_name$"
      description = "Nom affiché de l'hôte"
    }
    "-s" = {
      value = "$host.state$"
      description = "État de l'hôte"
    }
    "-o" = {
      value = "$host.output$"
      description = "Sortie de l'hôte"
    }
    "-p" = {
      value = "$user.vars.teams_webhook_url$"
      description = "URL du webhook Teams"
    }
    "-t" = {
      value = "$notification.type$"
      description = "Type de notification"
    }
  }

  env = {
    NOTIFICATIONTYPE = "$notification.type$"
    HOSTALIAS = "$host.display_name$"
    HOSTADDRESS = "$address$"
    HOSTSTATE = "$host.state$"
    HOSTOUTPUT = "$host.output$"
  }
}

And into users.conf

object User "teams" {
  import "generic-user"
  display_name = "Teams User"
  vars.teams_webhook_url = "Teams webooh URL here"
}

Hosts.conf

vars.notify_users = [ "teams" ]
vars.notification.teams = true

And each host or service I wan’t notifications I had
vars.notification.teams = true into config

1 Like