Repository: fedora-infra/bodhi

Issue:
Traceback from bodhi-dequeue-stable
@nirik sent me a traceback that seems to be coming from ```bodhi-dequeue-stable```:

```
Unable to send mail
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/bodhi/server/mail.py", line
391, in _send_mail
    smtp.sendmail(from_addr, [to_addr], body)
  File "/usr/lib64/python3.7/smtplib.py", line 861, in sendmail
    (code, resp) = self.mail(from_addr, esmtp_opts)
  File "/usr/lib64/python3.7/smtplib.py", line 534, in mail
    self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
  File "/usr/lib64/python3.7/smtplib.py", line 150, in quoteaddr
    displayname, addr = email.utils.parseaddr(addrstring)
  File "/usr/lib64/python3.7/email/utils.py", line 212, in parseaddr
    addrs = _AddressList(addr).addresslist
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 500, in __init__
    self.addresslist = self.getaddrlist()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 253, in getaddrlist
    ad = self.getaddress()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 263, in getaddress
    self.gotonext()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 236, in gotonext
    if self.field[self.pos] in self.LWS + '\n\r':
TypeError: 'in <string>' requires string as left operand, not int
```


Discussion:
It seems the composer also crashes with a similar if not identical traceback:

```
[2018-11-20 14:34:25][fedmsg.consumers    INFO]f28-updates Sending notifications
[2018-11-20 14:34:26][fedmsg.consumers    INFO]f28-updates Sending stable update announcements
[2018-11-20 14:34:27][bodhi.server    INFO]f28-updates Sending mail to b'package-announce@fpo': b'Fedora 28 Update: slibtool-0.5.28-1.fc28'

[2018-11-20 14:34:27][bodhi.server   ERROR]f28-updates Unable to send mail
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/bodhi/server/mail.py", line 391, in _send_mail
    smtp.sendmail(from_addr, [to_addr], body)
  File "/usr/lib64/python3.7/smtplib.py", line 861, in sendmail
    (code, resp) = self.mail(from_addr, esmtp_opts)
  File "/usr/lib64/python3.7/smtplib.py", line 534, in mail
    self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
  File "/usr/lib64/python3.7/smtplib.py", line 150, in quoteaddr
    displayname, addr = email.utils.parseaddr(addrstring)
  File "/usr/lib64/python3.7/email/utils.py", line 212, in parseaddr
    addrs = _AddressList(addr).addresslist
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 500, in __init__
    self.addresslist = self.getaddrlist()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 253, in getaddrlist
    ad = self.getaddress()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 263, in getaddress
    self.gotonext()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 236, in gotonext
    if self.field[self.pos] in self.LWS + '\n\r':
TypeError: 'in <string>' requires string as left operand, not int
```
This seems relevant: https://bugs.python.org/issue20084
Well here's a simple reproducer:

