-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
282 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
mattermost | ||
========== | ||
|
||
.. automodule:: law.mattermost | ||
|
||
.. contents:: | ||
|
||
|
||
Function ``notify_mattermost`` | ||
------------------------------ | ||
|
||
.. autofunction:: notify_mattermost | ||
|
||
|
||
Class ``NotifyMattermostParameter`` | ||
----------------------------------- | ||
|
||
.. autoclass:: NotifyMattermostParameter | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# coding: utf-8 | ||
# flake8: noqa | ||
|
||
""" | ||
Mattermost contrib functionality. | ||
""" | ||
|
||
__all__ = ["notify_mattermost", "NotifyMattermostParameter"] | ||
|
||
# provisioning imports | ||
from law.contrib.mattermost.notification import notify_mattermost | ||
from law.contrib.mattermost.parameter import NotifyMattermostParameter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# coding: utf-8 | ||
|
||
""" | ||
Function returning the config defaults of the mattermost package. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
from law._types import Any | ||
|
||
|
||
def config_defaults(default_config: dict) -> dict[str, dict[str, Any]]: | ||
return { | ||
"notifications": { | ||
"mattermost_hook_url": None, | ||
"mattermost_header": None, | ||
"mattermost_channel": None, | ||
"mattermost_user": None, | ||
"mattermost_mention_user": None, | ||
"mattermost_icon_url": "https://media.githubusercontent.com/media/riga/law/refs/heads/master/assets/logo_profile.png", # noqa | ||
"mattermost_icon_emoji": None, | ||
"mattermost_success_emoji": ":tada:", | ||
"mattermost_failure_emoji": ":rotating_light:", | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# coding: utf-8 | ||
|
||
""" | ||
Mattermost notifications. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
__all__ = ["notify_mattermost"] | ||
|
||
import threading | ||
import traceback | ||
|
||
from law.config import Config | ||
from law.util import escape_markdown | ||
from law.logger import get_logger | ||
from law._types import Any | ||
|
||
|
||
logger = get_logger(__name__) | ||
|
||
|
||
def notify_mattermost( | ||
title: str, | ||
content: str | dict[str, Any], | ||
hook_url: str | None = None, | ||
channel: str | None = None, | ||
user: str | None = None, | ||
mention_user: str | None = None, | ||
icon_url: str | None = None, | ||
icon_emoji: str | None = None, | ||
**kwargs, | ||
) -> bool: | ||
""" | ||
Sends a mattermost notification and returns *True* on success. The communication with the | ||
mattermost API might have some delays and is therefore handled by a thread. The format of the | ||
notification depends on *content*. If it is a string, a simple text notification is sent. | ||
Otherwise, it should be a dictionary whose fields are formatted as key-value pairs. | ||
""" | ||
cfg = Config.instance() | ||
|
||
# get default settings | ||
if not hook_url: | ||
hook_url = cfg.get_expanded("notifications", "mattermost_hook_url") | ||
if not channel: | ||
channel = cfg.get_expanded("notifications", "mattermost_channel") | ||
if not user: | ||
user = cfg.get_expanded("notifications", "mattermost_user") | ||
if not mention_user: | ||
mention_user = cfg.get_expanded("notifications", "mattermost_mention_user") | ||
if not icon_url: | ||
icon_url = cfg.get_expanded("notifications", "mattermost_icon_url") | ||
if not icon_emoji: | ||
icon_emoji = cfg.get_expanded("notifications", "mattermost_icon_emoji") | ||
|
||
if not hook_url: | ||
logger.warning(f"cannot send Mattermost notification, hook_url ({hook_url}) empty") | ||
return False | ||
|
||
# append the user to mention to the title | ||
# unless explicitly set to empty string | ||
mention_text = "" | ||
if mention_user: | ||
mention_text = " (@{})".format(escape_markdown(mention_user.lstrip("@"))) | ||
|
||
# request data for the API call | ||
request_data = {} | ||
if channel: | ||
request_data["channel"] = channel | ||
if user: | ||
request_data["username"] = user | ||
if icon_url: | ||
request_data["icon_url"] = icon_url | ||
if icon_emoji: | ||
request_data["icon_emoji"] = icon_emoji | ||
|
||
# standard or attachment content? | ||
request_data["text"] = f"{title}{mention_text}\n\n" | ||
if isinstance(content, str): | ||
request_data["text"] += content | ||
else: | ||
for k, v in content.items(): | ||
request_data["text"] += f"{k}: {v}\n" | ||
|
||
# extend by arbitrary kwargs | ||
request_data.update(kwargs) | ||
|
||
# threaded, non-blocking API communication | ||
thread = threading.Thread(target=_notify_mattermost, args=(hook_url, request_data)) | ||
thread.start() | ||
|
||
return True | ||
|
||
|
||
def _notify_mattermost(hook_url: str, request_data: dict[str, Any]) -> None: | ||
import requests # type: ignore[import-untyped] | ||
|
||
try: | ||
res = requests.post(hook_url, json=request_data) | ||
if not res.ok: | ||
logger.warning(f"unsuccessful Mattermost API call: {res}") | ||
except Exception as e: | ||
t = traceback.format_exc() | ||
logger.warning(f"could not send Mattermost notification: {e}\n{t}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# coding: utf-8 | ||
|
||
""" | ||
Mattermost related parameters. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
__all__ = ["NotifyMattermostParameter"] | ||
|
||
from collections import OrderedDict | ||
|
||
from law.config import Config | ||
from law.parameter import NotifyParameter | ||
from law.contrib.mattermost.notification import notify_mattermost | ||
from law._types import Any | ||
|
||
|
||
class NotifyMattermostParameter(NotifyParameter): | ||
|
||
def __init__(self, *args, **kwargs) -> None: | ||
super().__init__(*args, **kwargs) | ||
|
||
self.description: str | ||
if not self.description: | ||
self.description = ( | ||
"when true, and the task's run method is decorated with law.decorator.notify, " | ||
"a Mattermost notification is sent once the task finishes" | ||
) | ||
|
||
@classmethod | ||
def notify(cls, success: bool, title: str, content: dict[str, Any], **kwargs) -> bool: | ||
content = OrderedDict(content) | ||
|
||
# overwrite title | ||
cfg = Config.instance() | ||
header = cfg.get_expanded("notifications", "mattermost_header") | ||
task_block = f"```\n{content['Task']}\n```" | ||
title = f"{header}\n{task_block}" if header else task_block | ||
del content["Task"] | ||
|
||
# markup for traceback | ||
if "Traceback" in content: | ||
content["Traceback"] = f"\n```\n{content['Traceback']}\n```" | ||
|
||
# prepend the status text to the message content | ||
cfg = Config.instance() | ||
status_text = "success" if success else "failure" | ||
status_emoji = cfg.get_expanded("notifications", f"mattermost_{status_text}_emoji") | ||
if status_emoji: | ||
status_text += " " + status_emoji | ||
content["Status"] = status_text | ||
content.move_to_end("Status", last=False) | ||
|
||
# highlight last message | ||
if "Last message" in content: | ||
content["Last message"] = f"`{content['Last Message']}`" | ||
|
||
# highlight keys | ||
content = content.__class__((f"**{k}**", v) for k, v in content.items()) | ||
|
||
# send the notification | ||
return notify_mattermost(title, content, **kwargs) | ||
|
||
def get_transport(self) -> dict[str, Any]: | ||
return { | ||
"func": self.notify, | ||
"raw": True, | ||
"colored": False, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters