''' Audit changes to Perforce groups. This should be used as a form-save trigger: audit-groups form-save group "python3 audit_groups.py -p perforce:1666 -u P4USER -g supergroup -L /p4/audit.groups -S smtp.example.org -e p4admins@example.org -a p4admins@example.org %formname% %formfile% %user%" $Author: lester_cheung $ $Id: //guest/lester_cheung/p4util/p4util/trigger/form/audit_groups.py#2 $ ''' __version__ = '0.{0}'.format('$Change: 10926 $'.split()[-2]) import logging import argparse import difflib import shlex import os.path import sys import functools from email.mime.text import MIMEText import smtplib from pprint import pprint, pformat # try: # from ...p4cli import P4 # except: # # failback to P4Python (so we can run the script without installing the module) # import P4 from ...p4cli import P4CLI as P4 # We are using P4CLI.run_plaintext which is not in P4Python def parse_args(): ap = argparse.ArgumentParser(description=__doc__) p4 = P4() ap.add_argument('-p', '--port', metavar=p4.env('P4PORT'), default=p4.env('P4PORT')) ap.add_argument('-u', '--user', metavar=p4.env('P4USER'), default=p4.env('P4USER')) ap.add_argument('-c', '--client', metavar=p4.env('P4CLIENT'), default=p4.env('P4CLIENT')) ap.add_argument('-C', '--charset', metavar=p4.env('P4CHARSET'), default=p4.env('P4CHARSET')) ap.add_argument('-g', '--group', action='append', dest='groups', help='Group to audit. Repeat this options to specify more than one group.') ap.add_argument('-L', '--log', default='audit.group') ap.add_argument('-S', '--smtp-server', help='e.g. smtp.example.org:25') ap.add_argument('-e', '--email', action='append', dest='emails', help='Change notifications will be sent to this email. ' 'Repeat this option to add multiple recipients') ap.add_argument('-a', '--admin-email', default='p4admin@example.org', metavar='p4admin@example.org') ap.add_argument('formname', metavar='%formname%') ap.add_argument('formfile', metavar='%formfile%') ap.add_argument('user', metavar='%user%') return ap.parse_args() def sendemail(cfg, msg): mail = MIMEText(msg) mail['From'] = cfg.admin_email mail['To'] = ', '.join(cfg.emails) mail['Subject'] = '[Perforce audit] Group "{0}" updated by {1}'.format(cfg.formname, cfg.user) print(mail.as_string()) smtp = smtplib.SMTP( *(cfg.smtp_server.split(':')) ) smtp.sendmail(from_addr=cfg.admin_email, to_addrs=cfg.emails, msg=mail.as_string()) def main(): if cfg.groups and cfg.formname not in cfg.groups: sys.exit(0) # short-circuit and exit early p4 = P4() p4.prog = __file__ if cfg.user: p4.user = cfg.user if cfg.port: p4.port = cfg.port if cfg.client: p4.client = cfg.client if cfg.charset: if cfg.charset == 'none': p4.charset = None else: p4.charset = cfg.charset p4.connect() grp0 = p4.run_plaintext( *shlex.split('group -o {0}'.format(cfg.formname)) ).splitlines() p4.disconnect() with open(cfg.formfile) as fd: grp1 = fd.read().splitlines() differ = difflib.Differ() diff = list(differ.compare(grp0, grp1)) if not functools.reduce(lambda x, y: x or y, [ x[0] in '-+?' for x in filter(lambda x: not '', diff) ] ): # Exit if there is no diff markers in front of all lines sys.exit(0) msg = 'Group {0} updated by {1}:\n'.format(cfg.formname, cfg.user) + '\n'.join(diff) log.info(msg) if cfg.emails: try: sendemail(cfg, '\n'.join(diff)) except Exception as e: import io, traceback sbuf = io.StringIO() traceback.print_exc(file=sbuf) log.error('Error sending notification email.' + sbuf.getvalue()) if __name__ == '__main__': cfg = parse_args() logging.basicConfig(format='%(asctime)-15s %(funcName)s.%(levelname)s %(message)s', filename=cfg.log, level=logging.DEBUG) log = logging.getLogger(__name__) log.debug(cfg) main() # making sure that we exit with zero return so the group mod # gets committed sys.exit(0)