--- /dev/null
+#!/usr/bin/env python
+"""
+A script for reporting the results of an invirtibuild. Supports any
+combination of the following reporting mechanisms.
+
+Note that all strings are interpolated with a dictionary containing
+the following keys:
+
+build_id, commit, failed_stage, inserted_at, package,
+pocket, principal, result, short_commit, traceback, version
+
+== zephyr ==
+
+To configure zephyr, add something like the following to your invirt config:
+
+build:
+ hooks:
+ post_build:
+ zephyr: &post_build_zepyhr
+ class: myclass [required]
+ instance: myinstance [optional]
+ zsig: myzsig [optional]
+ failed_build:
+ zephyr: *post_build_zephyr
+
+== mail ==
+
+To configure email notifications, add something like the following to your invirt config:
+
+build:
+ hooks:
+ post_build:
+ mail: &post_build_mail
+ to: myemail@example.com [required]
+ from: myemail@example.com [required]
+ subject: My Subject [optional]
+ failed_build:
+ mail: *post_build_mail
+
+post_build values will be used when this script is invoked as
+post-build, while failed_build values will be used when it is invoked
+as failed-build.
+"""
+
+import optparse
+import os
+import re
+import sys
+import textwrap
+
+from email.mime import text
+
+from invirt import common, database, builder
+from invirt.config import structs as config
+
+def make_msg(build, values, verbose=True, success=lambda x: x, failure=lambda x: x):
+ values = dict(values)
+ if not verbose and values['traceback'] is not None:
+ values['traceback'] = textwrap.fill('\n'.join(values['traceback'].split('\n')[-2:]))
+
+ if build.succeeded:
+ values['result'] = success(values['result'])
+ msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s.
+
+Branch %(pocket)s has been advanced to %(short_commit)s.
+
+(Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
+ else:
+ values['result'] = failure(values['result'])
+ msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s while %(failed_stage)s.
+
+%(traceback)s
+
+(Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
+ return msg
+
+def zephyr_escape(m):
+ m = re.sub('@', '@@', m)
+ m = re.sub('}', '@(})', m)
+ return m
+
+def zephyr_success(m):
+ return '}@{@color(green)%s}@{' % zephyr_escape(m)
+
+def zephyr_failure(m):
+ return '}@{@color(red)%s}@{' % zephyr_escape(m)
+
+def main():
+ parser = optparse.OptionParser('Usage: %prog build_id')
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ parser.print_help()
+ return 1
+ prog = os.path.basename(sys.argv[0])
+
+ database.connect()
+ build = database.Build.query().get(args[0])
+ short_commit = builder.canonicalize_commit(build.package, build.commit, shorten=True)
+ values = { 'build_id' : build.build_id,
+ 'commit' : build.commit,
+ 'failed_stage' : build.failed_stage,
+ 'inserted_at' : build.inserted_at,
+ 'package' : build.package,
+ 'pocket' : build.pocket,
+ 'principal' : build.principal,
+ 'short_commit' : short_commit,
+ 'traceback' : build.traceback,
+ 'version' : build.version }
+ if build.succeeded:
+ values['result'] = 'succeeded'
+ else:
+ values['result'] = 'failed'
+
+ try:
+ if prog == 'post-build':
+ hook_config = config.build.hooks.post_build
+ elif prog == 'failed-build':
+ hook_config = config.build.hooks.failed_build
+ else:
+ print >>sys.stderr, '{post,failed}-build invoke with unrecognized name %s' % prog
+ return 2
+ except common.InvirtConfigError:
+ print >>sys.stderr, 'No hook configuration found for %s.' % prog
+ return 1
+
+ try:
+ zephyr_config = hook_config.zephyr
+ klass = zephyr_config['class'] % values
+ except common.InvirtConfigError:
+ print >>sys.stderr, 'No zephyr configuration specified for %s.' % prog
+ else:
+ msg = '@{%s}' % make_msg(build, values, verbose=False,
+ success=zephyr_success, failure=zephyr_failure)
+ instance = zephyr_config.get('instance', 'build_%(build_id)s') % values
+ zsig = zephyr_config.get('zsig', 'XVM Buildbot') % values
+ common.captureOutput(['zwrite', '-c', klass, '-i', instance, '-s',
+ zsig, '-d', '-m', msg],
+ stdout=None, stderr=None)
+
+ try:
+ mail_config = hook_config.mail
+ to = mail_config.to % values
+ sender = mail_config['from'] % values
+ except common.InvirtConfigError:
+ print >>sys.stderr, 'No email configuration specified for %s.' % prog
+ else:
+ msg = make_msg(build, values)
+ email = text.MIMEText(msg)
+ email['To'] = to % values
+ email['From'] = sender % values
+ email['Subject'] = mail_config.get('subject', 'XVM build %(build_id)s has %(result)s') % values
+ common.captureOutput(['sendmail', '-t'], email.as_string(),
+ stdout=None, stderr=None)
+
+if __name__ == '__main__':
+ sys.exit(main())
commit = b.canonicalize_commit(package, commit)
b.validateBuild(pocket, package, commit)
except b.InvalidBuild, e:
- print >>sys.stderr, "E: %s" % e
+ msg = "E: %s" % e
+ print >>sys.stderr, msg
+ # Prevent an attack by submitting excessively long arguments
+ args = [arg[:min(len(arg), 80)] for arg in (pocket, package, commit)]
+ b.runHook('failed-submit', args + [principal], stdin_str=msg)
sys.exit(1)
# To keep from triggering the Invirtibuilder before we've actually
print >>q, "%s %s %s %s" % (pocket, package, commit, principal)
q.close()
os.rename(q_name, q_path)
+ short_commit = b.canonicalize_commit(package, commit, shorten=True)
+ b.runHook('post-submit', [pocket, package, short_commit, principal])
+ print '%s, your job to build %s for %s:%s has been submitted!' % (principal, short_commit, package, pocket)
if __name__ == '__main__':
finally:
shutil.rmtree(workdir)
-
-def reportBuild(build):
- """Run hooks to report the results of a build attempt."""
-
- c.captureOutput(['run-parts',
- '--arg=%s' % build.build_id,
- '--',
- b._HOOKS_DIR])
-
-
def build():
"""Deal with items in the build queue.
src = b.validateBuild(pocket, package, commit)
db.version = str(b.getVersion(package, commit))
+ b.runHook('pre-build', [str(db.build_id), db.pocket, db.package,
+ db.commit, db.principal, db.version, str(db.inserted_at)])
# If validateBuild returns something other than True, then
# it means we should copy from that pocket to our pocket.
# build queue item
os.unlink(os.path.join(b._QUEUE_DIR, build))
- reportBuild(db)
-
+ if db.succeeded:
+ b.runHook('post-build', [str(db.build_id)])
+ else:
+ b.runHook('failed-build', [str(db.build_id)])
class Invirtibuilder(pyinotify.ProcessEvent):
"""Process inotify triggers to build new packages."""