Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 268 lines (224 sloc) 7.99 KB
#!/usr/bin/env python
#
# Buildbot CVS Mail
#
# This script was derrived from syncmail,
# Copyright (c) 2002-2006 Barry Warsaw, Fred Drake, and contributors
#
# http://cvs-syncmail.cvs.sourceforge.net
#
# The script was re-written with the sole pupose of providing updates to
# Buildbot master by Andy Howell
#
# Options handling done right by djmitche
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import optparse
import os
import re
import smtplib
import socket
import sys
import textwrap
import time
from email.utils import formataddr
from io import StringIO
"""
-t
--testing
Construct message and send to stdout for testing
The rest of the command line arguments are:
%%{sVv}
CVS %%{sVv} loginfo expansion. When invoked by CVS, this will be a single
string containing the files that are changing.
"""
__version__ = '$Revision: 1.3 $'
try:
import pwd
except ImportError:
# pwd is not available on Windows..
pwd = None
COMMASPACE = ', '
PROGRAM = sys.argv[0]
class SmtplibMock:
"""I stand in for smtplib for testing purposes.
"""
class SMTP:
"""I stand in for smtplib.SMTP connection for testing purposes.
I copy the message to stdout.
"""
def close(self):
pass
def connect(self, mailhost, mailport):
pass
def sendmail(self, address, email, msg):
sys.stdout.write(msg)
rfc822_specials_re = re.compile(r'[\(\)<>@,;:\\\"\.\[\]]')
def quotename(name):
if name and rfc822_specials_re.search(name):
return '"%s"' % name.replace('"', '\\"')
else:
return name
def send_mail(options):
# Create the smtp connection to the localhost
conn = options.smtplib.SMTP()
conn.connect(options.mailhost, options.mailport)
if pwd:
pwinfo = pwd.getpwuid(os.getuid())
user = pwinfo[0]
name = pwinfo[4]
else:
user = 'cvs'
name = 'CVS'
domain = options.fromhost
if not domain:
# getfqdn is not good for use in unit tests
if options.amTesting:
domain = 'testing.com'
else:
domain = socket.getfqdn()
address = '%s@%s' % (user, domain)
s = StringIO()
datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000',
time.gmtime(time.time()))
fileList = ' '.join(map(str, options.files))
vars = {'author': formataddr((name, address)),
'email': options.email,
'subject': 'cvs update for project %s' % options.project,
'version': __version__,
'date': datestamp,
}
print('''\
From: %(author)s
To: %(email)s''' % vars, file=s)
if options.replyto:
print('Reply-To: %s' % options.replyto, file=s)
print('''\
Subject: %(subject)s
Date: %(date)s
X-Mailer: Python buildbot-cvs-mail %(version)s
''' % vars, file=s)
print('Cvsmode: %s' % options.cvsmode, file=s)
print('Category: %s' % options.category, file=s)
print('CVSROOT: %s' % options.cvsroot, file=s)
print('Files: %s' % fileList, file=s)
if options.path:
print('Path: %s' % options.path, file=s)
print('Project: %s' % options.project, file=s)
cvs_input = sys.stdin.read()
# On Python 2, sys.stdin.read() returns bytes, but
# on Python 3, it returns unicode str.
if isinstance(cvs_input, bytes):
cvs_input = cvs_input.decode("utf-8")
s.write(cvs_input)
print('', file=s)
conn.sendmail(address, options.email, s.getvalue())
conn.close()
def fork_and_send_mail(options):
# cannot wait for child process or that will cause parent to retain cvs
# lock for too long. Urg!
if not os.fork():
# in the child
# give up the lock you cvs thang!
time.sleep(2)
send_mail(options)
os._exit(0)
description = """
This script is used to provide email notifications of changes to the CVS
repository to a buildbot master. It is invoked via a CVS loginfo file (see
$CVSROOT/CVSROOT/loginfo). See the Buildbot manual for more information.
"""
usage = "%prog [options] %{sVv}"
parser = optparse.OptionParser(description=description,
usage=usage,
add_help_option=True,
version=__version__)
parser.add_option("-C", "--category", dest='category', metavar="CAT",
help=textwrap.dedent("""\
Category for change. This becomes the Change.category attribute, which
can be used within the buildmaster to filter changes.
"""))
parser.add_option("-c", "--cvsroot", dest='cvsroot', metavar="PATH",
help=textwrap.dedent("""\
CVSROOT for use by buildbot workers to checkout code.
This becomes the Change.repository attribute.
Exmaple: :ext:myhost:/cvsroot
"""))
parser.add_option("-e", "--email", dest='email', metavar="EMAIL",
help=textwrap.dedent("""\
Email address of the buildbot.
"""))
parser.add_option("-f", "--fromhost", dest='fromhost', metavar="HOST",
help=textwrap.dedent("""\
The hostname that email messages appear to be coming from. The From:
header of the outgoing message will look like user@hostname. By
default, hostname is the machine's fully qualified domain name.
"""))
parser.add_option("-m", "--mailhost", dest='mailhost', metavar="HOST",
default="localhost",
help=textwrap.dedent("""\
The hostname of an available SMTP server. The default is
'localhost'.
"""))
parser.add_option("--mailport", dest='mailport', metavar="PORT",
default=25, type="int",
help=textwrap.dedent("""\
The port number of SMTP server. The default is '25'.
"""))
parser.add_option("-q", "--quiet", dest='verbose', action="store_false",
default=True,
help=textwrap.dedent("""\
Don't print as much status to stdout.
"""))
parser.add_option("-p", "--path", dest='path', metavar="PATH",
help=textwrap.dedent("""\
The path for the files in this update. This comes from the %p parameter
in loginfo for CVS version 1.12.x. Do not use this for CVS version 1.11.x
"""))
parser.add_option("-P", "--project", dest='project', metavar="PROJ",
help=textwrap.dedent("""\
The project for the source. Often set to the CVS module being modified. This becomes
the Change.project attribute.
"""))
parser.add_option("-R", "--reply-to", dest='replyto', metavar="ADDR",
help=textwrap.dedent("""\
Add a "Reply-To: ADDR" header to the email message.
"""))
parser.add_option(
"-t", "--testing", action="store_true", dest="amTesting", default=False)
parser.set_defaults(smtplib=smtplib)
def get_options():
options, args = parser.parse_args()
# rest of command line are the files.
options.files = args
if options.path is None:
options.cvsmode = '1.11'
else:
options.cvsmode = '1.12'
if options.cvsroot is None:
parser.error('--cvsroot is required')
if options.email is None:
parser.error('--email is required')
# set up for unit tests
if options.amTesting:
options.verbose = 0
options.smtplib = SmtplibMock
return options
# scan args for options
def main():
options = get_options()
if options.verbose:
print('Mailing %s...' % options.email)
print('Generating notification message...')
if options.amTesting:
send_mail(options)
else:
fork_and_send_mail(options)
if options.verbose:
print('Generating notification message... done.')
return 0
if __name__ == '__main__':
ret = main()
sys.exit(ret)