Make the default build notifications more useful
[invirt/packages/invirt-dev.git] / build-hooks / post-build
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:
59         # TODO: better heuristic
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 %(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 %(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 %(result)s: %(package)s %(version)s in %(pocket)s') % values
153         common.captureOutput(['sendmail', '-t'], email.as_string(),
154                              stdout=None, stderr=None)
155         
156 if __name__ == '__main__':
157     sys.exit(main())