feat: support rendering message template with Jinja

Add config setting to enable Jinja templates

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
Christopher Arndt 2023-07-24 01:07:32 +02:00
parent 92d85d31bf
commit d6e24aa359
3 changed files with 48 additions and 16 deletions

View File

@ -1,5 +1,5 @@
FROM python:3.11-alpine FROM python:3.11-alpine
RUN python3 -m pip --no-cache-dir install bleach markdown matrix-nio RUN python3 -m pip --no-cache-dir install bleach jinja2 markdown matrix-nio
ADD matrixchat-notify.py /bin/ ADD matrixchat-notify.py /bin/
ADD matrixchat-notify-config.json /etc/ ADD matrixchat-notify-config.json /etc/
RUN chmod +x /bin/matrixchat-notify.py RUN chmod +x /bin/matrixchat-notify.py

View File

@ -40,7 +40,7 @@ steps:
* `allowed_tags` *(default:* [`DEFAULT_ALLOWED_TAGS`]*)* * `allowed_tags` *(default:* [`DEFAULT_ALLOWED_TAGS`]*)*
List or set or string with comma-separated list of HTML tag names. HTML List or set or string with comma-separated list of HTML tag names. HTML
tags not included, will be stripped from the HTML output generated by tags not included will be stripped from the HTML output generated by
rendering a Markdown message template. rendering a Markdown message template.
Note that the default list does not include any tags, which allow to load Note that the default list does not include any tags, which allow to load
@ -64,14 +64,28 @@ steps:
The Matrix homeserver URL. The Matrix homeserver URL.
* `jinja`
If set to `yes`, `y`, `true`, `t`, `on` or `1`, the message template is
rendered with the [Jinja] templating engine (instead of performing simple
placeholder substitution). The template context is controlled by the
`pass_environment` setting, same as with non-Jinja templates, but
placeholders use a different syntax (example: `{{DRONE_REPO}}`), so the
`template` setting should be changed to be a valid Jinja2 template string
when this is enabled.
Using this feature requires the `jinja2` Python module to be available
(it is installed by default in the plugin's docker image).
* `markdown` * `markdown`
If set to `yes`, `y`, `true` or `on`, the message resulting from template If set to `yes`, `y`, `true`, `t`, `on` or `1`, the message resulting from
substtution is considered to be in Markdown format and will be rendered to template substtution is considered to be in Markdown format and will be
HTML and sent as a formatted message with `org.matrix.custom.html` format. rendered to HTML and sent as a formatted message with the format set to
`org.matrix.custom.html`.
Using this feature requires the `markdown` and `bleach` Python modules to Using this feature requires the `markdown` and `bleach` Python modules to
be available (the plugin's docker image has them installed). be available (they are installed by default in the plugin's docker image).
* `markdown_extensions` *(default:* `admonition, extra, sane_lists, smarty`) * `markdown_extensions` *(default:* `admonition, extra, sane_lists, smarty`)
@ -98,9 +112,9 @@ steps:
* `template` *(default:* `${DRONE_BUILD_STATUS}`*)* * `template` *(default:* `${DRONE_BUILD_STATUS}`*)*
The message template. Valid placeholders of the form `${PLACEHOLDER}` will The message template. Valid placeholders (example: `${DRONE_REPO}`) will be
be substituted with the values of the matching environment variables substituted with the values of the matching environment variables (subject
(subject to filtering according to the `pass_environment` setting). to filtering according to the `pass_environment` setting).
See this [reference] for environment variables available in drone.io CI See this [reference] for environment variables available in drone.io CI
pipelines. pipelines.
@ -114,6 +128,7 @@ steps:
[`DEFAULT_ALLOWED_TAGS`]: ./matrixchat-notify.py#L34 [`DEFAULT_ALLOWED_TAGS`]: ./matrixchat-notify.py#L34
[allowed attributes]: https://bleach.readthedocs.io/en/latest/clean.html#allowed-attributes-attributes [allowed attributes]: https://bleach.readthedocs.io/en/latest/clean.html#allowed-attributes-attributes
[drone.io]: https://drone.io/ [drone.io]: https://drone.io/
[jinja]: https://jinja.palletsprojects.com/
[list of extensions]: https://python-markdown.github.io/extensions/ [list of extensions]: https://python-markdown.github.io/extensions/
[plugin]: https://docs.drone.io/plugins/overview/ [plugin]: https://docs.drone.io/plugins/overview/
[reference]: https://docs.drone.io/pipeline/environment/reference/ [reference]: https://docs.drone.io/pipeline/environment/reference/

View File

@ -5,6 +5,7 @@ Requires:
* <https://pypi.org/project/matrix-nio> * <https://pypi.org/project/matrix-nio>
* Optional: <https://pypi.org/project/bleach/> * Optional: <https://pypi.org/project/bleach/>
* Optional: <https://pypi.org/project/Jinja2/>
* Optional: <https://pypi.org/project/markdown/> * Optional: <https://pypi.org/project/markdown/>
""" """
@ -73,6 +74,7 @@ SETTINGS_KEYS = (
"deviceid", "deviceid",
"devicename", "devicename",
"homeserver", "homeserver",
"jinja",
"markdown", "markdown",
"markdown_extensions", "markdown_extensions",
"pass_environment", "pass_environment",
@ -86,7 +88,7 @@ log = logging.getLogger(PROG)
def tobool(s): def tobool(s):
try: try:
return strtobool(s) return strtobool(str(s))
except ValueError: except ValueError:
return False return False
@ -156,15 +158,15 @@ async def send_notification(config, message):
await client.close() await client.close()
def render_message(config): def get_template_context(config):
pass_environment = config.get("pass_environment", "") pass_environment = config.get("pass_environment", [])
if not isinstance(pass_environment, list): if not isinstance(pass_environment, list):
pass_environment = [pass_environment] pass_environment = [pass_environment]
patterns = [] patterns = []
for value in pass_environment: for value in pass_environment:
# expand any comma-separetd names/patterns # expand any comma-separated names/patterns
if "," in value: if "," in value:
patterns.extend([p.strip() for p in value.split(",") if p.strip()]) patterns.extend([p.strip() for p in value.split(",") if p.strip()])
else: else:
@ -176,8 +178,23 @@ def render_message(config):
for pattern in patterns: for pattern in patterns:
filtered_names.update(fnmatch.filter(env_names, pattern)) filtered_names.update(fnmatch.filter(env_names, pattern))
context = {name: os.environ[name] for name in tuple(filtered_names)} return {name: os.environ[name] for name in tuple(filtered_names)}
def render_message(config):
context = get_template_context(config)
template = config.get("template", DEFAULT_TEMPLATE) template = config.get("template", DEFAULT_TEMPLATE)
if tobool(config.get("jinja")):
try:
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
return env.from_string(template).render(context)
except Exception as exc:
log.error("Could not render Jinja2 template: %s", exc)
return template
else:
return Template(template).safe_substitute(context) return Template(template).safe_substitute(context)