Merge "Improve std.email action"
This commit is contained in:
commit
bef9aa5bc3
@ -1056,8 +1056,11 @@ std.email
|
||||
Sends an email message via SMTP protocol.
|
||||
|
||||
- **to_addrs** - Comma separated list of recipients. *Required*.
|
||||
- **cc_addrs** - Comma separated list of CC recipients. *Optional*.
|
||||
- **bcc_addrs** - Comma separated list of BCC recipients. *Optional*.
|
||||
- **subject** - Subject of the message. *Optional*.
|
||||
- **body** - Text containing message body. *Optional*.
|
||||
- **html_body** - Text containing the message in HTML format. *Optional*.
|
||||
- **from_addr** - Sender email address. *Required*.
|
||||
- **smtp_server** - SMTP server host name. *Required*.
|
||||
- **smtp_password** - SMTP server password. *Required*.
|
||||
|
@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from email import header
|
||||
from email.mime import multipart
|
||||
from email.mime import text
|
||||
import json
|
||||
import smtplib
|
||||
@ -277,15 +278,19 @@ class MistralHTTPAction(HTTPAction):
|
||||
|
||||
|
||||
class SendEmailAction(actions.Action):
|
||||
def __init__(self, from_addr, to_addrs, smtp_server,
|
||||
smtp_password=None, subject=None, body=None):
|
||||
def __init__(self, from_addr, to_addrs, smtp_server, cc_addrs=None,
|
||||
bcc_addrs=None, smtp_password=None, subject=None, body=None,
|
||||
html_body=None):
|
||||
super(SendEmailAction, self).__init__()
|
||||
# TODO(dzimine): validate parameters
|
||||
|
||||
# Task invocation parameters.
|
||||
self.to = to_addrs
|
||||
self.cc = cc_addrs or []
|
||||
self.bcc = bcc_addrs or []
|
||||
self.subject = subject or "<No subject>"
|
||||
self.body = body or "<No body>"
|
||||
self.html_body = html_body
|
||||
|
||||
# Action provider settings.
|
||||
self.smtp_server = smtp_server
|
||||
@ -295,19 +300,35 @@ class SendEmailAction(actions.Action):
|
||||
def run(self, context):
|
||||
LOG.info(
|
||||
"Sending email message "
|
||||
"[from=%s, to=%s, subject=%s, using smtp=%s, body=%s...]",
|
||||
"[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
|
||||
"body=%s...]",
|
||||
self.sender,
|
||||
self.to,
|
||||
self.cc,
|
||||
self.bcc,
|
||||
self.subject,
|
||||
self.smtp_server,
|
||||
self.body[:128]
|
||||
)
|
||||
|
||||
message = text.MIMEText(self.body, _charset='utf-8')
|
||||
if not self.html_body:
|
||||
message = text.MIMEText(self.body, _charset='utf-8')
|
||||
else:
|
||||
message = multipart.MIMEMultipart('alternative')
|
||||
message.attach(text.MIMEText(self.body,
|
||||
'plain',
|
||||
_charset='utf-8'))
|
||||
message.attach(text.MIMEText(self.html_body,
|
||||
'html',
|
||||
_charset='utf-8'))
|
||||
message['Subject'] = header.Header(self.subject, 'utf-8')
|
||||
message['From'] = self.sender
|
||||
message['To'] = ', '.join(self.to)
|
||||
|
||||
if self.cc:
|
||||
message['cc'] = ', '.join(self.cc)
|
||||
|
||||
rcpt = self.cc + self.bcc + self.to
|
||||
|
||||
try:
|
||||
s = smtplib.SMTP(self.smtp_server)
|
||||
|
||||
@ -319,7 +340,7 @@ class SendEmailAction(actions.Action):
|
||||
s.login(self.sender, self.password)
|
||||
|
||||
s.sendmail(from_addr=self.sender,
|
||||
to_addrs=self.to,
|
||||
to_addrs=rcpt,
|
||||
msg=message.as_string())
|
||||
except (smtplib.SMTPException, IOError) as e:
|
||||
raise exc.ActionException("Failed to send an email message: %s"
|
||||
@ -330,9 +351,12 @@ class SendEmailAction(actions.Action):
|
||||
# to return a result.
|
||||
LOG.info(
|
||||
"Sending email message "
|
||||
"[from=%s, to=%s, subject=%s, using smtp=%s, body=%s...]",
|
||||
"[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
|
||||
"body=%s...]",
|
||||
self.sender,
|
||||
self.to,
|
||||
self.cc,
|
||||
self.bcc,
|
||||
self.subject,
|
||||
self.smtp_server,
|
||||
self.body[:128]
|
||||
|
@ -54,8 +54,11 @@ class SendEmailActionTest(base.BaseTest):
|
||||
super(SendEmailActionTest, self).setUp()
|
||||
self.to_addrs = ["dz@example.com", "deg@example.com",
|
||||
"xyz@example.com"]
|
||||
self.cc_addrs = ['copy@example.com']
|
||||
self.bcc_addrs = ['hidden_copy@example.com']
|
||||
self.subject = "Multi word subject с русскими буквами"
|
||||
self.body = "short multiline\nbody\nc русскими буквами"
|
||||
self.html_body = '<html><body><b>HTML</b> body</body></html>'
|
||||
|
||||
self.smtp_server = 'mail.example.com:25'
|
||||
self.from_addr = "bot@example.com"
|
||||
@ -66,8 +69,12 @@ class SendEmailActionTest(base.BaseTest):
|
||||
@testtools.skipIf(not LOCAL_SMTPD, "Setup local smtpd to run it")
|
||||
def test_send_email_real(self):
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, None, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
action.run(self.ctx)
|
||||
|
||||
@ -79,8 +86,12 @@ class SendEmailActionTest(base.BaseTest):
|
||||
self.smtp_password = 'secret'
|
||||
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, self.smtp_password, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=self.smtp_password,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
@ -89,8 +100,12 @@ class SendEmailActionTest(base.BaseTest):
|
||||
def test_with_mutli_to_addrs(self, smtp):
|
||||
smtp_password = "secret"
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, smtp_password, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=smtp_password,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
action.run(self.ctx)
|
||||
|
||||
@ -100,16 +115,24 @@ class SendEmailActionTest(base.BaseTest):
|
||||
smtp_password = "secret"
|
||||
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, to_addr,
|
||||
self.smtp_server, smtp_password, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=to_addr,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=smtp_password,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
action.run(self.ctx)
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_send_email(self, smtp):
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, None, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
@ -149,13 +172,141 @@ class SendEmailActionTest(base.BaseTest):
|
||||
base64.b64decode(message.get_payload()).decode('utf-8')
|
||||
)
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_send_email_with_cc(self, smtp):
|
||||
to_addrs = self.cc_addrs + self.to_addrs
|
||||
cc_addrs_str = ", ".join(self.cc_addrs)
|
||||
|
||||
action = std.SendEmailAction(
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
cc_addrs=self.cc_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
|
||||
smtp.assert_called_once_with(self.smtp_server)
|
||||
|
||||
sendmail = smtp.return_value.sendmail
|
||||
|
||||
self.assertTrue(sendmail.called, "should call sendmail")
|
||||
self.assertEqual(
|
||||
self.from_addr, sendmail.call_args[1]['from_addr'])
|
||||
self.assertEqual(
|
||||
to_addrs, sendmail.call_args[1]['to_addrs'])
|
||||
|
||||
message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
|
||||
|
||||
self.assertEqual(self.from_addr, message['from'])
|
||||
self.assertEqual(self.to_addrs_str, message['to'])
|
||||
self.assertEqual(cc_addrs_str, message['cc'])
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_send_email_with_bcc(self, smtp):
|
||||
to_addrs = self.bcc_addrs + self.to_addrs
|
||||
action = std.SendEmailAction(
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
bcc_addrs=self.bcc_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
|
||||
smtp.assert_called_once_with(self.smtp_server)
|
||||
|
||||
sendmail = smtp.return_value.sendmail
|
||||
|
||||
self.assertTrue(sendmail.called, "should call sendmail")
|
||||
self.assertEqual(
|
||||
self.from_addr, sendmail.call_args[1]['from_addr'])
|
||||
self.assertEqual(
|
||||
to_addrs, sendmail.call_args[1]['to_addrs'])
|
||||
|
||||
message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
|
||||
|
||||
self.assertEqual(self.from_addr, message['from'])
|
||||
self.assertEqual(self.to_addrs_str, message['to'])
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_send_email_html(self, smtp):
|
||||
action = std.SendEmailAction(
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body,
|
||||
html_body=self.html_body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
|
||||
smtp.assert_called_once_with(self.smtp_server)
|
||||
|
||||
sendmail = smtp.return_value.sendmail
|
||||
|
||||
self.assertTrue(sendmail.called, "should call sendmail")
|
||||
self.assertEqual(
|
||||
self.from_addr, sendmail.call_args[1]['from_addr'])
|
||||
self.assertEqual(
|
||||
self.to_addrs, sendmail.call_args[1]['to_addrs'])
|
||||
|
||||
message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
|
||||
|
||||
self.assertEqual(self.from_addr, message['from'])
|
||||
self.assertEqual(self.to_addrs_str, message['to'])
|
||||
if six.PY3:
|
||||
self.assertEqual(
|
||||
self.subject,
|
||||
decode_header(message['subject'])[0][0].decode('utf-8')
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
self.subject.decode('utf-8'),
|
||||
decode_header(message['subject'])[0][0].decode('utf-8')
|
||||
)
|
||||
body_payload = message.get_payload(0).get_payload()
|
||||
if six.PY3:
|
||||
self.assertEqual(
|
||||
self.body,
|
||||
base64.b64decode(body_payload).decode('utf-8')
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
self.body.decode('utf-8'),
|
||||
base64.b64decode(body_payload).decode('utf-8')
|
||||
)
|
||||
html_body_payload = message.get_payload(1).get_payload()
|
||||
if six.PY3:
|
||||
self.assertEqual(
|
||||
self.html_body,
|
||||
base64.b64decode(html_body_payload).decode('utf-8')
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
self.html_body.decode('utf-8'),
|
||||
base64.b64decode(html_body_payload).decode('utf-8')
|
||||
)
|
||||
|
||||
@mock.patch('smtplib.SMTP')
|
||||
def test_with_password(self, smtp):
|
||||
self.smtp_password = "secret"
|
||||
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, self.smtp_password, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=self.smtp_password,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
@ -173,8 +324,12 @@ class SendEmailActionTest(base.BaseTest):
|
||||
self.smtp_server = "wrong host"
|
||||
|
||||
action = std.SendEmailAction(
|
||||
self.from_addr, self.to_addrs,
|
||||
self.smtp_server, None, self.subject, self.body
|
||||
from_addr=self.from_addr,
|
||||
to_addrs=self.to_addrs,
|
||||
smtp_server=self.smtp_server,
|
||||
smtp_password=None,
|
||||
subject=self.subject,
|
||||
body=self.body
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -31,8 +31,9 @@ class ActionManagerTest(base.DbTestCase):
|
||||
self.assertEqual(http_action_input, std_http.input)
|
||||
|
||||
std_email_input = (
|
||||
"from_addr, to_addrs, smtp_server, "
|
||||
"smtp_password=null, subject=null, body=null"
|
||||
"from_addr, to_addrs, smtp_server, cc_addrs=null, "
|
||||
"bcc_addrs=null, smtp_password=null, subject=null, body=null, "
|
||||
"html_body=null"
|
||||
)
|
||||
|
||||
self.assertEqual(std_email_input, std_email.input)
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Improves std.email action with cc, bcc and html formatting.
|
Loading…
Reference in New Issue
Block a user