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 groups
|
||||||
from confspirator import fields
|
from confspirator import fields
|
||||||
|
from confspirator import types
|
||||||
|
|
||||||
|
from adjutant.common import constants
|
||||||
|
|
||||||
|
|
||||||
config_group = groups.ConfigGroup("workflow")
|
config_group = groups.ConfigGroup("workflow")
|
||||||
@ -39,23 +42,42 @@ config_group.register_child_config(
|
|||||||
|
|
||||||
def _build_default_email_group(
|
def _build_default_email_group(
|
||||||
group_name,
|
group_name,
|
||||||
email_subject,
|
subject,
|
||||||
email_from,
|
email_from,
|
||||||
|
email_to,
|
||||||
email_reply,
|
email_reply,
|
||||||
email_template,
|
template,
|
||||||
email_html_template,
|
html_template,
|
||||||
|
email_current_user,
|
||||||
|
emails,
|
||||||
):
|
):
|
||||||
email_group = groups.ConfigGroup(group_name)
|
email_group = groups.ConfigGroup(group_name)
|
||||||
email_group.register_child_config(
|
email_group.register_child_config(
|
||||||
fields.StrConfig(
|
fields.StrConfig(
|
||||||
"subject",
|
"subject",
|
||||||
help_text="Default email subject for this stage",
|
help_text="Default email subject for this stage",
|
||||||
default=email_subject,
|
default=subject,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
email_group.register_child_config(
|
email_group.register_child_config(
|
||||||
fields.StrConfig(
|
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(
|
email_group.register_child_config(
|
||||||
@ -69,14 +91,32 @@ def _build_default_email_group(
|
|||||||
fields.StrConfig(
|
fields.StrConfig(
|
||||||
"template",
|
"template",
|
||||||
help_text="Default email template for this stage",
|
help_text="Default email template for this stage",
|
||||||
default=email_template,
|
default=template,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
email_group.register_child_config(
|
email_group.register_child_config(
|
||||||
fields.StrConfig(
|
fields.StrConfig(
|
||||||
"html_template",
|
"html_template",
|
||||||
help_text="Default email html template for this stage",
|
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
|
return email_group
|
||||||
@ -90,31 +130,40 @@ _task_defaults_group.register_child_config(_email_defaults_group)
|
|||||||
_email_defaults_group.register_child_config(
|
_email_defaults_group.register_child_config(
|
||||||
_build_default_email_group(
|
_build_default_email_group(
|
||||||
group_name="initial",
|
group_name="initial",
|
||||||
email_subject="Task Confirmation",
|
subject="Task Confirmation",
|
||||||
email_reply="no-reply@example.com",
|
|
||||||
email_from="bounce+%(task_uuid)s@example.com",
|
email_from="bounce+%(task_uuid)s@example.com",
|
||||||
email_template="initial.txt",
|
email_to=None,
|
||||||
email_html_template=None,
|
email_reply="no-reply@example.com",
|
||||||
|
template="initial.txt",
|
||||||
|
html_template=None,
|
||||||
|
email_current_user=False,
|
||||||
|
emails=[],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_email_defaults_group.register_child_config(
|
_email_defaults_group.register_child_config(
|
||||||
_build_default_email_group(
|
_build_default_email_group(
|
||||||
group_name="token",
|
group_name="token",
|
||||||
email_subject="Task Token",
|
subject="Task Token",
|
||||||
email_reply="no-reply@example.com",
|
|
||||||
email_from="bounce+%(task_uuid)s@example.com",
|
email_from="bounce+%(task_uuid)s@example.com",
|
||||||
email_template="token.txt",
|
email_to=None,
|
||||||
email_html_template=None,
|
email_reply="no-reply@example.com",
|
||||||
|
template="token.txt",
|
||||||
|
html_template=None,
|
||||||
|
email_current_user=False,
|
||||||
|
emails=[],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_email_defaults_group.register_child_config(
|
_email_defaults_group.register_child_config(
|
||||||
_build_default_email_group(
|
_build_default_email_group(
|
||||||
group_name="completed",
|
group_name="completed",
|
||||||
email_subject="Task Completed",
|
subject="Task Completed",
|
||||||
email_reply="no-reply@example.com",
|
|
||||||
email_from="bounce+%(task_uuid)s@example.com",
|
email_from="bounce+%(task_uuid)s@example.com",
|
||||||
email_template="completed.txt",
|
email_to=None,
|
||||||
email_html_template=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 django.utils import timezone
|
||||||
|
|
||||||
from adjutant.api.models import Token
|
from adjutant.api.models import Token
|
||||||
|
from adjutant.common import user_store
|
||||||
from adjutant.notifications.utils import create_notification
|
from adjutant.notifications.utils import create_notification
|
||||||
from adjutant.config import CONF
|
from adjutant.config import CONF
|
||||||
from adjutant import exceptions
|
from adjutant import exceptions
|
||||||
@ -58,27 +59,109 @@ def create_token(task, expiry_time=None):
|
|||||||
|
|
||||||
|
|
||||||
def send_stage_email(task, email_conf, token=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:
|
if not email_conf:
|
||||||
return
|
return
|
||||||
|
|
||||||
text_template = loader.get_template(
|
# Send one or more emails according to per-email configurations
|
||||||
email_conf["template"], using="include_etc_templates"
|
# if provided. If not, send a single email using the stage-global
|
||||||
)
|
# email configuration values.
|
||||||
html_template = email_conf["html_template"]
|
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:
|
if html_template:
|
||||||
html_template = loader.get_template(
|
html_template = loader.get_template(
|
||||||
html_template, using="include_etc_templates"
|
html_template, using="include_etc_templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# find our set of emails and actions that require email
|
||||||
emails = set()
|
emails = set()
|
||||||
actions = {}
|
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:
|
for action in task.actions:
|
||||||
act = action.get_action()
|
act = action.get_action()
|
||||||
email = act.get_email()
|
email = act.get_email()
|
||||||
if email:
|
if email:
|
||||||
emails.add(email)
|
action_name = str(act)
|
||||||
actions[str(act)] = 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:
|
if not emails:
|
||||||
return
|
return
|
||||||
@ -93,7 +176,20 @@ def send_stage_email(task, email_conf, token=None):
|
|||||||
create_notification(task, notes, error=True)
|
create_notification(task, notes, error=True)
|
||||||
return
|
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:
|
if token:
|
||||||
tokenurl = CONF.workflow.horizon_url
|
tokenurl = CONF.workflow.horizon_url
|
||||||
if not tokenurl.endswith("/"):
|
if not tokenurl.endswith("/"):
|
||||||
@ -104,28 +200,20 @@ def send_stage_email(task, email_conf, token=None):
|
|||||||
try:
|
try:
|
||||||
message = text_template.render(context)
|
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
|
# these are the message headers which will be visible to
|
||||||
# the email client.
|
# the email client.
|
||||||
headers = {
|
headers = {
|
||||||
"X-Adjutant-Task-UUID": task.uuid,
|
"X-Adjutant-Task-UUID": task.uuid,
|
||||||
# From needs to be set to be disctinct from return-path
|
# From needs to be set to be disctinct from return-path
|
||||||
"From": email_conf["reply"],
|
"From": email_reply,
|
||||||
"Reply-To": email_conf["reply"],
|
"Reply-To": email_reply,
|
||||||
}
|
}
|
||||||
|
|
||||||
email = EmailMultiAlternatives(
|
email = EmailMultiAlternatives(
|
||||||
email_conf["subject"],
|
subject,
|
||||||
message,
|
message,
|
||||||
from_email,
|
from_email,
|
||||||
[emails.pop()],
|
[email_address],
|
||||||
headers=headers,
|
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