source: rtems-tools/rtemstoolkit/mailer.py @ f18e6d8

Last change on this file since f18e6d8 was f18e6d8, checked in by Alex White <alex.white@…>, on 05/05/21 at 14:04:49

rtemstoolkit/mailer.py: Fix option ordering for add_arguments

The ordering of keys cannot be guaranteed in a dictionary. This changes
the options iteration to no longer rely on key ordering.

Closes #4402

  • Property mode set to 100644
File size: 8.9 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2013-2016 Chris Johns (chrisj@rtems.org)
4# Copyright (C) 2021 On-Line Applications Research Corporation (OAR)
5# All rights reserved.
6#
7# This file is part of the RTEMS Tools package in 'rtems-tools'.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12# 1. Redistributions of source code must retain the above copyright notice,
13# this list of conditions and the following disclaimer.
14#
15# 2. Redistributions in binary form must reproduce the above copyright notice,
16# this list of conditions and the following disclaimer in the documentation
17# and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32#
33# Manage emailing results or reports.
34#
35
36from __future__ import print_function
37
38import os
39import smtplib
40import socket
41
42from rtemstoolkit import error
43from rtemstoolkit import execute
44from rtemstoolkit import options
45from rtemstoolkit import path
46
47_options = {
48    '--mail'         : 'Send email report or results.',
49    '--use-gitconfig': 'Use mail configuration from git config.',
50    '--mail-to'      : 'Email address to send the email to.',
51    '--mail-from'    : 'Email address the report is from.',
52    '--smtp-host'    : 'SMTP host to send via.',
53    '--smtp-port'    : 'SMTP port to send via.',
54    '--smtp-user'    : 'User for SMTP authentication.',
55    '--smtp-password': 'Password for SMTP authentication.'
56}
57
58def append_options(opts):
59    for o in _options:
60        opts[o] = _options[o]
61
62def add_arguments(argsp):
63    argsp.add_argument('--mail', help = _options['--mail'], action = 'store_true')
64    argsp.add_argument('--use-gitconfig', help = _options['--use-gitconfig'], action = 'store_true')
65    no_add = ['--mail', '--use-gitconfig']
66    for o in [opt for opt in list(_options) if opt not in no_add]:
67        argsp.add_argument(o, help = _options[o], type = str)
68
69class mail:
70    def __init__(self, opts):
71        self.opts = opts
72        self.gitconfig_lines = None
73        if opts.find_arg('--use-gitconfig') is not None:
74            # Read the output of `git config --list` instead of reading the
75            # .gitconfig file directly because Python 2 ConfigParser does not
76            # accept tabs at the beginning of lines.
77            e = execute.capture_execution()
78            exit_code, proc, output = e.open('git config --list', shell=True)
79            if exit_code == 0:
80                self.gitconfig_lines = output.split(os.linesep)
81
82    def _args_are_macros(self):
83        return isinstance(self.opts, options.command_line)
84
85    def _get_arg(self, arg):
86        if self._args_are_macros():
87            value = self.opts.find_arg(arg)
88            if value is not None:
89                value = self.opts.find_arg(arg)[1]
90        else:
91            if arg.startswith('--'):
92                arg = arg[2:]
93            arg = arg.replace('-', '_')
94            if arg in vars(self.opts):
95                value = vars(self.opts)[arg]
96            else:
97                value = None
98        return value
99
100    def _get_from_gitconfig(self, variable_name):
101        if self.gitconfig_lines is None:
102            return None
103
104        for line in self.gitconfig_lines:
105            if line.startswith(variable_name):
106                ls = line.split('=')
107                if len(ls) >= 2:
108                    return ls[1]
109
110    def from_address(self):
111
112        def _clean(l):
113            if '#' in l:
114                l = l[:l.index('#')]
115            if '\r' in l:
116                l = l[:l.index('r')]
117            if '\n' in l:
118                l = l[:l.index('\n')]
119            return l.strip()
120
121        addr = self._get_arg('--mail-from')
122        if addr is not None:
123            return addr
124        addr = self._get_from_gitconfig('user.email')
125        if addr is not None:
126            name = self._get_from_gitconfig('user.name')
127            if name is not None:
128                addr = '%s <%s>' % (name, addr)
129            return addr
130        mailrc = None
131        if 'MAILRC' in os.environ:
132            mailrc = os.environ['MAILRC']
133        if mailrc is None and 'HOME' in os.environ:
134            mailrc = path.join(os.environ['HOME'], '.mailrc')
135        if mailrc is not None and path.exists(mailrc):
136            # set from="Joe Blow <joe@blow.org>"
137            try:
138                with open(mailrc, 'r') as mrc:
139                    lines = mrc.readlines()
140            except IOError as err:
141                raise error.general('error reading: %s' % (mailrc))
142            for l in lines:
143                l = _clean(l)
144                if 'from' in l:
145                    fa = l[l.index('from') + len('from'):]
146                    if '=' in fa:
147                        addr = fa[fa.index('=') + 1:].replace('"', ' ').strip()
148            if addr is not None:
149                return addr
150        if self._args_are_macros():
151            addr = self.opts.defaults.get_value('%{_sbgit_mail}')
152        else:
153            raise error.general('no valid from address for mail')
154        return addr
155
156    def smtp_host(self):
157        host = self._get_arg('--smtp-host')
158        if host is not None:
159            return host
160        host = self._get_from_gitconfig('sendemail.smtpserver')
161        if host is not None:
162            return host
163        if self._args_are_macros():
164            host = self.opts.defaults.get_value('%{_mail_smtp_host}')
165        if host is not None:
166            return host
167        return 'localhost'
168
169    def smtp_port(self):
170        port = self._get_arg('--smtp-port')
171        if port is not None:
172            return port
173        port = self._get_from_gitconfig('sendemail.smtpserverport')
174        if port is not None:
175            return port
176        if self._args_are_macros():
177            port = self.opts.defaults.get_value('%{_mail_smtp_port}')
178        return port
179
180    def smtp_user(self):
181        user = self._get_arg('--smtp-user')
182        if user is not None:
183            return user
184        user = self._get_from_gitconfig('sendemail.smtpuser')
185        return user
186
187    def smtp_password(self):
188        password = self._get_arg('--smtp-password')
189        if password is not None:
190            return password
191        password = self._get_from_gitconfig('sendemail.smtppass')
192        return password
193
194    def send(self, to_addr, subject, body):
195        from_addr = self.from_address()
196        msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % \
197            (from_addr, to_addr, subject) + body
198        port = self.smtp_port()
199
200        try:
201            s = smtplib.SMTP(self.smtp_host(), port, timeout=10)
202
203            password = self.smtp_password()
204            # If a password is provided, assume that authentication is required.
205            if password is not None:
206                user = self.smtp_user()
207                if user is None:
208                    user = from_addr
209                s.starttls()
210                s.login(user, password)
211
212            s.sendmail(from_addr, [to_addr], msg)
213        except smtplib.SMTPException as se:
214            raise error.general('sending mail: %s' % (str(se)))
215        except socket.error as se:
216            raise error.general('sending mail: %s' % (str(se)))
217
218    def send_file_as_body(self, to_addr, subject, name, intro = None):
219        try:
220            with open(name, 'r') as f:
221                body = f.readlines()
222        except IOError as err:
223            raise error.general('error reading mail body: %s' % (name))
224        if intro is not None:
225            body = intro + body
226        self.send(to_addr, from_addr, body)
227
228if __name__ == '__main__':
229    import sys
230    from rtemstoolkit import macros
231    optargs = {}
232    rtdir = 'rtemstoolkit'
233    defaults = '%s/defaults.mc' % (rtdir)
234    append_options(optargs)
235    opts = options.command_line(base_path = '.',
236                                argv = sys.argv,
237                                optargs = optargs,
238                                defaults = macros.macros(name = defaults, rtdir = rtdir),
239                                command_path = '.')
240    options.load(opts)
241    m = mail(opts)
242    print('From: %s' % (m.from_address()))
243    print('SMTP Host: %s' % (m.smtp_host()))
244    if '--mail' in sys.argv:
245        m.send(m.from_address(), 'Test mailer.py', 'This is a test')
Note: See TracBrowser for help on using the repository browser.