diff --git a/Dockerfile b/Dockerfile index c9b6788..09ee15a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ 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-config.json /etc/ RUN chmod +x /bin/matrixchat-notify.py diff --git a/README.md b/README.md index 6d65a47..6673910 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ steps: * `allowed_tags` *(default:* [`DEFAULT_ALLOWED_TAGS`]*)* 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. Note that the default list does not include any tags, which allow to load @@ -64,14 +64,28 @@ steps: 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` - If set to `yes`, `y`, `true` or `on`, the message resulting from template - substtution is considered to be in Markdown format and will be rendered to - HTML and sent as a formatted message with `org.matrix.custom.html` format. + If set to `yes`, `y`, `true`, `t`, `on` or `1`, the message resulting from + template substtution is considered to be in Markdown format and will be + 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 - 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`) @@ -98,9 +112,9 @@ steps: * `template` *(default:* `${DRONE_BUILD_STATUS}`*)* - The message template. Valid placeholders of the form `${PLACEHOLDER}` will - be substituted with the values of the matching environment variables - (subject to filtering according to the `pass_environment` setting). + The message template. Valid placeholders (example: `${DRONE_REPO}`) will be + substituted with the values of the matching environment variables (subject + to filtering according to the `pass_environment` setting). See this [reference] for environment variables available in drone.io CI pipelines. @@ -114,6 +128,7 @@ steps: [`DEFAULT_ALLOWED_TAGS`]: ./matrixchat-notify.py#L34 [allowed attributes]: https://bleach.readthedocs.io/en/latest/clean.html#allowed-attributes-attributes [drone.io]: https://drone.io/ +[jinja]: https://jinja.palletsprojects.com/ [list of extensions]: https://python-markdown.github.io/extensions/ [plugin]: https://docs.drone.io/plugins/overview/ -[reference]: https://docs.drone.io/pipeline/environment/reference/ +[reference]: https://docs.drone.io/pipeline/environment/reference/ diff --git a/matrixchat-notify.py b/matrixchat-notify.py index 65b6e4d..b905ce6 100755 --- a/matrixchat-notify.py +++ b/matrixchat-notify.py @@ -5,6 +5,7 @@ Requires: * * Optional: +* Optional: * Optional: """ @@ -73,6 +74,7 @@ SETTINGS_KEYS = ( "deviceid", "devicename", "homeserver", + "jinja", "markdown", "markdown_extensions", "pass_environment", @@ -86,7 +88,7 @@ log = logging.getLogger(PROG) def tobool(s): try: - return strtobool(s) + return strtobool(str(s)) except ValueError: return False @@ -156,15 +158,15 @@ async def send_notification(config, message): await client.close() -def render_message(config): - pass_environment = config.get("pass_environment", "") +def get_template_context(config): + pass_environment = config.get("pass_environment", []) if not isinstance(pass_environment, list): pass_environment = [pass_environment] patterns = [] for value in pass_environment: - # expand any comma-separetd names/patterns + # expand any comma-separated names/patterns if "," in value: patterns.extend([p.strip() for p in value.split(",") if p.strip()]) else: @@ -176,9 +178,24 @@ def render_message(config): for pattern in patterns: 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) - return Template(template).safe_substitute(context) + + 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) def render_markdown(message, config):