```
$ python3
Python 3.7.1 (default, Nov  5 2018, 14:07:04) 
[GCC 8.2.1 20181011 (Red Hat 8.2.1-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import smtplib
>>> e = smtplib.SMTP('my.smtp.server')
>>> e.sendmail(b'package-announce@fedoraproject.org', [b'me@mydomain.com'], b'Subject: sup\n\nhello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.7/smtplib.py", line 861, in sendmail
    (code, resp) = self.mail(from_addr, esmtp_opts)
  File "/usr/lib64/python3.7/smtplib.py", line 534, in mail
    self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
  File "/usr/lib64/python3.7/smtplib.py", line 150, in quoteaddr
    displayname, addr = email.utils.parseaddr(addrstring)
  File "/usr/lib64/python3.7/email/utils.py", line 212, in parseaddr
    addrs = _AddressList(addr).addresslist
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 500, in __init__
    self.addresslist = self.getaddrlist()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 253, in getaddrlist
    ad = self.getaddress()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 263, in getaddress
    self.gotonext()
  File "/usr/lib64/python3.7/email/_parseaddr.py", line 236, in gotonext
    if self.field[self.pos] in self.LWS + '\n\r':
TypeError: 'in <string>' requires string as left operand, not int
```
The [docs](https://docs.python.org/3.7/library/smtplib.html#smtplib.SMTP.sendmail) state that the from_addr and to_addr should be strings, so perhaps it is wrong for us to convert them to ```bytes```.

Resolving patch:
diff --git a/bodhi/server/mail.py b/bodhi/server/mail.py
--- a/bodhi/server/mail.py
+++ b/bodhi/server/mail.py
@@ -403,8 +403,8 @@ def send_mail(from_addr, to_addr, subject, body_text, headers=None):
     Send an e-mail.
 
     Args:
-        from_addr (basestring): The address to use in the From: header.
-        to_addr (basestring): The address to send the e-mail to.
+        from_addr (str): The address to use in the From: header.
+        to_addr (str): The address to send the e-mail to.
         subject (basestring): The subject of the e-mail.
         body_text (basestring): The body of the e-mail to be sent.
         headers (dict or None): A mapping of header fields to values to be included in the e-mail,
@@ -418,12 +418,10 @@ def send_mail(from_addr, to_addr, subject, body_text, headers=None):
     if to_addr in config.get('exclude_mail'):
         return
 
-    from_addr = to_bytes(from_addr)
-    to_addr = to_bytes(to_addr)
     subject = to_bytes(subject)
     body_text = to_bytes(body_text)
 
-    msg = [b'From: %s' % from_addr, b'To: %s' % to_addr]
+    msg = [b'From: %s' % to_bytes(from_addr), b'To: %s' % to_bytes(to_addr)]
     if headers:
         for key, value in headers.items():
             msg.append(b'%s: %s' % (to_bytes(key), to_bytes(value)))
@@ -440,11 +438,11 @@ def send(to, msg_type, update, sender=None, agent=None):
     Send an update notification email to a given recipient.
 
     Args:
-        to (iterable): An iterable of e-mail addresses to send an update e-mail to.
+        to (iterable): An iterable strs of e-mail addresses to send an update e-mail to.
         msg_type (basestring): The message template to use. Should be one of the keys in the
             MESSAGES template.
         update (bodhi.server.models.Update): The Update we are mailing people about.
-        sender (basestring or None): The address to use in the From: header. If None, the
+        sender (str or None): The address to use in the From: header. If None, the
             "bodhi_email" setting will be used as the From: header.
         agent (basestring): The username that performed the action that generated this e-mail.
     """


Test patch:
diff --git a/bodhi/tests/server/consumers/test_masher.py b/bodhi/tests/server/consumers/test_masher.py
--- a/bodhi/tests/server/consumers/test_masher.py
+++ b/bodhi/tests/server/consumers/test_masher.py
@@ -33,7 +33,6 @@
 
 from click import testing
 import mock
-from kitchen.text.converters import to_bytes
 import six
 import six.moves.urllib.parse as urlparse
 from six.moves.urllib.error import HTTPError, URLError
@@ -601,8 +600,8 @@ def test_testing_digest(self, mail, *args):
 
 """ % time.strftime('%Y'))
 
