On 12/5/21 3:19 am, Alex White wrote: > This adds the improved mailer.py script from rtems-tools. > > Closes #4388 > --- > source-builder/sb/mailer.py | 194 ++++++++++++++++++++++++++------ > source-builder/sb/options.py | 26 ++++- > source-builder/sb/setbuilder.py | 2 + > 3 files changed, 189 insertions(+), 33 deletions(-) > > diff --git a/source-builder/sb/mailer.py b/source-builder/sb/mailer.py > index ff25df5..aafe6d6 100644 > --- a/source-builder/sb/mailer.py > +++ b/source-builder/sb/mailer.py > @@ -1,21 +1,33 @@ > # > # RTEMS Tools Project (http://www.rtems.org/) > -# Copyright 2013 Chris Johns (chr...@rtems.org) > +# Copyright 2013-2016 Chris Johns (chr...@rtems.org) > +# Copyright (C) 2021 On-Line Applications Research Corporation (OAR) > # All rights reserved. > # > # This file is part of the RTEMS Tools package in 'rtems-tools'. > # > -# Permission to use, copy, modify, and/or distribute this software for any > -# purpose with or without fee is hereby granted, provided that the above > -# copyright notice and this permission notice appear in all copies. > +# Redistribution and use in source and binary forms, with or without > +# modification, are permitted provided that the following conditions are met: > +# > +# 1. Redistributions of source code must retain the above copyright notice, > +# this list of conditions and the following disclaimer. > +# > +# 2. Redistributions in binary form must reproduce the above copyright > notice, > +# this list of conditions and the following disclaimer in the documentation > +# and/or other materials provided with the distribution. > +# > +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" > +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE > +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE > +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE > +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS > +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN > +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) > +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE > +# POSSIBILITY OF SUCH DAMAGE. > # > -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > > # > # Manage emailing results or reports. > @@ -28,18 +40,72 @@ import smtplib > import socket > > from . import error > +from . import execute > from . import options > from . import path > > +_options = { > + '--mail' : 'Send email report or results.', > + '--use-gitconfig': 'Use mail configuration from git config.', > + '--mail-to' : 'Email address to send the email to.', > + '--mail-from' : 'Email address the report is from.', > + '--smtp-host' : 'SMTP host to send via.', > + '--smtp-port' : 'SMTP port to send via.', > + '--smtp-user' : 'User for SMTP authentication.', > + '--smtp-password': 'Password for SMTP authentication.' > +} > + > def append_options(opts): > - opts['--mail'] = 'Send email report or results.' > - opts['--smtp-host'] = 'SMTP host to send via.' > - opts['--mail-to'] = 'Email address to send the email too.' > - opts['--mail-from'] = 'Email address the report is from.' > + for o in _options: > + opts[o] = _options[o] > + > +def add_arguments(argsp): > + argsp.add_argument('--mail', help = _options['--mail'], action = > 'store_true') > + argsp.add_argument('--use-gitconfig', help = > _options['--use-gitconfig'], action = 'store_true') > + no_add = ['--mail', '--use-gitconfig'] > + for o in [opt for opt in list(_options) if opt not in no_add]: > + argsp.add_argument(o, help = _options[o], type = str) > > class mail: > def __init__(self, opts): > self.opts = opts > + self.gitconfig_lines = None > + if opts.find_arg('--use-gitconfig') is not None: > + # Read the output of `git config --list` instead of reading the > + # .gitconfig file directly because Python 2 ConfigParser does not > + # accept tabs at the beginning of lines. > + e = execute.capture_execution() > + exit_code, proc, output = e.open('git config --list', shell=True) > + if exit_code == 0: > + self.gitconfig_lines = output.split(os.linesep) > + > + def _args_are_macros(self): > + return isinstance(self.opts, options.command_line) > + > + def _get_arg(self, arg): > + if self._args_are_macros(): > + value = self.opts.find_arg(arg) > + if value is not None: > + value = self.opts.find_arg(arg)[1] > + else: > + if arg.startswith('--'): > + arg = arg[2:] > + arg = arg.replace('-', '_') > + if arg in vars(self.opts): > + value = vars(self.opts)[arg] > + else: > + value = None > + return value > + > + def _get_from_gitconfig(self, variable_name): > + if self.gitconfig_lines is None: > + return None > + > + for line in self.gitconfig_lines: > + if line.startswith(variable_name): > + ls = line.split('=') > + if len(ls) >= 2: > + return ls[1] > > def from_address(self): > > @@ -52,9 +118,15 @@ class mail: > l = l[:l.index('\n')] > return l.strip() > > - addr = self.opts.get_arg('--mail-from') > + addr = self._get_arg('--mail-from') > if addr is not None: > - return addr[1] > + return addr > + addr = self._get_from_gitconfig('user.email') > + if addr is not None: > + name = self._get_from_gitconfig('user.name') > + if name is not None: > + addr = '%s <%s>' % (name, addr) > + return addr > mailrc = None > if 'MAILRC' in os.environ: > mailrc = os.environ['MAILRC'] > @@ -63,9 +135,8 @@ class mail: > if mailrc is not None and path.exists(mailrc): > # set from="Joe Blow <j...@blow.org>" > try: > - mrc = open(mailrc, 'r') > - lines = mrc.readlines() > - mrc.close() > + with open(mailrc, 'r') as mrc: > + lines = mrc.readlines() > except IOError as err: > raise error.general('error reading: %s' % (mailrc)) > for l in lines: > @@ -76,40 +147,99 @@ class mail: > addr = fa[fa.index('=') + 1:].replace('"', ' > ').strip() > if addr is not None: > return addr > - addr = self.opts.defaults.get_value('%{_sbgit_mail}') > + if self._args_are_macros(): > + addr = self.opts.defaults.get_value('%{_sbgit_mail}') > + else: > + raise error.general('no valid from address for mail') > return addr > > def smtp_host(self): > - host = self.opts.get_arg('--smtp-host') > + host = self._get_arg('--smtp-host') > if host is not None: > - return host[1] > - host = self.opts.defaults.get_value('%{_mail_smtp_host}') > + return host > + host = self._get_from_gitconfig('sendemail.smtpserver') > + if host is not None: > + return host > + if self._args_are_macros(): > + host = self.opts.defaults.get_value('%{_mail_smtp_host}') > if host is not None: > return host > return 'localhost' > > + def smtp_port(self): > + port = self._get_arg('--smtp-port') > + if port is not None: > + return port > + port = self._get_from_gitconfig('sendemail.smtpserverport') > + if port is not None: > + return port > + if self._args_are_macros(): > + port = self.opts.defaults.get_value('%{_mail_smtp_port}') > + return port > + > + def smtp_user(self): > + user = self._get_arg('--smtp-user') > + if user is not None: > + return user > + user = self._get_from_gitconfig('sendemail.smtpuser') > + return user > + > + def smtp_password(self): > + password = self._get_arg('--smtp-password') > + if password is not None: > + return password > + password = self._get_from_gitconfig('sendemail.smtppass') > + return password > + > def send(self, to_addr, subject, body): > from_addr = self.from_address() > msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % \ > (from_addr, to_addr, subject) + body > - if type(to_addr) is str: > - to_addr = to_addr.split(',') > - if type(to_addr) is not list: > - raise error.general('invalid to_addr type') > + port = self.smtp_port() > + > try: > - s = smtplib.SMTP(self.smtp_host()) > - s.sendmail(from_addr, to_addr, msg) > + s = smtplib.SMTP(self.smtp_host(), port, timeout=10) > + > + password = self.smtp_password() > + # If a password is provided, assume that authentication is > required. > + if password is not None: > + user = self.smtp_user() > + if user is None: > + user = from_addr > + s.starttls() > + s.login(user, password) > + > + s.sendmail(from_addr, [to_addr], msg) > except smtplib.SMTPException as se: > raise error.general('sending mail: %s' % (str(se))) > except socket.error as se: > raise error.general('sending mail: %s' % (str(se))) > > + def send_file_as_body(self, to_addr, subject, name, intro = None): > + try: > + with open(name, 'r') as f: > + body = f.readlines() > + except IOError as err: > + raise error.general('error reading mail body: %s' % (name)) > + if intro is not None: > + body = intro + body > + self.send(to_addr, from_addr, body)
What is this call for? Chris > + > if __name__ == '__main__': > import sys > + from . import macros > optargs = {} > + rtdir = 'source-builder' > + defaults = '%s/defaults.mc' % (rtdir) > append_options(optargs) > - opts = options.load(sys.argv, optargs = optargs, defaults = > 'defaults.mc') > + opts = options.command_line(base_path = '.', > + argv = sys.argv, > + optargs = optargs, > + defaults = macros.macros(name = defaults, > rtdir = rtdir), > + command_path = '.') > + options.load(opts) > m = mail(opts) > print('From: %s' % (m.from_address())) > print('SMTP Host: %s' % (m.smtp_host())) > - m.send(m.from_address(), 'Test mailer.py', 'This is a test') > + if '--mail' in sys.argv: > + m.send(m.from_address(), 'Test mailer.py', 'This is a test') > diff --git a/source-builder/sb/options.py b/source-builder/sb/options.py > index d6bffd0..a0f196b 100644 > --- a/source-builder/sb/options.py > +++ b/source-builder/sb/options.py > @@ -517,6 +517,15 @@ class command_line: > return None > return self.parse_args(arg) > > + def find_arg(self, arg): > + if self.optargs is None or arg not in self.optargs: > + raise error.internal('bad arg: %s' % (arg)) > + for a in self.args: > + sa = a.split('=') > + if sa[0].startswith(arg): > + return sa > + return None > + > def with_arg(self, label, default = 'not-found'): > # the default if there is no option for without. > result = default > @@ -582,7 +591,22 @@ class command_line: > self.opts['no-install'] = '1' > > def info(self): > - s = ' Command Line: %s%s' % (' '.join(self.argv), os.linesep) > + # Filter potentially sensitive mail options out. > + filtered_args = [ > + arg for arg in self.argv > + if all( > + smtp_opt not in arg > + for smtp_opt in [ > + '--smtp-host', > + '--mail-to', > + '--mail-from', > + '--smtp-user', > + '--smtp-password', > + '--smtp-port' > + ] > + ) > + ] > + s = ' Command Line: %s%s' % (' '.join(filtered_args), os.linesep) > s += ' Python: %s' % (sys.version.replace('\n', '')) > return s > > diff --git a/source-builder/sb/setbuilder.py b/source-builder/sb/setbuilder.py > index b0e2b23..c8c8fee 100644 > --- a/source-builder/sb/setbuilder.py > +++ b/source-builder/sb/setbuilder.py > @@ -695,6 +695,8 @@ def run(): > 'log' : '', > 'reports': [], > 'failure': None } > + # Request this now to generate any errors. > + smtp_host = mail['mail'].smtp_host() > to_addr = opts.get_arg('--mail-to') > if to_addr is not None: > mail['to'] = to_addr[1] > _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel