[3036] | 1 | #!/usr/bin/env python |
---|
| 2 | """ |
---|
| 3 | A script for reporting the results of an invirtibuild. Supports any |
---|
| 4 | combination of the following reporting mechanisms. |
---|
| 5 | |
---|
| 6 | Note that all strings are interpolated with a dictionary containing |
---|
| 7 | the following keys: |
---|
| 8 | |
---|
| 9 | build_id, commit, failed_stage, inserted_at, package, |
---|
| 10 | pocket, principal, result, short_commit, traceback, version |
---|
| 11 | |
---|
| 12 | == zephyr == |
---|
| 13 | |
---|
| 14 | To configure zephyr, add something like the following to your invirt config: |
---|
| 15 | |
---|
| 16 | build: |
---|
| 17 | hooks: |
---|
| 18 | post_build: |
---|
| 19 | zephyr: &post_build_zepyhr |
---|
| 20 | class: myclass [required] |
---|
| 21 | instance: myinstance [optional] |
---|
| 22 | zsig: myzsig [optional] |
---|
| 23 | failed_build: |
---|
| 24 | zephyr: *post_build_zephyr |
---|
| 25 | |
---|
| 26 | == mail == |
---|
| 27 | |
---|
| 28 | To configure email notifications, add something like the following to your invirt config: |
---|
| 29 | |
---|
| 30 | build: |
---|
| 31 | hooks: |
---|
| 32 | post_build: |
---|
| 33 | mail: &post_build_mail |
---|
| 34 | to: myemail@example.com [required] |
---|
| 35 | from: myemail@example.com [required] |
---|
| 36 | subject: My Subject [optional] |
---|
| 37 | failed_build: |
---|
| 38 | mail: *post_build_mail |
---|
| 39 | |
---|
| 40 | post_build values will be used when this script is invoked as |
---|
| 41 | post-build, while failed_build values will be used when it is invoked |
---|
| 42 | as failed-build. |
---|
| 43 | """ |
---|
| 44 | |
---|
| 45 | import optparse |
---|
| 46 | import os |
---|
| 47 | import re |
---|
| 48 | import sys |
---|
| 49 | import textwrap |
---|
| 50 | |
---|
| 51 | from email.mime import text |
---|
| 52 | |
---|
| 53 | from invirt import common, database, builder |
---|
| 54 | from invirt.config import structs as config |
---|
| 55 | |
---|
| 56 | def make_msg(build, values, verbose=True, success=lambda x: x, failure=lambda x: x): |
---|
| 57 | values = dict(values) |
---|
| 58 | if not verbose and values['traceback'] is not None: |
---|
[3037] | 59 | # TODO: better heuristic |
---|
[3036] | 60 | values['traceback'] = textwrap.fill('\n'.join(values['traceback'].split('\n')[-2:])) |
---|
| 61 | |
---|
| 62 | if build.succeeded: |
---|
| 63 | values['result'] = success(values['result']) |
---|
| 64 | msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s. |
---|
| 65 | |
---|
| 66 | Branch %(pocket)s has been advanced to %(short_commit)s. |
---|
| 67 | |
---|
| 68 | (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values |
---|
| 69 | else: |
---|
| 70 | values['result'] = failure(values['result']) |
---|
| 71 | msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s while %(failed_stage)s. |
---|
| 72 | |
---|
| 73 | %(traceback)s |
---|
| 74 | |
---|
| 75 | (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values |
---|
| 76 | return msg |
---|
| 77 | |
---|
| 78 | def zephyr_escape(m): |
---|
| 79 | m = re.sub('@', '@@', m) |
---|
| 80 | m = re.sub('}', '@(})', m) |
---|
| 81 | return m |
---|
| 82 | |
---|
| 83 | def zephyr_success(m): |
---|
| 84 | return '}@{@color(green)%s}@{' % zephyr_escape(m) |
---|
| 85 | |
---|
| 86 | def zephyr_failure(m): |
---|
| 87 | return '}@{@color(red)%s}@{' % zephyr_escape(m) |
---|
| 88 | |
---|
| 89 | def main(): |
---|
| 90 | parser = optparse.OptionParser('Usage: %prog build_id') |
---|
| 91 | opts, args = parser.parse_args() |
---|
| 92 | if len(args) != 1: |
---|
| 93 | parser.print_help() |
---|
| 94 | return 1 |
---|
| 95 | prog = os.path.basename(sys.argv[0]) |
---|
| 96 | |
---|
| 97 | database.connect() |
---|
| 98 | build = database.Build.query().get(args[0]) |
---|
| 99 | short_commit = builder.canonicalize_commit(build.package, build.commit, shorten=True) |
---|
| 100 | values = { 'build_id' : build.build_id, |
---|
| 101 | 'commit' : build.commit, |
---|
| 102 | 'failed_stage' : build.failed_stage, |
---|
| 103 | 'inserted_at' : build.inserted_at, |
---|
| 104 | 'package' : build.package, |
---|
| 105 | 'pocket' : build.pocket, |
---|
| 106 | 'principal' : build.principal, |
---|
| 107 | 'short_commit' : short_commit, |
---|
| 108 | 'traceback' : build.traceback, |
---|
| 109 | 'version' : build.version } |
---|
| 110 | if build.succeeded: |
---|
| 111 | values['result'] = 'succeeded' |
---|
| 112 | else: |
---|
| 113 | values['result'] = 'failed' |
---|
| 114 | |
---|
| 115 | try: |
---|
| 116 | if prog == 'post-build': |
---|
| 117 | hook_config = config.build.hooks.post_build |
---|
| 118 | elif prog == 'failed-build': |
---|
| 119 | hook_config = config.build.hooks.failed_build |
---|
| 120 | else: |
---|
| 121 | print >>sys.stderr, '{post,failed}-build invoke with unrecognized name %s' % prog |
---|
| 122 | return 2 |
---|
| 123 | except common.InvirtConfigError: |
---|
| 124 | print >>sys.stderr, 'No hook configuration found for %s.' % prog |
---|
| 125 | return 1 |
---|
| 126 | |
---|
| 127 | try: |
---|
| 128 | zephyr_config = hook_config.zephyr |
---|
| 129 | klass = zephyr_config['class'] % values |
---|
| 130 | except common.InvirtConfigError: |
---|
| 131 | print >>sys.stderr, 'No zephyr configuration specified for %s.' % prog |
---|
| 132 | else: |
---|
| 133 | msg = '@{%s}' % make_msg(build, values, verbose=False, |
---|
| 134 | success=zephyr_success, failure=zephyr_failure) |
---|
| 135 | instance = zephyr_config.get('instance', 'build_%(build_id)s') % values |
---|
| 136 | zsig = zephyr_config.get('zsig', 'XVM Buildbot') % values |
---|
| 137 | common.captureOutput(['zwrite', '-c', klass, '-i', instance, '-s', |
---|
| 138 | zsig, '-d', '-m', msg], |
---|
| 139 | stdout=None, stderr=None) |
---|
| 140 | |
---|
| 141 | try: |
---|
| 142 | mail_config = hook_config.mail |
---|
| 143 | to = mail_config.to % values |
---|
| 144 | sender = mail_config['from'] % values |
---|
| 145 | except common.InvirtConfigError: |
---|
| 146 | print >>sys.stderr, 'No email configuration specified for %s.' % prog |
---|
| 147 | else: |
---|
| 148 | msg = make_msg(build, values) |
---|
| 149 | email = text.MIMEText(msg) |
---|
| 150 | email['To'] = to % values |
---|
| 151 | email['From'] = sender % values |
---|
| 152 | email['Subject'] = mail_config.get('subject', 'XVM build %(build_id)s has %(result)s') % values |
---|
| 153 | common.captureOutput(['sendmail', '-t'], email.as_string(), |
---|
| 154 | stdout=None, stderr=None) |
---|
| 155 | |
---|
| 156 | if __name__ == '__main__': |
---|
| 157 | sys.exit(main()) |
---|