3 A script for reporting the results of an invirtibuild. Supports any
4 combination of the following reporting mechanisms.
6 Note that all strings are interpolated with a dictionary containing
9 build_id, commit, failed_stage, inserted_at, package,
10 pocket, principal, result, short_commit, traceback, version
14 To configure zephyr, add something like the following to your invirt config:
19 zephyr: &post_build_zepyhr
20 class: myclass [required]
21 instance: myinstance [optional]
22 zsig: myzsig [optional]
24 zephyr: *post_build_zephyr
28 To configure email notifications, add something like the following to your invirt config:
33 mail: &post_build_mail
34 to: myemail@example.com [required]
35 from: myemail@example.com [required]
36 subject: My Subject [optional]
38 mail: *post_build_mail
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
51 from email.mime import text
53 from invirt import common, database, builder
54 from invirt.config import structs as config
56 def build_completion_msg(succeeded, values, verbose=True, success=lambda x: x, failure=lambda x: x):
57 """Format a message reporting the results of a build"""
59 if not verbose and values['traceback'] is not None:
60 # TODO: better heuristic
61 values['traceback'] = textwrap.fill('\n'.join(values['traceback'].split('\n')[-2:]))
64 values['result'] = success(values['result'])
65 msg = """Build of %(package)s %(version)s in %(pocket)s %(result)s.
67 Branch %(pocket)s has been advanced to %(short_commit)s.
69 (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
71 values['result'] = failure(values['result'])
72 msg = """Build of %(package)s %(version)s in %(pocket)s %(result)s while %(failed_stage)s.
76 (Build %(build_id)s was submitted by %(principal)s at %(inserted_at)s.)""" % values
79 def submit_completion_msg(succeeded, values, verbose=True, success=lambda x: x, failure=lambda x: x):
82 values['result'] = success(values['result'])
84 values['result'] = failure(values['result'])
85 msg = """Submission of %(commit)s to be built in %(pocket)s %(result)s.
86 Build submitted by %(principal)s.""" % values
89 def repo_creation_msg(succeeded, values, verbose=True, success=lambda x: x, failure=lambda x: x):
92 msg = '%(principal)s just created a new repository, %(category)s/%(name)s.git' % values
96 POST_BUILD = 'post-build'
97 FAILED_BUILD = 'failed-build'
98 POST_SUBMIT = 'post-submit'
99 FAILED_SUBMIT = 'failed-submit'
100 POST_ADD_REPO = 'post-add-repo'
102 # Types of communication
107 message_generators = {
108 ZEPHYR : { POST_BUILD : build_completion_msg,
109 FAILED_BUILD : build_completion_msg,
110 POST_SUBMIT : submit_completion_msg,
111 FAILED_SUBMIT : submit_completion_msg,
112 POST_ADD_REPO : repo_creation_msg },
113 MAIL : { POST_BUILD : build_completion_msg,
114 FAILED_BUILD : build_completion_msg,
115 POST_SUBMIT : submit_completion_msg,
116 FAILED_SUBMIT : submit_completion_msg,
117 POST_ADD_REPO : repo_creation_msg }
120 def zephyr_escape(m):
121 m = re.sub('@', '@@', m)
122 m = re.sub('}', '@(})', m)
125 def zephyr_success(m):
126 return '}@{@color(green)%s}@{' % zephyr_escape(m)
128 def zephyr_failure(m):
129 return '}@{@color(red)%s}@{' % zephyr_escape(m)
132 parser = optparse.OptionParser('Usage: %prog [options] [arguments]')
133 opts, args = parser.parse_args()
134 prog = os.path.basename(sys.argv[0])
137 if prog == POST_BUILD:
138 hook_config = config.build.hooks.post_build
139 elif prog == FAILED_BUILD:
140 hook_config = config.build.hooks.failed_build
141 elif prog == POST_SUBMIT:
142 hook_config = config.build.hooks.post_submit
143 elif prog == FAILED_SUBMIT:
144 hook_config = config.build.hooks.failed_submit
145 elif prog == POST_ADD_REPO:
146 hook_config = config.build.hooks.post_add_repo
148 parser.error('hook script invoked with unrecognized name %s' % prog)
150 except common.InvirtConfigError:
151 print >>sys.stderr, 'No hook configuration found for %s.' % prog
154 if prog in [POST_BUILD, FAILED_BUILD]:
156 parser.set_usage('Usage: %prog [options] build_id')
160 build = database.Build.query().get(args[0])
161 short_commit = builder.canonicalize_commit(build.package, build.commit, shorten=True)
162 values = { 'build_id' : build.build_id,
163 'commit' : build.commit,
164 'failed_stage' : build.failed_stage,
165 'inserted_at' : build.inserted_at,
166 'package' : build.package,
167 'pocket' : build.pocket,
168 'principal' : build.principal,
169 'short_commit' : short_commit,
170 'traceback' : build.traceback,
171 'version' : build.version,
172 'default_instance' : 'build_%(build_id)s',
173 'default_subject' : 'XVM build %(result)s: %(package)s %(version)s in %(pocket)s'}
175 assert prog == POST_BUILD
176 values['result'] = 'succeeded'
179 assert prog == FAILED_BUILD
180 values['result'] = 'failed'
182 elif prog in [POST_SUBMIT, FAILED_SUBMIT]:
184 parser.set_usage('Usage: %prog [options] pocket package commit principal')
187 values = { 'pocket' : args[0],
190 'principal' : args[3],
191 'default_instance' : 'submission',
192 'default_subject' : 'Submission %(result)s: %(package)s %(version)s in %(pocket)s'}
193 if prog == POST_SUBMIT:
194 values['result'] = 'succeeded'
197 values['result'] = 'failed'
199 elif prog in [POST_ADD_REPO]:
201 parser.set_usage('Usage: %prog [options] category name principal')
204 values = { 'category' : args[0],
206 'principal' : args[2],
207 'default_instance' : 'new-repo',
208 'default_subject' : 'New repository %(category)s/%(name)s'}
211 raise AssertionError('Impossible state')
214 zephyr_config = hook_config.zephyr
215 klass = zephyr_config['class'] % values
216 except common.InvirtConfigError:
217 print >>sys.stderr, 'No zephyr configuration specified for %s.' % prog
219 make_msg = message_generators[ZEPHYR][prog]
220 msg = '@{%s}' % make_msg(succeeded, values, verbose=False,
221 success=zephyr_success, failure=zephyr_failure)
222 instance = zephyr_config.get('instance', values['default_instance']) % values
223 zsig = zephyr_config.get('zsig', 'XVM Buildbot') % values
224 common.captureOutput(['zwrite', '-c', klass, '-i', instance, '-s',
225 zsig, '-d', '-m', msg],
226 stdout=None, stderr=None)
229 mail_config = hook_config.mail
230 to = mail_config.to % values
231 sender = mail_config['from'] % values
232 except common.InvirtConfigError:
233 print >>sys.stderr, 'No email configuration specified for %s.' % prog
235 make_msg = message_generators[MAIL][prog]
236 msg = make_msg(succeeded, values)
237 email = text.MIMEText(msg)
238 email['To'] = to % values
239 email['From'] = sender % values
240 email['Subject'] = mail_config.get('subject', values['default_subject']) % values
241 common.captureOutput(['sendmail', '-t'], email.as_string(),
242 stdout=None, stderr=None)
244 if __name__ == '__main__':