Added reporting
[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         values['traceback'] = textwrap.fill('\n'.join(values['traceback'].split('\n')[-2:]))
60
61     if build.succeeded:
62         values['result'] = success(values['result'])
63         msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s.
64
65 Branch %(pocket)s has been advanced to %(short_commit)s.
66
67 (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
68     else:
69         values['result'] = failure(values['result'])
70         msg = """Build of %(package)s v%(version)s in %(pocket)s %(result)s while %(failed_stage)s.
71
72 %(traceback)s
73
74 (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
75     return msg
76
77 def zephyr_escape(m):
78     m = re.sub('@', '@@', m)
79     m = re.sub('}', '@(})', m)
80     return m
81
82 def zephyr_success(m):
83     return '}@{@color(green)%s}@{' % zephyr_escape(m)
84
85 def zephyr_failure(m):
86     return '}@{@color(red)%s}@{' % zephyr_escape(m)
87
88 def main():
89     parser = optparse.OptionParser('Usage: %prog build_id')
90     opts, args = parser.parse_args()
91     if len(args) != 1:
92         parser.print_help()
93         return 1
94     prog = os.path.basename(sys.argv[0])
95
96     database.connect()
97     build = database.Build.query().get(args[0])
98     short_commit = builder.canonicalize_commit(build.package, build.commit, shorten=True)
99     values = { 'build_id' : build.build_id,
100                'commit' : build.commit,
101                'failed_stage' : build.failed_stage,
102                'inserted_at' : build.inserted_at,
103                'package' : build.package,
104                'pocket' : build.pocket,
105                'principal' : build.principal,
106                'short_commit' : short_commit,
107                'traceback' : build.traceback,
108                'version' : build.version }
109     if build.succeeded:
110         values['result'] = 'succeeded'
111     else:
112         values['result'] = 'failed'
113
114     try:
115         if prog == 'post-build':
116             hook_config = config.build.hooks.post_build
117         elif prog == 'failed-build':
118             hook_config = config.build.hooks.failed_build
119         else:
120             print >>sys.stderr, '{post,failed}-build invoke with unrecognized name %s' % prog
121             return 2
122     except common.InvirtConfigError:
123         print >>sys.stderr, 'No hook configuration found for %s.' % prog
124         return 1
125
126     try:
127         zephyr_config = hook_config.zephyr
128         klass = zephyr_config['class'] % values
129     except common.InvirtConfigError:
130         print >>sys.stderr, 'No zephyr configuration specified for %s.' % prog
131     else:
132         msg = '@{%s}' % make_msg(build, values, verbose=False,
133                                  success=zephyr_success, failure=zephyr_failure)
134         instance = zephyr_config.get('instance', 'build_%(build_id)s') % values
135         zsig = zephyr_config.get('zsig', 'XVM Buildbot') % values
136         common.captureOutput(['zwrite', '-c', klass, '-i', instance, '-s',
137                               zsig, '-d', '-m', msg],
138                              stdout=None, stderr=None)
139
140     try:
141         mail_config = hook_config.mail
142         to = mail_config.to % values
143         sender = mail_config['from'] % values
144     except common.InvirtConfigError:
145         print >>sys.stderr, 'No email configuration specified for %s.' % prog
146     else:
147         msg = make_msg(build, values)
148         email = text.MIMEText(msg)
149         email['To'] = to % values
150         email['From'] = sender % values
151         email['Subject'] = mail_config.get('subject', 'XVM build %(build_id)s has %(result)s') % values
152         common.captureOutput(['sendmail', '-t'], email.as_string(),
153                              stdout=None, stderr=None)
154         
155 if __name__ == '__main__':
156     sys.exit(main())