-        mail.assert_called_with(to_bytes(config.get('bodhi_email')),
-                                to_bytes(config.get('fedora_test_announce_list')),
+        mail.assert_called_with(config.get('bodhi_email'),
+                                config.get('fedora_test_announce_list'),
                                 mock.ANY)
         assert len(mail.mock_calls) == 2, len(mail.mock_calls)
         body = mail.mock_calls[1][1][2]
@@ -3341,9 +3340,8 @@ def test_critpath_updates(self, SMTP):
         sendmail = SMTP.return_value.sendmail
         self.assertEqual(sendmail.call_count, 1)
         args = sendmail.mock_calls[0][1]
-        self.assertEqual(args[0].decode('utf-8'), config['bodhi_email'])
-        self.assertEqual([c.decode('utf-8') for c in args[1]],
-                         [config['fedora_test_announce_list']])
+        self.assertEqual(args[0], config['bodhi_email'])
+        self.assertEqual(args[1], [config['fedora_test_announce_list']])
         self.assertTrue(
             'The following Fedora 17 Critical Path updates have yet to be approved:\n Age URL\n'
             in args[2].decode('utf-8'))
@@ -3373,9 +3371,8 @@ def test_security_updates(self, SMTP):
         sendmail = SMTP.return_value.sendmail
         self.assertEqual(sendmail.call_count, 1)
         args = sendmail.mock_calls[0][1]
-        self.assertEqual(args[0].decode('utf-8'), config['bodhi_email'])
-        self.assertEqual([c.decode('utf-8') for c in args[1]],
-                         [config['fedora_test_announce_list']])
+        self.assertEqual(args[0], config['bodhi_email'])
+        self.assertEqual(args[1], [config['fedora_test_announce_list']])
         self.assertTrue(
             'The following Fedora 17 Security updates need testing:\n Age  URL\n'
             in args[2].decode('utf-8'))
diff --git a/bodhi/tests/server/test_mail.py b/bodhi/tests/server/test_mail.py
--- a/bodhi/tests/server/test_mail.py
+++ b/bodhi/tests/server/test_mail.py
@@ -210,11 +210,12 @@ def test_msg_type_new(self, SMTP):
         self.assertEqual(sendmail.call_count, 1)
         sendmail = sendmail.mock_calls[0]
         self.assertEqual(len(sendmail[1]), 3)
-        self.assertEqual(sendmail[1][0], b'updates@fedoraproject.org')
-        self.assertEqual(sendmail[1][1], [b'bowlofeggs@example.com'])
-        self.assertTrue('Message-ID: <bodhi-update-{}-{}-{}@{}>'.format(
+        self.assertEqual(sendmail[1][0], 'updates@fedoraproject.org')
+        self.assertEqual(sendmail[1][1], ['bowlofeggs@example.com'])
+        expected_body = 'Message-ID: <bodhi-update-{}-{}-{}@{}>'.format(
             update.id, update.user.name, update.release.name,
-            config.config.get('message_id_email_domain')) in str(sendmail[1][2]))
+            config.config.get('message_id_email_domain'))
+        self.assertTrue(expected_body.encode('utf-8') in sendmail[1][2])
 
     @mock.patch.dict('bodhi.server.mail.config', {'smtp_server': 'smtp.example.com'})
     @mock.patch('bodhi.server.mail.smtplib.SMTP')
@@ -227,8 +228,8 @@ def test_nvr_in_subject(self, SMTP):
         SMTP.assert_called_once_with('smtp.example.com')
         sendmail = SMTP.return_value.sendmail
         self.assertEqual(sendmail.call_count, 1)
-        self.assertEqual(sendmail.mock_calls[0][1][0], b'updates@fedoraproject.org')
-        self.assertEqual(sendmail.mock_calls[0][1][1], [b'fake@news.com'])
+        self.assertEqual(sendmail.mock_calls[0][1][0], 'updates@fedoraproject.org')
+        self.assertEqual(sendmail.mock_calls[0][1][1], ['fake@news.com'])
         self.assertTrue(b'X-Bodhi-Update-Title: bodhi-2.0-1.fc17' in sendmail.mock_calls[0][1][2])
         self.assertTrue(
             b'Subject: [Fedora Update] [comment] bodhi-2.0-1.fc17' in sendmail.mock_calls[0][1][2])
@@ -271,7 +272,7 @@ def test_headers(self, SMTP):
 
         SMTP.assert_called_once_with('smtp.fp.o')
         smtp.sendmail.assert_called_once_with(
-            b'bodhi@example.com', [b'bowlofeggs@example.com'],
+            'bodhi@example.com', ['bowlofeggs@example.com'],
             (b'From: bodhi@example.com\r\nTo: bowlofeggs@example.com\r\nBodhi-Is: Great\r\n'
              b'X-Bodhi: fedoraproject.org\r\nSubject: R013X\r\n\r\nWant a c00l w@tch?'))
 
@@ -297,7 +298,7 @@ def test_send(self, SMTP):
 
         SMTP.assert_called_once_with('smtp.fp.o')
         smtp.sendmail.assert_called_once_with(
-            b'bodhi@example.com', [b'bowlofeggs@example.com'],
+            'bodhi@example.com', ['bowlofeggs@example.com'],
             (b'From: bodhi@example.com\r\nTo: bowlofeggs@example.com\r\n'
              b'X-Bodhi: fedoraproject.org\r\nSubject: R013X\r\n\r\nWant a c00l w@tch?'))
 
@@ -315,8 +316,8 @@ def test_send_releng(self, SMTP):
 
         SMTP.assert_called_once_with('smtp.fp.o')
         smtp.sendmail.assert_called_once_with(
-            config.config.get('bodhi_email').encode('utf-8'),
-            [config.config.get('release_team_address').encode('utf-8')],
+            config.config.get('bodhi_email'),
+            [config.config.get('release_team_address')],
             ('From: {}\r\nTo: {}\r\nX-Bodhi: fedoraproject.org\r\nSubject: sup\r\n\r\nr u '
              'ready 2 upd8').format(
                 config.config.get('bodhi_email'), config.config.get('release_team_address')).encode(
diff --git a/bodhi/tests/server/test_models.py b/bodhi/tests/server/test_models.py
--- a/bodhi/tests/server/test_models.py
+++ b/bodhi/tests/server/test_models.py
@@ -3402,8 +3402,8 @@ def test_send_update_notice_status_testing(self, publish, SMTP):
             config['bodhi_email'], config['{}_test_announce_list'.format(release_name)],
             config['default_email_domain'], self.obj.builds[0].nvr, body)
         SMTP.return_value.sendmail.assert_called_once_with(
-            config['bodhi_email'].encode('utf-8'),
-            [config['{}_test_announce_list'.format(release_name)].encode('utf-8')],
+            config['bodhi_email'],
+            [config['{}_test_announce_list'.format(release_name)]],
             msg.encode('utf-8'))
 
     def test_check_requirements_empty(self):