Merge "Improve std.email action"

This commit is contained in:
Zuul 2018-07-31 14:32:33 +00:00 committed by Gerrit Code Review
commit bef9aa5bc3
5 changed files with 210 additions and 23 deletions

View File

@ -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*.

View File

@ -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]

View File

@ -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:

View File

@ -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)

View File

@ -0,0 +1,4 @@
---
features:
- |
Improves std.email action with cc, bcc and html formatting.