feat: add environment white-list config setting and cmdline option
See description in readme for details Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
parent
da31628d86
commit
db7edd5dae
11
README.md
11
README.md
|
@ -52,6 +52,14 @@ steps:
|
||||||
substtution is considered to be in Markdown format and will be rendered to
|
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.
|
HTML and sent as a formatted message with `org.matrix.custom.html` format.
|
||||||
|
|
||||||
|
* `pass_environment` *(default:* `DRONE_*`*)*
|
||||||
|
|
||||||
|
Comma-separated white-list of environment variable names or name patterns.
|
||||||
|
Patterns are shell-glob style patterns and case-sensitive.
|
||||||
|
|
||||||
|
Only environment variables matching any of the given names or patterns will
|
||||||
|
be available as valid placeholders in the message template.
|
||||||
|
|
||||||
* `password`
|
* `password`
|
||||||
|
|
||||||
Password to use for authenticating the user set with `userid`. Either a
|
Password to use for authenticating the user set with `userid`. Either a
|
||||||
|
@ -64,7 +72,8 @@ 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 of the form `${PLACEHOLDER}` will
|
||||||
be substituted with the values of the matching environment variables.
|
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
|
See this [reference] for environment variables available in drone.io CI
|
||||||
pipelines.
|
pipelines.
|
||||||
|
|
|
@ -10,6 +10,7 @@ Requires:
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import fnmatch
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -22,14 +23,16 @@ from nio import AsyncClient, LoginResponse
|
||||||
|
|
||||||
PROG = "matrixchat-notify"
|
PROG = "matrixchat-notify"
|
||||||
CONFIG_FILENAME = f"{PROG}-config.json"
|
CONFIG_FILENAME = f"{PROG}-config.json"
|
||||||
DEFAULT_TEMPLATE = "${DRONE_BUILD_STATUS}"
|
|
||||||
DEFAULT_HOMESERVER = "https://matrix.org"
|
DEFAULT_HOMESERVER = "https://matrix.org"
|
||||||
|
DEFAULT_PASS_ENVIRONMENT = ["DRONE_*"]
|
||||||
|
DEFAULT_TEMPLATE = "${DRONE_BUILD_STATUS}"
|
||||||
SETTINGS_KEYS = (
|
SETTINGS_KEYS = (
|
||||||
"accesstoken",
|
"accesstoken",
|
||||||
"deviceid",
|
"deviceid",
|
||||||
"devicename",
|
"devicename",
|
||||||
"homeserver",
|
"homeserver",
|
||||||
"markdown",
|
"markdown",
|
||||||
|
"pass_environment",
|
||||||
"password",
|
"password",
|
||||||
"roomid",
|
"roomid",
|
||||||
"template",
|
"template",
|
||||||
|
@ -110,6 +113,38 @@ async def send_notification(config, message):
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
|
|
||||||
|
def render_message(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
|
||||||
|
if "," in value:
|
||||||
|
patterns.extend([p.strip() for p in value.split(",") if p.strip()])
|
||||||
|
else:
|
||||||
|
patterns.append(value)
|
||||||
|
|
||||||
|
env_names = tuple(os.environ)
|
||||||
|
filtered_names = set()
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
filtered_names.update(fnmatch.filter(env_names, pattern))
|
||||||
|
|
||||||
|
context = {name: os.environ[name] for name in tuple(filtered_names)}
|
||||||
|
template = config.get("template", DEFAULT_TEMPLATE)
|
||||||
|
return Template(template).safe_substitute(context)
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(message):
|
||||||
|
import markdown
|
||||||
|
|
||||||
|
formatted = markdown.markdown(message)
|
||||||
|
return {"formatted_body": formatted, "body": message, "format": "org.matrix.custom.html"}
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
ap = argparse.ArgumentParser(prog=PROG, description=__doc__.splitlines()[0])
|
ap = argparse.ArgumentParser(prog=PROG, description=__doc__.splitlines()[0])
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
|
@ -125,6 +160,17 @@ def main(args=None):
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Don't send notification message, only print it.",
|
help="Don't send notification message, only print it.",
|
||||||
)
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"-e",
|
||||||
|
"--pass-environment",
|
||||||
|
nargs="*",
|
||||||
|
help=(
|
||||||
|
"Comma-separated white-list of environment variable names or name patterns. Only "
|
||||||
|
"environment variables matching any of the given names or patterns will be available "
|
||||||
|
"as valid placeholders in the message template. "
|
||||||
|
"Accepts shell glob patterns and may be passed more than once (default: 'DRONE_*')."
|
||||||
|
),
|
||||||
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"-m",
|
"-m",
|
||||||
"--render-markdown",
|
"--render-markdown",
|
||||||
|
@ -150,23 +196,22 @@ def main(args=None):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return f"Could not parse configuration: {exc}"
|
return f"Could not parse configuration: {exc}"
|
||||||
|
|
||||||
template = config.get("template", DEFAULT_TEMPLATE)
|
if args.pass_environment is not None:
|
||||||
message = Template(template).safe_substitute(os.environ)
|
# Security feature: if any environment names/patterns are passed via -e|--pass-environment
|
||||||
|
# options, they completely replace any given via the config or environment.
|
||||||
|
config["pass_environment"] = args.pass_environment
|
||||||
|
|
||||||
|
if "pass_environment" not in config:
|
||||||
|
config["pass_environment"] = DEFAULT_PASS_ENVIRONMENT
|
||||||
|
|
||||||
|
message = render_message(config)
|
||||||
|
|
||||||
if tobool(config.get("markdown")) or args.render_markdown:
|
if tobool(config.get("markdown")) or args.render_markdown:
|
||||||
log.debug("Rendering markdown message to HTML.")
|
log.debug("Rendering markdown message to HTML.")
|
||||||
try:
|
try:
|
||||||
import markdown
|
message = render_markdown(message)
|
||||||
|
|
||||||
formatted = markdown.markdown(message)
|
|
||||||
except: ## noqa
|
except: ## noqa
|
||||||
log.exception("Failed to render message with markdown.")
|
log.exception("Failed to render message with markdown.")
|
||||||
return 1
|
|
||||||
|
|
||||||
body = message
|
|
||||||
message = {"formatted_body": formatted}
|
|
||||||
message["body"] = body
|
|
||||||
message["format"] = "org.matrix.custom.html"
|
|
||||||
|
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
if not config.get("userid"):
|
if not config.get("userid"):
|
||||||
|
|
Loading…
Reference in New Issue