Merge "Add new configuration options for task emails"
This commit is contained in:
commit
95360b775b
@ -14,6 +14,9 @@
|
||||
|
||||
from confspirator import groups
|
||||
from confspirator import fields
|
||||
from confspirator import types
|
||||
|
||||
from adjutant.common import constants
|
||||
|
||||
|
||||
config_group = groups.ConfigGroup("workflow")
|
||||
@ -39,23 +42,42 @@ config_group.register_child_config(
|
||||
|
||||
def _build_default_email_group(
|
||||
group_name,
|
||||
email_subject,
|
||||
subject,
|
||||
email_from,
|
||||
email_to,
|
||||
email_reply,
|
||||
email_template,
|
||||
email_html_template,
|
||||
template,
|
||||
html_template,
|
||||
email_current_user,
|
||||
emails,
|
||||
):
|
||||
email_group = groups.ConfigGroup(group_name)
|
||||
email_group.register_child_config(
|
||||
fields.StrConfig(
|
||||
"subject",
|
||||
help_text="Default email subject for this stage",
|
||||
default=email_subject,
|
||||
default=subject,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
fields.StrConfig(
|
||||
"from", help_text="Default from email for this stage", default=email_from
|
||||
"from",
|
||||
help_text="Default from email for this stage",
|
||||
regex=constants.EMAIL_WITH_TEMPLATE_REGEX,
|
||||
default=email_from,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
fields.StrConfig(
|
||||
"to",
|
||||
help_text=(
|
||||
"Send the email to the given email address. "
|
||||
"If not set, the email will be sent to the "
|
||||
"recipient email address determined by the action "
|
||||
"being run."
|
||||
),
|
||||
regex=constants.EMAIL_WITH_TEMPLATE_REGEX,
|
||||
default=email_to,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
@ -69,14 +91,32 @@ def _build_default_email_group(
|
||||
fields.StrConfig(
|
||||
"template",
|
||||
help_text="Default email template for this stage",
|
||||
default=email_template,
|
||||
default=template,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
fields.StrConfig(
|
||||
"html_template",
|
||||
help_text="Default email html template for this stage",
|
||||
default=email_html_template,
|
||||
default=html_template,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
fields.BoolConfig(
|
||||
"email_current_user",
|
||||
help_text="Email the user who initiated the task",
|
||||
default=email_current_user,
|
||||
)
|
||||
)
|
||||
email_group.register_child_config(
|
||||
fields.ListConfig(
|
||||
"emails",
|
||||
item_type=types.List(item_type=types.Dict()),
|
||||
help_text=(
|
||||
"Send more than one email, setting parameter overrides "
|
||||
"for each specific email as required"
|
||||
),
|
||||
default=emails,
|
||||
)
|
||||
)
|
||||
return email_group
|
||||
@ -90,31 +130,40 @@ _task_defaults_group.register_child_config(_email_defaults_group)
|
||||
_email_defaults_group.register_child_config(
|
||||
_build_default_email_group(
|
||||
group_name="initial",
|
||||
email_subject="Task Confirmation",
|
||||
email_reply="no-reply@example.com",
|
||||
subject="Task Confirmation",
|
||||
email_from="bounce+%(task_uuid)s@example.com",
|
||||
email_template="initial.txt",
|
||||
email_html_template=None,
|
||||
email_to=None,
|
||||
email_reply="no-reply@example.com",
|
||||
template="initial.txt",
|
||||
html_template=None,
|
||||
email_current_user=False,
|
||||
emails=[],
|
||||
)
|
||||
)
|
||||
_email_defaults_group.register_child_config(
|
||||
_build_default_email_group(
|
||||
group_name="token",
|
||||
email_subject="Task Token",
|
||||
email_reply="no-reply@example.com",
|
||||
subject="Task Token",
|
||||
email_from="bounce+%(task_uuid)s@example.com",
|
||||
email_template="token.txt",
|
||||
email_html_template=None,
|
||||
email_to=None,
|
||||
email_reply="no-reply@example.com",
|
||||
template="token.txt",
|
||||
html_template=None,
|
||||
email_current_user=False,
|
||||
emails=[],
|
||||
)
|
||||
)
|
||||
_email_defaults_group.register_child_config(
|
||||
_build_default_email_group(
|
||||
group_name="completed",
|
||||
email_subject="Task Completed",
|
||||
email_reply="no-reply@example.com",
|
||||
subject="Task Completed",
|
||||
email_from="bounce+%(task_uuid)s@example.com",
|
||||
email_template="completed.txt",
|
||||
email_html_template=None,
|
||||
email_to=None,
|
||||
email_reply="no-reply@example.com",
|
||||
template="completed.txt",
|
||||
html_template=None,
|
||||
email_current_user=False,
|
||||
emails=[],
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -22,6 +22,7 @@ from django.template import loader
|
||||
from django.utils import timezone
|
||||
|
||||
from adjutant.api.models import Token
|
||||
from adjutant.common import user_store
|
||||
from adjutant.notifications.utils import create_notification
|
||||
from adjutant.config import CONF
|
||||
from adjutant import exceptions
|
||||
@ -58,27 +59,109 @@ def create_token(task, expiry_time=None):
|
||||
|
||||
|
||||
def send_stage_email(task, email_conf, token=None):
|
||||
"""Send one or more stage emails for a task using the given configuration.
|
||||
|
||||
This also accepts ``None`` for ``email_conf``, in which case
|
||||
no emails are sent.
|
||||
|
||||
:param task: Task to send the stage email for
|
||||
:type task: Task
|
||||
:param email_conf: Stage email configuration (if configured)
|
||||
:type email_conf: confspirator.groups.GroupNamespace | None
|
||||
:param token: Token to add to the email template, defaults to None
|
||||
:type token: str | None, optional
|
||||
"""
|
||||
|
||||
if not email_conf:
|
||||
return
|
||||
|
||||
text_template = loader.get_template(
|
||||
email_conf["template"], using="include_etc_templates"
|
||||
)
|
||||
html_template = email_conf["html_template"]
|
||||
# Send one or more emails according to per-email configurations
|
||||
# if provided. If not, send a single email using the stage-global
|
||||
# email configuration values.
|
||||
emails = email_conf["emails"] or [{}]
|
||||
|
||||
# For each per-email configuration, send a stage email using
|
||||
# that configuration.
|
||||
# We want to use the per-email configuration values if provided,
|
||||
# but fall back to the stage-global email configuration value
|
||||
# for any that are not.
|
||||
for conf in emails:
|
||||
_send_stage_email(
|
||||
task=task,
|
||||
token=token,
|
||||
subject=conf.get("subject", email_conf["subject"]),
|
||||
template=conf.get("template", email_conf["template"]),
|
||||
html_template=conf.get(
|
||||
"html_template",
|
||||
email_conf["html_template"],
|
||||
),
|
||||
email_from=conf.get("from", email_conf["from"]),
|
||||
email_to=conf.get("to", email_conf["to"]),
|
||||
email_reply=conf.get("reply", email_conf["reply"]),
|
||||
email_current_user=conf.get(
|
||||
"email_current_user",
|
||||
email_conf["email_current_user"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _send_stage_email(
|
||||
task,
|
||||
token,
|
||||
subject,
|
||||
template,
|
||||
html_template,
|
||||
email_from,
|
||||
email_to,
|
||||
email_reply,
|
||||
email_current_user,
|
||||
):
|
||||
text_template = loader.get_template(template, using="include_etc_templates")
|
||||
if html_template:
|
||||
html_template = loader.get_template(
|
||||
html_template, using="include_etc_templates"
|
||||
)
|
||||
|
||||
# find our set of emails and actions that require email
|
||||
emails = set()
|
||||
actions = {}
|
||||
# find our set of emails and actions that require email
|
||||
|
||||
# Fetch all possible email addresses that can be configured.
|
||||
# Even if these are not actually used as the target email,
|
||||
# they are made available in the email templates to be referenced.
|
||||
if CONF.identity.username_is_email and "username" in task.keystone_user:
|
||||
email_current_user_address = task.keystone_user["username"]
|
||||
elif "user_id" in task.keystone_user:
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.get_user(task.keystone_user["user_id"])
|
||||
email_current_user_address = user.email if user else None
|
||||
else:
|
||||
email_current_user_address = None
|
||||
email_action_addresses = {}
|
||||
for action in task.actions:
|
||||
act = action.get_action()
|
||||
email = act.get_email()
|
||||
if email:
|
||||
emails.add(email)
|
||||
actions[str(act)] = act
|
||||
action_name = str(act)
|
||||
email_action_addresses[action_name] = email
|
||||
actions[action_name] = act
|
||||
|
||||
if email_to:
|
||||
emails.add(email_to)
|
||||
elif email_current_user:
|
||||
if not email_current_user_address:
|
||||
notes = {
|
||||
"errors": (
|
||||
"Error: Unable to send update, "
|
||||
"task email is configured to send to current user "
|
||||
f"but no username or user ID found in task: {task.uuid}"
|
||||
),
|
||||
}
|
||||
create_notification(task, notes, error=True)
|
||||
return
|
||||
emails.add(email_current_user_address)
|
||||
else:
|
||||
emails |= set(email_action_addresses.values())
|
||||
|
||||
if not emails:
|
||||
return
|
||||
@ -93,7 +176,20 @@ def send_stage_email(task, email_conf, token=None):
|
||||
create_notification(task, notes, error=True)
|
||||
return
|
||||
|
||||
context = {"task": task, "actions": actions}
|
||||
# from_email is the return-path and is distinct from the
|
||||
# message headers
|
||||
from_email = email_from % {"task_uuid": task.uuid} if email_from else email_reply
|
||||
email_address = emails.pop()
|
||||
|
||||
context = {
|
||||
"task": task,
|
||||
"actions": actions,
|
||||
"from_address": from_email,
|
||||
"reply_address": email_reply,
|
||||
"email_address": email_address,
|
||||
"email_current_user_address": email_current_user_address,
|
||||
"email_action_addresses": email_action_addresses,
|
||||
}
|
||||
if token:
|
||||
tokenurl = CONF.workflow.horizon_url
|
||||
if not tokenurl.endswith("/"):
|
||||
@ -104,28 +200,20 @@ def send_stage_email(task, email_conf, token=None):
|
||||
try:
|
||||
message = text_template.render(context)
|
||||
|
||||
# from_email is the return-path and is distinct from the
|
||||
# message headers
|
||||
from_email = email_conf["from"]
|
||||
if not from_email:
|
||||
from_email = email_conf["reply"]
|
||||
elif "%(task_uuid)s" in from_email:
|
||||
from_email = from_email % {"task_uuid": task.uuid}
|
||||
|
||||
# these are the message headers which will be visible to
|
||||
# the email client.
|
||||
headers = {
|
||||
"X-Adjutant-Task-UUID": task.uuid,
|
||||
# From needs to be set to be disctinct from return-path
|
||||
"From": email_conf["reply"],
|
||||
"Reply-To": email_conf["reply"],
|
||||
"From": email_reply,
|
||||
"Reply-To": email_reply,
|
||||
}
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
email_conf["subject"],
|
||||
subject,
|
||||
message,
|
||||
from_email,
|
||||
[emails.pop()],
|
||||
[email_address],
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,45 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added the ``to`` field to task stage email configurations, for setting
|
||||
an arbitrary address to send task stage emails to.
|
||||
- |
|
||||
Added the ``email_current_user`` field to task stage email configurations,
|
||||
for sending task stage emails to the user who initiated the task.
|
||||
Set ``email_current_user`` to ``true`` to enable this behaviour.
|
||||
- |
|
||||
Added the ``from_address`` variable to task stage email template
|
||||
contexts, allowing the address the email is being sent from internally
|
||||
to be templated in task stage email bodies.
|
||||
Note that this is not necessarily the same address that is set in the
|
||||
``From`` header of the email. For that address, use
|
||||
``reply_address`` instead.
|
||||
- |
|
||||
Added the ``reply_address`` variable to task stage email template
|
||||
contexts, allowing the reply-to address sent to the recipient to be
|
||||
templated in task stage email bodies.
|
||||
- |
|
||||
Added the ``email_address`` variable to task stage email template contexts,
|
||||
allowing the recipient email address to be templated in task stage email
|
||||
bodies.
|
||||
- |
|
||||
Added the ``email_current_user_address`` variable to task stage email
|
||||
template contexts, which exposes the email address of the user that
|
||||
initiated the task for use in task stage email templates.
|
||||
Note that depending on the task being run this value may not be
|
||||
available for use, in which case it will be set to ``None``.
|
||||
- |
|
||||
Added the ``email_action_addresses`` variable to task stage email
|
||||
template contexts, which exposes a dictionary mapping task actions
|
||||
to their recipient email addresses for use in task stage email templates.
|
||||
Note that depending on the task being run there may not be an email
|
||||
address available for certain actions, in which case the dictionary will
|
||||
not store a value for those tasks. If no tasks have any recipient email
|
||||
addresses, the dictionary will be empty.
|
||||
- |
|
||||
Multiple emails can now be sent per task stage using the new ``emails``
|
||||
configuration field. To send multiple emails per task stage, define a list
|
||||
of emails to be sent as ``emails``, with per-email configuration set in
|
||||
the list elements. If a value is not set per-email, the value set in the
|
||||
stage configuration will be used, and if that is unset, the default value
|
||||
will be used.
|
Loading…
Reference in New Issue
Block a user