diff options
Diffstat (limited to 'cros_utils/email_sender.py')
-rwxr-xr-x | cros_utils/email_sender.py | 113 |
1 files changed, 109 insertions, 4 deletions
diff --git a/cros_utils/email_sender.py b/cros_utils/email_sender.py index 0019982e..6b8893ea 100755 --- a/cros_utils/email_sender.py +++ b/cros_utils/email_sender.py @@ -9,17 +9,35 @@ from __future__ import print_function -from email import encoders as Encoders -from email.mime.base import MIMEBase -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText +import base64 +import contextlib +import datetime import getpass +import json import os import smtplib import tempfile +from email import encoders as Encoders +from email.mime.base import MIMEBase +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText from cros_utils import command_executer +X20_PATH = '/google/data/rw/teams/c-compiler-chrome/prod_emails' + + +@contextlib.contextmanager +def AtomicallyWriteFile(file_path): + temp_path = file_path + '.in_progress' + try: + with open(temp_path, 'w') as f: + yield f + os.rename(temp_path, file_path) + except: + os.remove(temp_path) + raise + class EmailSender(object): """Utility class to send email through SMTP or SendGMR.""" @@ -31,6 +49,93 @@ class EmailSender(object): self.name = name self.content = content + def SendX20Email(self, + subject, + identifier, + well_known_recipients=(), + direct_recipients=(), + text_body=None, + html_body=None): + """Enqueues an email in our x20 outbox. + + These emails ultimately get sent by the machinery in + //depot/google3/googleclient/chrome/chromeos_toolchain/mailer/mail.go. This + kind of sending is intended for accounts that don't have smtp or gmr access + (e.g., role accounts), but can be used by anyone with x20 access. + + All emails are sent from `mdb.c-compiler-chrome+${identifier}@google.com`. + + Args: + subject: email subject. Must be nonempty. + identifier: email identifier, or the text that lands after the `+` in the + "From" email address. Must be nonempty. + well_known_recipients: a list of well-known recipients for the email. + These are translated into addresses by our mailer. + Current potential values for this are ('sheriff', + 'cwp-team', 'cros-team', 'mage'). Either this or + direct_recipients must be a nonempty list. + direct_recipients: @google.com emails to send addresses to. Either this + or well_known_recipients must be a nonempty list. + text_body: a 'text/plain' email body to send. Either this or html_body + must be a nonempty string. Both may be specified + html_body: a 'text/html' email body to send. Either this or text_body + must be a nonempty string. Both may be specified + """ + # `str`s act a lot like tuples/lists. Ensure that we're not accidentally + # iterating over one of those (or anything else that's sketchy, for that + # matter). + if not isinstance(well_known_recipients, (tuple, list)): + raise ValueError('`well_known_recipients` is unexpectedly a %s' % + type(well_known_recipients)) + + if not isinstance(direct_recipients, (tuple, list)): + raise ValueError( + '`direct_recipients` is unexpectedly a %s' % type(direct_recipients)) + + if not subject or not identifier: + raise ValueError('both `subject` and `identifier` must be nonempty') + + if not (well_known_recipients or direct_recipients): + raise ValueError('either `well_known_recipients` or `direct_recipients` ' + 'must be specified') + + for recipient in direct_recipients: + if not recipient.endswith('@google.com'): + raise ValueError('All recipients must end with @google.com') + + if not (text_body or html_body): + raise ValueError('either `text_body` or `html_body` must be specified') + + email_json = { + 'email_identifier': identifier, + 'subject': subject, + } + + if well_known_recipients: + email_json['well_known_recipients'] = well_known_recipients + + if direct_recipients: + email_json['direct_recipients'] = direct_recipients + + if text_body: + email_json['body'] = text_body + + if html_body: + email_json['html_body'] = html_body + + # The name of this has two parts: + # - An easily sortable time, to provide uniqueness and let our emailer + # send things in the order they were put into the outbox. + # - 64 bits of entropy, so two racing email sends don't clobber the same + # file. + now = datetime.datetime.utcnow().isoformat('T', 'seconds') + 'Z' + entropy = base64.urlsafe_b64encode(os.getrandom(8)) + entropy_str = entropy.rstrip(b'=').decode('utf-8') + result_path = os.path.join(X20_PATH, now + '_' + entropy_str + '.json') + + with AtomicallyWriteFile(result_path) as f: + json.dump(email_json, f) + def SendEmail(self, email_to, subject, |