aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Ryabitsev <konstantin@linuxfoundation.org>2021-10-19 17:34:17 -0400
committerKonstantin Ryabitsev <konstantin@linuxfoundation.org>2021-10-19 17:34:17 -0400
commit6a212b5524903c170d420763c9c08a5444745281 (patch)
tree305c879817044b12b43bf916c20b50ff28418ef8
parenta57ec40ef6d57b5aea62fbd1c814175d1da3fd23 (diff)
downloadb4-6a212b5524903c170d420763c9c08a5444745281.tar.gz
Initial implementation of native mail sending
I'm felling comfortable that "b4 ty" is sufficiently mature at this point to implement sending thank-yous directly. This is only the initial implementation that covers only the very basic parts of git's sendemail configuration options, but this should actually cover 90% of cases if not more. One important caveat -- I moved the "b4 ty -s" flag to be "b4 ty -t" in order to disambiguate it from the capital -S (that actually does the sending). Since "b4 ty" is still marked as an experimental feature, I feel we can do this without much impact. Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
-rw-r--r--b4/__init__.py53
-rw-r--r--b4/command.py6
-rw-r--r--b4/ty.py96
3 files changed, 125 insertions, 30 deletions
diff --git a/b4/__init__.py b/b4/__init__.py
index f8766d5..adbcf7d 100644
--- a/b4/__init__.py
+++ b/b4/__init__.py
@@ -130,6 +130,8 @@ DEFAULT_CONFIG = {
# git-config for gpg.program, and if that's not set,
# we'll use "gpg" and hope for the better
'gpgbin': None,
+ # When sending mail, use this sendemail identity configuration
+ 'sendemail-identity': None,
}
# This is where we store actual config
@@ -2480,3 +2482,54 @@ def read_template(tptfile):
continue
tpt += line
return tpt
+
+
+def get_smtp(identity: Optional[str] = None):
+ import smtplib
+ if identity:
+ sconfig = get_config_from_git(rf'sendemail\.{identity}\..*')
+ sectname = f'sendemail.{identity}'
+ else:
+ sconfig = get_config_from_git(rf'sendemail\..*')
+ sectname = 'sendemail'
+ if not len(sconfig):
+ raise smtplib.SMTPException('Unable to find %s settings in any applicable git config' % sectname)
+
+ # Limited support for smtp settings to begin with, but should cover the vast majority of cases
+ fromaddr = sconfig.get('from')
+ server = sconfig.get('smtpserver', 'localhost')
+ port = sconfig.get('smtpserverport', 0)
+ try:
+ port = int(port)
+ except ValueError:
+ raise smtplib.SMTPException('Invalid smtpport entry in %s' % sectname)
+
+ encryption = sconfig.get('smtpencryption')
+ # We only authenticate if we have encryption
+ if encryption:
+ if encryption in ('tls', 'starttls'):
+ # We do startssl
+ smtp = smtplib.SMTP(server, port)
+ # Introduce ourselves
+ smtp.ehlo()
+ # Start encryption
+ smtp.starttls()
+ # Introduce ourselves again to get new criteria
+ smtp.ehlo()
+ elif encryption in ('ssl', 'smtps'):
+ # We do TLS from the get-go
+ smtp = smtplib.SMTP_SSL(server, port)
+ else:
+ raise smtplib.SMTPException('Unclear what to do with smtpencryption=%s' % encryption)
+
+ # If we got to this point, we should do authentication.
+ auser = sconfig.get('smtpuser')
+ apass = sconfig.get('smtppass')
+ if auser and apass:
+ # Let any exceptions bubble up
+ smtp.login(auser, apass)
+ else:
+ # We assume you know what you're doing if you don't need encryption
+ smtp = smtplib.SMTP(server, port)
+
+ return smtp, fromaddr
diff --git a/b4/command.py b/b4/command.py
index 199d0c2..9d364dd 100644
--- a/b4/command.py
+++ b/b4/command.py
@@ -199,7 +199,7 @@ def cmd():
help='Write thanks files into this dir (default=.)')
sp_ty.add_argument('-l', '--list', action='store_true', default=False,
help='List pull requests and patch series you have retrieved')
- sp_ty.add_argument('-s', '--send', default=None,
+ sp_ty.add_argument('-t', '--thank-for', default=None,
help='Generate thankyous for specific entries from -l (e.g.: 1,3-5,7-; or "all")')
sp_ty.add_argument('-d', '--discard', default=None,
help='Discard specific messages from -l (e.g.: 1,3-5,7-; or "all")')
@@ -209,6 +209,10 @@ def cmd():
help='The branch to check against, instead of current')
sp_ty.add_argument('--since', default='1.week',
help='The --since option to use when auto-matching patches (default=1.week)')
+ sp_ty.add_argument('-S', '--send-email', action='store_true', dest='sendemail', default=False,
+ help='Send email instead of writing out .thanks files')
+ sp_ty.add_argument('--dry-run', action='store_true', dest='dryrun', default=False,
+ help='Print out emails instead of sending them')
sp_ty.set_defaults(func=cmd_ty)
# b4 diff
diff --git a/b4/ty.py b/b4/ty.py
index fbae20c..b790007 100644
--- a/b4/ty.py
+++ b/b4/ty.py
@@ -374,19 +374,30 @@ def auto_thankanator(cmdargs):
sys.exit(0)
logger.info('---')
- send_messages(applied, cmdargs.gitdir, cmdargs.outdir, wantbranch, since=cmdargs.since)
+ send_messages(applied, wantbranch, cmdargs)
sys.exit(0)
-def send_messages(listing, gitdir, outdir, branch, since='1.week'):
- # Not really sending, but writing them out to be sent on your own
- # We'll probably gain ability to send these once the feature is
- # more mature and we're less likely to mess things up
- datadir = b4.get_data_dir()
+def send_messages(listing, branch, cmdargs):
logger.info('Generating %s thank-you letters', len(listing))
- # Check if the outdir exists and if it has any .thanks files in it
- if not os.path.exists(outdir):
- os.mkdir(outdir)
+ gitdir = cmdargs.gitdir
+ datadir = b4.get_data_dir()
+ fromaddr = None
+ if cmdargs.sendemail:
+ # See if we have sendemail-identity set
+ config = b4.get_main_config()
+ identity = config.get('sendemail-identity')
+ try:
+ smtp, fromaddr = b4.get_smtp(identity)
+ except Exception as ex: # noqa
+ logger.critical('Failed to configure the smtp connection:')
+ logger.critical(ex)
+ sys.exit(1)
+ else:
+ # We write .thanks notes
+ # Check if the outdir exists and if it has any .thanks files in it
+ if not os.path.exists(cmdargs.outdir):
+ os.mkdir(cmdargs.outdir)
usercfg = b4.get_user_config()
# Do we have a .signature file?
@@ -399,10 +410,6 @@ def send_messages(listing, gitdir, outdir, branch, since='1.week'):
outgoing = 0
for jsondata in listing:
- slug_from = re.sub(r'\W', '_', jsondata['fromemail'])
- slug_subj = re.sub(r'\W', '_', jsondata['subject'])
- slug = '%s_%s' % (slug_from.lower(), slug_subj.lower())
- slug = re.sub(r'_+', '_', slug)
jsondata['myname'] = usercfg['name']
jsondata['myemail'] = usercfg['email']
jsondata['signature'] = signature
@@ -411,29 +418,60 @@ def send_messages(listing, gitdir, outdir, branch, since='1.week'):
msg = generate_pr_thanks(gitdir, jsondata, branch)
else:
# This is a patch series
- msg = generate_am_thanks(gitdir, jsondata, branch, since)
+ msg = generate_am_thanks(gitdir, jsondata, branch, cmdargs.since)
if msg is None:
continue
outgoing += 1
- outfile = os.path.join(outdir, '%s.thanks' % slug)
- logger.info(' Writing: %s', outfile)
msg.set_charset('utf-8')
msg.replace_header('Content-Transfer-Encoding', '8bit')
- with open(outfile, 'w') as fh:
- fh.write(msg.as_string(policy=b4.emlpolicy))
- logger.debug('Cleaning up: %s', jsondata['trackfile'])
- fullpath = os.path.join(datadir, jsondata['trackfile'])
- os.rename(fullpath, '%s.sent' % fullpath)
+ if cmdargs.sendemail:
+ if not fromaddr:
+ fromaddr = jsondata['myemail']
+ if cmdargs.dryrun:
+ logger.info('--- DRYRUN: message follows ---')
+ emldata = msg.as_string(policy=b4.emlpolicy)
+ logger.info('\t' + emldata.replace('\n', '\n\t'))
+ logger.info('--- DRYRUN: message ends ---')
+ else:
+ alldests = email.utils.getaddresses([str(x) for x in msg.get_all('to', [])])
+ alldests += email.utils.getaddresses([str(x) for x in msg.get_all('cc', [])])
+ sendto = {x[1] for x in alldests}
+ logger.info('Sending: %s', msg.get('subject'))
+ mypolicy = email.policy.EmailPolicy(utf8=True, cte_type='8bit')
+ smtp.sendmail(fromaddr, sendto, msg.as_string(policy=mypolicy)) # noqa
+ else:
+ slug_from = re.sub(r'\W', '_', jsondata['fromemail'])
+ slug_subj = re.sub(r'\W', '_', jsondata['subject'])
+ slug = '%s_%s' % (slug_from.lower(), slug_subj.lower())
+ slug = re.sub(r'_+', '_', slug)
+ outfile = os.path.join(cmdargs.outdir, '%s.thanks' % slug)
+ logger.info(' Writing: %s', outfile)
+ with open(outfile, 'w') as fh:
+ fh.write(msg.as_string(policy=b4.emlpolicy))
+ if cmdargs.dryrun:
+ logger.info('Dry run, preserving tracked series.')
+ else:
+ logger.debug('Cleaning up: %s', jsondata['trackfile'])
+ fullpath = os.path.join(datadir, jsondata['trackfile'])
+ os.rename(fullpath, '%s.sent' % fullpath)
+
logger.info('---')
if not outgoing:
logger.info('No thanks necessary.')
return
- logger.debug('Wrote %s thank-you letters', outgoing)
- logger.info('You can now run:')
- logger.info(' git send-email %s/*.thanks', outdir)
+ if cmdargs.sendemail:
+ if cmdargs.dryrun:
+ logger.info('DRYRUN: generated %s thank-you letters', outgoing)
+ else:
+ logger.info('Sent %s thank-you letters', outgoing)
+ smtp.quit()
+ else:
+ logger.debug('Wrote %s thank-you letters', outgoing)
+ logger.info('You can now run:')
+ logger.info(' git send-email %s/*.thanks', cmdargs.outdir)
def list_tracked():
@@ -465,7 +503,7 @@ def write_tracked(tracked):
counter += 1
-def send_selected(cmdargs):
+def thank_selected(cmdargs):
tracked = list_tracked()
if not len(tracked):
logger.info('Nothing to do')
@@ -494,7 +532,7 @@ def send_selected(cmdargs):
sys.exit(0)
wantbranch = get_wanted_branch(cmdargs)
- send_messages(listing, cmdargs.gitdir, cmdargs.outdir, wantbranch, cmdargs.since)
+ send_messages(listing, wantbranch, cmdargs)
sys.exit(0)
@@ -621,9 +659,9 @@ def main(cmdargs):
if cmdargs.auto:
check_stale_thanks(cmdargs.outdir)
auto_thankanator(cmdargs)
- elif cmdargs.send:
+ elif cmdargs.thank:
check_stale_thanks(cmdargs.outdir)
- send_selected(cmdargs)
+ thank_selected(cmdargs)
elif cmdargs.discard:
discard_selected(cmdargs)
else:
@@ -634,4 +672,4 @@ def main(cmdargs):
write_tracked(tracked)
logger.info('---')
logger.info('You can send them using number ranges, e.g:')
- logger.info(' b4 ty -s 1-3,5,7-')
+ logger.info(' b4 ty -t 1-3,5,7-')