1#!/usr/bin/env python2
  2
  3"""Receive messages from supervisor process over stdin, using supervisor utility functions.
  4
  5Send an email in response to status changes in other processes.
  6"""
  7
  8import os
  9import sys
 10import socket
 11import smtplib
 12import argparse
 13
 14def log(message):
 15    """Log `message` to the supervisord stderr file."""
 16    sys.stderr.write('{message}\n'.format(message=message))
 17    sys.stderr.flush()
 18
 19
 20def listen(sendmail_program,
 21           to_address,
 22           from_address,
 23           subject_prefix):
 24    """Listen for messages and send emails."""
 25    from supervisor import childutils  # throws off lint/doctest tools because this is
 26    # python2 code
 27
 28    log('Listening')
 29
 30    while True:
 31        # we explicitly use self.stdin, self.stdout, and self.stderr
 32        # instead of sys.* so we can unit test this code
 33        headers, payload = childutils.listener.wait(sys.stdin, sys.stdout)
 34        log('headers ' + str(headers))
 35        log('payload ' + str(payload))
 36
 37        # if not headers['eventname'] == 'PROCESS_STATE_EXITED':
 38            # do nothing with non-TICK events
 39            # childutils.listener.ok(self.stdout)
 40            # if test:
 41                # self.stderr.write('non-exited event\n')
 42                # self.stderr.flush()
 43                # break
 44            # continue
 45
 46        pheaders, pdata = childutils.eventdata(payload + '\n')
 47
 48        # if int(pheaders['expected']):
 49            # childutils.listener.ok(self.stdout)
 50            # if test:
 51                # self.stderr.write('expected exit\n')
 52                # self.stderr.flush()
 53                # break
 54            # continue
 55
 56        log('pheaders ' + str(pheaders))
 57        log('pdata ' + str(pdata))
 58
 59        # headers['server'] gives a decent subj prefix
 60
 61        # headers {'ver': '3.0', 'poolserial': '10', 'len': '89', 'server': 'CHART_MIG',
 62        #   'eventname': 'PROCESS_STATE_RUNNING', 'serial': '10', 'pool': 'event_notifications'}
 63        # payload processname:event_notifications groupname:event_notifications
 64        #   from_state:STARTING pid:643
 65        #   pheaders {'from_state': 'STARTING', 'processname': 'event_notifications', 'pid': '643',
 66        #   'groupname': 'event_notifications'}
 67        # pdata
 68
 69        if from_address is None:
 70            from_address = '{user} <{nonspaceuser}@{hostname}>'.format(
 71                user=headers.get('server', '').replace('_', ' '),
 72                nonspaceuser=headers.get('server', '').replace('_',''),
 73                hostname=socket.gethostname())
 74
 75        if 'server' in headers and len(headers['server']) > 0:
 76            subject_prefix = '{name}: '.format(name=headers['server'].replace('_', ' '))
 77
 78        else:
 79            subject_prefix = ''
 80
 81        subject = '{prefix}{processname} entered {eventname}'.format(
 82            prefix=subject_prefix,
 83            processname=pheaders.get('processname', 'supervisord'),
 84            eventname=headers['eventname'])
 85
 86        message = 'Process {processname} entered {eventname} from {oldstate}'.format(
 87            processname=pheaders.get('processname', 'supervisord'),
 88            eventname=headers['eventname'],
 89            oldstate=pheaders.get('from_state'))
 90
 91        sendmail(sendmail_program,
 92                 to_address,
 93                 from_address,
 94                 subject,
 95                 message)
 96        childutils.listener.ok(sys.stdout)
 97
 98
 99def sendmail(sendmail_program,
100             to_address,
101             from_address,
102             subject,
103             message,
104             fake=False):
105    """Send an email."""
106    # m = os.popen(sendmail_program, 'w')
107    # m.write(body)
108    # m.close()
109    # sys.stderr.write('Mailed:\n\n{body}'.format(body=body))
110    body = """To: {to_address}
111From: {from_address}
112Subject: {subject}
113
114{message}""".format(to_address=to_address,
115                    from_address=from_address,
116                    subject=subject,
117                    message=message)
118
119    if not fake:
120        s = smtplib.SMTP()
121        s.connect()
122        s.sendmail(from_address,
123                   [to_address],
124                   body)
125        s.close()
126
127    log(body)
128
129
130def main():
131    """Command line entry point."""
132    if 'SUPERVISOR_SERVER_URL' not in os.environ:
133        sys.stderr.write('This program must be run as a supervisord process (SUPERVISOR_SERVER_URL not set)\n')
134        sys.stderr.flush()
135        return
136
137    parser = argparse.ArgumentParser()
138    parser.add_argument('--to-address', '-m',
139                        help=('Email address(s) to send alerts to. Use "name <address>" for nice '
140                              'display'))
141    parser.add_argument('--from-address', '--from',
142                        help=('Email address(s) to send alerts to. Use "name <address>" for nice '
143                              'display'))
144    parser.add_argument('--sendmail', '-s',
145                        help='Command to run sendmail program',
146                        default='sendmail -t -i')
147    parser.add_argument('--subject-prefix', '-p',
148                        help='Prefix subject field of emails')
149    parser.add_argument('--test',
150                        action='store_true',
151                        help='Send a test email')
152    parser.add_argument('--fake',
153                        action='store_true',
154                        help='Do not send emails')
155    args = parser.parse_args()
156
157    if not args.to_address:
158        parser.error('No recipient address specified')
159
160    if args.test:
161        sendmail(sendmail_program=args.sendmail,
162                 to_address=args.to_address,
163                 from_address='Chart',
164                 subject='Test message',
165                 message='This is a test message',
166                 fake=args.fake)
167        parser.exit()
168
169    listen(sendmail_program=args.sendmail,
170           to_address=args.to_address,
171           from_address=args.from_address,
172           subject_prefix=args.subject_prefix)
173
174if __name__ == '__main__':
175    main()