summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-07-31 14:32:33 +0000
committerGerrit Code Review <review@openstack.org>2018-07-31 14:32:33 +0000
commitbef9aa5bc3de3125ad7e091aecfbd46948adf4d4 (patch)
tree68d8e87f74849b1093cdf6c4ac337ae9f8caea2d
parent443cd6fc119504925f9f46b2ede53aca777085b3 (diff)
parent3c430ef0a2ae54ae7e42fead98015f6a1d2740e9 (diff)
Merge "Improve std.email action"
-rw-r--r--doc/source/user/wf_lang_v2.rst3
-rw-r--r--mistral/actions/std_actions.py38
-rw-r--r--mistral/tests/unit/actions/test_std_email_action.py183
-rw-r--r--mistral/tests/unit/services/test_action_manager.py5
-rw-r--r--releasenotes/notes/improve_std_html_action-eca10df5bf934be8.yaml4
5 files changed, 210 insertions, 23 deletions
diff --git a/doc/source/user/wf_lang_v2.rst b/doc/source/user/wf_lang_v2.rst
index 09b9909..73b6f21 100644
--- a/doc/source/user/wf_lang_v2.rst
+++ b/doc/source/user/wf_lang_v2.rst
@@ -1056,8 +1056,11 @@ std.email
1056Sends an email message via SMTP protocol. 1056Sends an email message via SMTP protocol.
1057 1057
1058- **to_addrs** - Comma separated list of recipients. *Required*. 1058- **to_addrs** - Comma separated list of recipients. *Required*.
1059- **cc_addrs** - Comma separated list of CC recipients. *Optional*.
1060- **bcc_addrs** - Comma separated list of BCC recipients. *Optional*.
1059- **subject** - Subject of the message. *Optional*. 1061- **subject** - Subject of the message. *Optional*.
1060- **body** - Text containing message body. *Optional*. 1062- **body** - Text containing message body. *Optional*.
1063- **html_body** - Text containing the message in HTML format. *Optional*.
1061- **from_addr** - Sender email address. *Required*. 1064- **from_addr** - Sender email address. *Required*.
1062- **smtp_server** - SMTP server host name. *Required*. 1065- **smtp_server** - SMTP server host name. *Required*.
1063- **smtp_password** - SMTP server password. *Required*. 1066- **smtp_password** - SMTP server password. *Required*.
diff --git a/mistral/actions/std_actions.py b/mistral/actions/std_actions.py
index 91527e5..eab06e6 100644
--- a/mistral/actions/std_actions.py
+++ b/mistral/actions/std_actions.py
@@ -14,6 +14,7 @@
14# limitations under the License. 14# limitations under the License.
15 15
16from email import header 16from email import header
17from email.mime import multipart
17from email.mime import text 18from email.mime import text
18import json 19import json
19import smtplib 20import smtplib
@@ -277,15 +278,19 @@ class MistralHTTPAction(HTTPAction):
277 278
278 279
279class SendEmailAction(actions.Action): 280class SendEmailAction(actions.Action):
280 def __init__(self, from_addr, to_addrs, smtp_server, 281 def __init__(self, from_addr, to_addrs, smtp_server, cc_addrs=None,
281 smtp_password=None, subject=None, body=None): 282 bcc_addrs=None, smtp_password=None, subject=None, body=None,
283 html_body=None):
282 super(SendEmailAction, self).__init__() 284 super(SendEmailAction, self).__init__()
283 # TODO(dzimine): validate parameters 285 # TODO(dzimine): validate parameters
284 286
285 # Task invocation parameters. 287 # Task invocation parameters.
286 self.to = to_addrs 288 self.to = to_addrs
289 self.cc = cc_addrs or []
290 self.bcc = bcc_addrs or []
287 self.subject = subject or "<No subject>" 291 self.subject = subject or "<No subject>"
288 self.body = body or "<No body>" 292 self.body = body or "<No body>"
293 self.html_body = html_body
289 294
290 # Action provider settings. 295 # Action provider settings.
291 self.smtp_server = smtp_server 296 self.smtp_server = smtp_server
@@ -295,19 +300,35 @@ class SendEmailAction(actions.Action):
295 def run(self, context): 300 def run(self, context):
296 LOG.info( 301 LOG.info(
297 "Sending email message " 302 "Sending email message "
298 "[from=%s, to=%s, subject=%s, using smtp=%s, body=%s...]", 303 "[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
304 "body=%s...]",
299 self.sender, 305 self.sender,
300 self.to, 306 self.to,
307 self.cc,
308 self.bcc,
301 self.subject, 309 self.subject,
302 self.smtp_server, 310 self.smtp_server,
303 self.body[:128] 311 self.body[:128]
304 ) 312 )
305 313 if not self.html_body:
306 message = text.MIMEText(self.body, _charset='utf-8') 314 message = text.MIMEText(self.body, _charset='utf-8')
315 else:
316 message = multipart.MIMEMultipart('alternative')
317 message.attach(text.MIMEText(self.body,
318 'plain',
319 _charset='utf-8'))
320 message.attach(text.MIMEText(self.html_body,
321 'html',
322 _charset='utf-8'))
307 message['Subject'] = header.Header(self.subject, 'utf-8') 323 message['Subject'] = header.Header(self.subject, 'utf-8')
308 message['From'] = self.sender 324 message['From'] = self.sender
309 message['To'] = ', '.join(self.to) 325 message['To'] = ', '.join(self.to)
310 326
327 if self.cc:
328 message['cc'] = ', '.join(self.cc)
329
330 rcpt = self.cc + self.bcc + self.to
331
311 try: 332 try:
312 s = smtplib.SMTP(self.smtp_server) 333 s = smtplib.SMTP(self.smtp_server)
313 334
@@ -319,7 +340,7 @@ class SendEmailAction(actions.Action):
319 s.login(self.sender, self.password) 340 s.login(self.sender, self.password)
320 341
321 s.sendmail(from_addr=self.sender, 342 s.sendmail(from_addr=self.sender,
322 to_addrs=self.to, 343 to_addrs=rcpt,
323 msg=message.as_string()) 344 msg=message.as_string())
324 except (smtplib.SMTPException, IOError) as e: 345 except (smtplib.SMTPException, IOError) as e:
325 raise exc.ActionException("Failed to send an email message: %s" 346 raise exc.ActionException("Failed to send an email message: %s"
@@ -330,9 +351,12 @@ class SendEmailAction(actions.Action):
330 # to return a result. 351 # to return a result.
331 LOG.info( 352 LOG.info(
332 "Sending email message " 353 "Sending email message "
333 "[from=%s, to=%s, subject=%s, using smtp=%s, body=%s...]", 354 "[from=%s, to=%s, cc=%s, bcc=%s, subject=%s, using smtp=%s, "
355 "body=%s...]",
334 self.sender, 356 self.sender,
335 self.to, 357 self.to,
358 self.cc,
359 self.bcc,
336 self.subject, 360 self.subject,
337 self.smtp_server, 361 self.smtp_server,
338 self.body[:128] 362 self.body[:128]
diff --git a/mistral/tests/unit/actions/test_std_email_action.py b/mistral/tests/unit/actions/test_std_email_action.py
index 6eee21a..020d22d 100644
--- a/mistral/tests/unit/actions/test_std_email_action.py
+++ b/mistral/tests/unit/actions/test_std_email_action.py
@@ -54,8 +54,11 @@ class SendEmailActionTest(base.BaseTest):
54 super(SendEmailActionTest, self).setUp() 54 super(SendEmailActionTest, self).setUp()
55 self.to_addrs = ["dz@example.com", "deg@example.com", 55 self.to_addrs = ["dz@example.com", "deg@example.com",
56 "xyz@example.com"] 56 "xyz@example.com"]
57 self.cc_addrs = ['copy@example.com']
58 self.bcc_addrs = ['hidden_copy@example.com']
57 self.subject = "Multi word subject с русскими буквами" 59 self.subject = "Multi word subject с русскими буквами"
58 self.body = "short multiline\nbody\nc русскими буквами" 60 self.body = "short multiline\nbody\nc русскими буквами"
61 self.html_body = '<html><body><b>HTML</b> body</body></html>'
59 62
60 self.smtp_server = 'mail.example.com:25' 63 self.smtp_server = 'mail.example.com:25'
61 self.from_addr = "bot@example.com" 64 self.from_addr = "bot@example.com"
@@ -66,8 +69,12 @@ class SendEmailActionTest(base.BaseTest):
66 @testtools.skipIf(not LOCAL_SMTPD, "Setup local smtpd to run it") 69 @testtools.skipIf(not LOCAL_SMTPD, "Setup local smtpd to run it")
67 def test_send_email_real(self): 70 def test_send_email_real(self):
68 action = std.SendEmailAction( 71 action = std.SendEmailAction(
69 self.from_addr, self.to_addrs, 72 from_addr=self.from_addr,
70 self.smtp_server, None, self.subject, self.body 73 to_addrs=self.to_addrs,
74 smtp_server=self.smtp_server,
75 smtp_password=None,
76 subject=self.subject,
77 body=self.body
71 ) 78 )
72 action.run(self.ctx) 79 action.run(self.ctx)
73 80
@@ -79,8 +86,12 @@ class SendEmailActionTest(base.BaseTest):
79 self.smtp_password = 'secret' 86 self.smtp_password = 'secret'
80 87
81 action = std.SendEmailAction( 88 action = std.SendEmailAction(
82 self.from_addr, self.to_addrs, 89 from_addr=self.from_addr,
83 self.smtp_server, self.smtp_password, self.subject, self.body 90 to_addrs=self.to_addrs,
91 smtp_server=self.smtp_server,
92 smtp_password=self.smtp_password,
93 subject=self.subject,
94 body=self.body
84 ) 95 )
85 96
86 action.run(self.ctx) 97 action.run(self.ctx)
@@ -89,8 +100,12 @@ class SendEmailActionTest(base.BaseTest):
89 def test_with_mutli_to_addrs(self, smtp): 100 def test_with_mutli_to_addrs(self, smtp):
90 smtp_password = "secret" 101 smtp_password = "secret"
91 action = std.SendEmailAction( 102 action = std.SendEmailAction(
92 self.from_addr, self.to_addrs, 103 from_addr=self.from_addr,
93 self.smtp_server, smtp_password, self.subject, self.body 104 to_addrs=self.to_addrs,
105 smtp_server=self.smtp_server,
106 smtp_password=smtp_password,
107 subject=self.subject,
108 body=self.body
94 ) 109 )
95 action.run(self.ctx) 110 action.run(self.ctx)
96 111
@@ -100,16 +115,24 @@ class SendEmailActionTest(base.BaseTest):
100 smtp_password = "secret" 115 smtp_password = "secret"
101 116
102 action = std.SendEmailAction( 117 action = std.SendEmailAction(
103 self.from_addr, to_addr, 118 from_addr=self.from_addr,
104 self.smtp_server, smtp_password, self.subject, self.body 119 to_addrs=to_addr,
120 smtp_server=self.smtp_server,
121 smtp_password=smtp_password,
122 subject=self.subject,
123 body=self.body
105 ) 124 )
106 action.run(self.ctx) 125 action.run(self.ctx)
107 126
108 @mock.patch('smtplib.SMTP') 127 @mock.patch('smtplib.SMTP')
109 def test_send_email(self, smtp): 128 def test_send_email(self, smtp):
110 action = std.SendEmailAction( 129 action = std.SendEmailAction(
111 self.from_addr, self.to_addrs, 130 from_addr=self.from_addr,
112 self.smtp_server, None, self.subject, self.body 131 to_addrs=self.to_addrs,
132 smtp_server=self.smtp_server,
133 smtp_password=None,
134 subject=self.subject,
135 body=self.body
113 ) 136 )
114 137
115 action.run(self.ctx) 138 action.run(self.ctx)
@@ -150,12 +173,140 @@ class SendEmailActionTest(base.BaseTest):
150 ) 173 )
151 174
152 @mock.patch('smtplib.SMTP') 175 @mock.patch('smtplib.SMTP')
176 def test_send_email_with_cc(self, smtp):
177 to_addrs = self.cc_addrs + self.to_addrs
178 cc_addrs_str = ", ".join(self.cc_addrs)
179
180 action = std.SendEmailAction(
181 from_addr=self.from_addr,
182 to_addrs=self.to_addrs,
183 cc_addrs=self.cc_addrs,
184 smtp_server=self.smtp_server,
185 smtp_password=None,
186 subject=self.subject,
187 body=self.body
188 )
189
190 action.run(self.ctx)
191
192 smtp.assert_called_once_with(self.smtp_server)
193
194 sendmail = smtp.return_value.sendmail
195
196 self.assertTrue(sendmail.called, "should call sendmail")
197 self.assertEqual(
198 self.from_addr, sendmail.call_args[1]['from_addr'])
199 self.assertEqual(
200 to_addrs, sendmail.call_args[1]['to_addrs'])
201
202 message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
203
204 self.assertEqual(self.from_addr, message['from'])
205 self.assertEqual(self.to_addrs_str, message['to'])
206 self.assertEqual(cc_addrs_str, message['cc'])
207
208 @mock.patch('smtplib.SMTP')
209 def test_send_email_with_bcc(self, smtp):
210 to_addrs = self.bcc_addrs + self.to_addrs
211 action = std.SendEmailAction(
212 from_addr=self.from_addr,
213 to_addrs=self.to_addrs,
214 bcc_addrs=self.bcc_addrs,
215 smtp_server=self.smtp_server,
216 smtp_password=None,
217 subject=self.subject,
218 body=self.body
219 )
220
221 action.run(self.ctx)
222
223 smtp.assert_called_once_with(self.smtp_server)
224
225 sendmail = smtp.return_value.sendmail
226
227 self.assertTrue(sendmail.called, "should call sendmail")
228 self.assertEqual(
229 self.from_addr, sendmail.call_args[1]['from_addr'])
230 self.assertEqual(
231 to_addrs, sendmail.call_args[1]['to_addrs'])
232
233 message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
234
235 self.assertEqual(self.from_addr, message['from'])
236 self.assertEqual(self.to_addrs_str, message['to'])
237
238 @mock.patch('smtplib.SMTP')
239 def test_send_email_html(self, smtp):
240 action = std.SendEmailAction(
241 from_addr=self.from_addr,
242 to_addrs=self.to_addrs,
243 smtp_server=self.smtp_server,
244 smtp_password=None,
245 subject=self.subject,
246 body=self.body,
247 html_body=self.html_body
248 )
249
250 action.run(self.ctx)
251
252 smtp.assert_called_once_with(self.smtp_server)
253
254 sendmail = smtp.return_value.sendmail
255
256 self.assertTrue(sendmail.called, "should call sendmail")
257 self.assertEqual(
258 self.from_addr, sendmail.call_args[1]['from_addr'])
259 self.assertEqual(
260 self.to_addrs, sendmail.call_args[1]['to_addrs'])
261
262 message = parser.Parser().parsestr(sendmail.call_args[1]['msg'])
263
264 self.assertEqual(self.from_addr, message['from'])
265 self.assertEqual(self.to_addrs_str, message['to'])
266 if six.PY3:
267 self.assertEqual(
268 self.subject,
269 decode_header(message['subject'])[0][0].decode('utf-8')
270 )
271 else:
272 self.assertEqual(
273 self.subject.decode('utf-8'),
274 decode_header(message['subject'])[0][0].decode('utf-8')
275 )
276 body_payload = message.get_payload(0).get_payload()
277 if six.PY3:
278 self.assertEqual(
279 self.body,
280 base64.b64decode(body_payload).decode('utf-8')
281 )
282 else:
283 self.assertEqual(
284 self.body.decode('utf-8'),
285 base64.b64decode(body_payload).decode('utf-8')
286 )
287 html_body_payload = message.get_payload(1).get_payload()
288 if six.PY3:
289 self.assertEqual(
290 self.html_body,
291 base64.b64decode(html_body_payload).decode('utf-8')
292 )
293 else:
294 self.assertEqual(
295 self.html_body.decode('utf-8'),
296 base64.b64decode(html_body_payload).decode('utf-8')
297 )
298
299 @mock.patch('smtplib.SMTP')
153 def test_with_password(self, smtp): 300 def test_with_password(self, smtp):
154 self.smtp_password = "secret" 301 self.smtp_password = "secret"
155 302
156 action = std.SendEmailAction( 303 action = std.SendEmailAction(
157 self.from_addr, self.to_addrs, 304 from_addr=self.from_addr,
158 self.smtp_server, self.smtp_password, self.subject, self.body 305 to_addrs=self.to_addrs,
306 smtp_server=self.smtp_server,
307 smtp_password=self.smtp_password,
308 subject=self.subject,
309 body=self.body
159 ) 310 )
160 311
161 action.run(self.ctx) 312 action.run(self.ctx)
@@ -173,8 +324,12 @@ class SendEmailActionTest(base.BaseTest):
173 self.smtp_server = "wrong host" 324 self.smtp_server = "wrong host"
174 325
175 action = std.SendEmailAction( 326 action = std.SendEmailAction(
176 self.from_addr, self.to_addrs, 327 from_addr=self.from_addr,
177 self.smtp_server, None, self.subject, self.body 328 to_addrs=self.to_addrs,
329 smtp_server=self.smtp_server,
330 smtp_password=None,
331 subject=self.subject,
332 body=self.body
178 ) 333 )
179 334
180 try: 335 try:
diff --git a/mistral/tests/unit/services/test_action_manager.py b/mistral/tests/unit/services/test_action_manager.py
index 2996df9..1981342 100644
--- a/mistral/tests/unit/services/test_action_manager.py
+++ b/mistral/tests/unit/services/test_action_manager.py
@@ -31,8 +31,9 @@ class ActionManagerTest(base.DbTestCase):
31 self.assertEqual(http_action_input, std_http.input) 31 self.assertEqual(http_action_input, std_http.input)
32 32
33 std_email_input = ( 33 std_email_input = (
34 "from_addr, to_addrs, smtp_server, " 34 "from_addr, to_addrs, smtp_server, cc_addrs=null, "
35 "smtp_password=null, subject=null, body=null" 35 "bcc_addrs=null, smtp_password=null, subject=null, body=null, "
36 "html_body=null"
36 ) 37 )
37 38
38 self.assertEqual(std_email_input, std_email.input) 39 self.assertEqual(std_email_input, std_email.input)
diff --git a/releasenotes/notes/improve_std_html_action-eca10df5bf934be8.yaml b/releasenotes/notes/improve_std_html_action-eca10df5bf934be8.yaml
new file mode 100644
index 0000000..6d8724d
--- /dev/null
+++ b/releasenotes/notes/improve_std_html_action-eca10df5bf934be8.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - |
4 Improves std.email action with cc, bcc and html formatting.