1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os, weakref
18
19 from zope.interface import implements
20 from twisted.python import log
21 from twisted.application import strports, service
22 from twisted.web import server, distrib, static
23 from twisted.spread import pb
24 from twisted.web.util import Redirect
25
26 from buildbot.interfaces import IStatusReceiver
27
28 from buildbot.status.web.base import StaticFile, createJinjaEnv
29 from buildbot.status.web.feeds import Rss20StatusResource, \
30 Atom10StatusResource
31 from buildbot.status.web.waterfall import WaterfallStatusResource
32 from buildbot.status.web.console import ConsoleStatusResource
33 from buildbot.status.web.olpb import OneLinePerBuild
34 from buildbot.status.web.grid import GridStatusResource, TransposedGridStatusResource
35 from buildbot.status.web.changes import ChangesResource
36 from buildbot.status.web.builder import BuildersResource
37 from buildbot.status.web.buildstatus import BuildStatusStatusResource
38 from buildbot.status.web.slaves import BuildSlavesResource
39 from buildbot.status.web.status_json import JsonStatusResource
40 from buildbot.status.web.about import AboutBuildbot
41 from buildbot.status.web.authz import Authz
42 from buildbot.status.web.auth import AuthFailResource
43 from buildbot.status.web.root import RootPage
44 from buildbot.status.web.change_hook import ChangeHookResource
45
46
47
48
50 implements(IStatusReceiver)
51
52
53
54
55
56
57
58 """
59 The webserver provided by this class has the following resources:
60
61 /waterfall : the big time-oriented 'waterfall' display, with links
62 to individual changes, builders, builds, steps, and logs.
63 A number of query-arguments can be added to influence
64 the display.
65 /rss : a rss feed summarizing all failed builds. The same
66 query-arguments used by 'waterfall' can be added to
67 influence the feed output.
68 /atom : an atom feed summarizing all failed builds. The same
69 query-arguments used by 'waterfall' can be added to
70 influence the feed output.
71 /grid : another summary display that shows a grid of builds, with
72 sourcestamps on the x axis, and builders on the y. Query
73 arguments similar to those for the waterfall can be added.
74 /tgrid : similar to the grid display, but the commits are down the
75 left side, and the build hosts are across the top.
76 /builders/BUILDERNAME: a page summarizing the builder. This includes
77 references to the Schedulers that feed it,
78 any builds currently in the queue, which
79 buildslaves are designated or attached, and a
80 summary of the build process it uses.
81 /builders/BUILDERNAME/builds/NUM: a page describing a single Build
82 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step
83 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog
84 /builders/_all/{force,stop}: force a build/stop building on all builders.
85 /buildstatus?builder=...&number=...: an embedded iframe for the console
86 /changes : summarize all ChangeSources
87 /changes/CHANGENUM: a page describing a single Change
88 /buildslaves : list all BuildSlaves
89 /buildslaves/SLAVENAME : describe a single BuildSlave
90 /one_line_per_build : summarize the last few builds, one line each
91 /one_line_per_build/BUILDERNAME : same, but only for a single builder
92 /about : describe this buildmaster (Buildbot and support library versions)
93 /change_hook[/DIALECT] : accepts changes from external sources, optionally
94 choosing the dialect that will be permitted
95 (i.e. github format, etc..)
96
97 and more! see the manual.
98
99
100 All URLs for pages which are not defined here are used to look
101 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html.
102 This means that /robots.txt or /favicon.ico can be placed in
103 that directory
104
105 This webserver uses the jinja2 template system to generate the web pages
106 (see http://jinja.pocoo.org/2/) and by default loads pages from the
107 buildbot.status.web.templates package. Any file here can be overridden by placing
108 a corresponding file in the master's 'templates' directory.
109
110 The main customization points are layout.html which loads style sheet
111 (css) and provides header and footer content, and root.html, which
112 generates the root page.
113
114 All of the resources provided by this service use relative URLs to reach
115 each other. The only absolute links are the c['titleURL'] links at the
116 top and bottom of the page, and the buildbot home-page link at the
117 bottom.
118
119 Buildbot uses some generic classes to identify the type of object, and
120 some more specific classes for the various kinds of those types. It does
121 this by specifying both in the class attributes where applicable,
122 separated by a space. It is important that in your CSS you declare the
123 more generic class styles above the more specific ones. For example,
124 first define a style for .Event, and below that for .SUCCESS
125
126 The following CSS class names are used:
127 - Activity, Event, BuildStep, LastBuild: general classes
128 - waiting, interlocked, building, offline, idle: Activity states
129 - start, running, success, failure, warnings, skipped, exception:
130 LastBuild and BuildStep states
131 - Change: box with change
132 - Builder: box for builder name (at top)
133 - Project
134 - Time
135
136 """
137
138
139
140
141
142
143
144 - def __init__(self, http_port=None, distrib_port=None, allowForce=None,
145 public_html="public_html", site=None, numbuilds=20,
146 num_events=200, num_events_max=None, auth=None,
147 order_console_by_time=False, changecommentlink=None,
148 revlink=None, projects=None, repositories=None,
149 authz=None, logRotateLength=None, maxRotatedFiles=None,
150 change_hook_dialects = {}, provide_feeds=None):
151 """Run a web server that provides Buildbot status.
152
153 @type http_port: int or L{twisted.application.strports} string
154 @param http_port: a strports specification describing which port the
155 buildbot should use for its web server, with the
156 Waterfall display as the root page. For backwards
157 compatibility this can also be an int. Use
158 'tcp:8000' to listen on that port, or
159 'tcp:12345:interface=127.0.0.1' if you only want
160 local processes to connect to it (perhaps because
161 you are using an HTTP reverse proxy to make the
162 buildbot available to the outside world, and do not
163 want to make the raw port visible).
164
165 @type distrib_port: int or L{twisted.application.strports} string
166 @param distrib_port: Use this if you want to publish the Waterfall
167 page using web.distrib instead. The most common
168 case is to provide a string that is an absolute
169 pathname to the unix socket on which the
170 publisher should listen
171 (C{os.path.expanduser(~/.twistd-web-pb)} will
172 match the default settings of a standard
173 twisted.web 'personal web server'). Another
174 possibility is to pass an integer, which means
175 the publisher should listen on a TCP socket,
176 allowing the web server to be on a different
177 machine entirely. Both forms are provided for
178 backwards compatibility; the preferred form is a
179 strports specification like
180 'unix:/home/buildbot/.twistd-web-pb'. Providing
181 a non-absolute pathname will probably confuse
182 the strports parser.
183
184 @param allowForce: deprecated; use authz instead
185 @param auth: deprecated; use with authz
186
187 @param authz: a buildbot.status.web.authz.Authz instance giving the authorization
188 parameters for this view
189
190 @param public_html: the path to the public_html directory for this display,
191 either absolute or relative to the basedir. The default
192 is 'public_html', which selects BASEDIR/public_html.
193
194 @type site: None or L{twisted.web.server.Site}
195 @param site: Use this if you want to define your own object instead of
196 using the default.`
197
198 @type numbuilds: int
199 @param numbuilds: Default number of entries in lists at the /one_line_per_build
200 and /builders/FOO URLs. This default can be overriden both programatically ---
201 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder
202 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL.
203
204 @type num_events: int
205 @param num_events: Default number of events to show in the waterfall.
206
207 @type num_events_max: int
208 @param num_events_max: The maximum number of events that are allowed to be
209 shown in the waterfall. The default value of C{None} will disable this
210 check
211
212 @type auth: a L{status.web.auth.IAuth} or C{None}
213 @param auth: an object that performs authentication to restrict access
214 to the C{allowForce} features. Ignored if C{allowForce}
215 is not C{True}. If C{auth} is C{None}, people can force or
216 stop builds without auth.
217
218 @type order_console_by_time: bool
219 @param order_console_by_time: Whether to order changes (commits) in the console
220 view according to the time they were created (for VCS like Git) or
221 according to their integer revision numbers (for VCS like SVN).
222
223 @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None}
224 @param changecommentlink: adds links to ticket/bug ids in change comments,
225 see buildbot.status.web.base.changecommentlink for details
226
227 @type revlink: callable, dict, string or C{None}
228 @param revlink: decorations revision ids with links to a web-view,
229 see buildbot.status.web.base.revlink for details
230
231 @type projects: callable, dict or c{None}
232 @param projects: maps project identifiers to URLs, so that any project listed
233 is automatically decorated with a link to it's front page.
234 see buildbot.status.web.base.dictlink for details
235
236 @type repositories: callable, dict or c{None}
237 @param repositories: maps repository identifiers to URLs, so that any project listed
238 is automatically decorated with a link to it's web view.
239 see buildbot.status.web.base.dictlink for details
240
241 @type logRotateLength: None or int
242 @param logRotateLength: file size at which the http.log is rotated/reset.
243 If not set, the value set in the buildbot.tac will be used,
244 falling back to the BuildMaster's default value (1 Mb).
245
246 @type maxRotatedFiles: None or int
247 @param maxRotatedFiles: number of old http.log files to keep during log rotation.
248 If not set, the value set in the buildbot.tac will be used,
249 falling back to the BuildMaster's default value (10 files).
250
251 @type change_hook_dialects: None or dict
252 @param change_hook_dialects: If empty, disables change_hook support, otherwise
253 whitelists valid dialects. In the format of
254 {"dialect1": "Option1", "dialect2", None}
255 Where the values are options that will be passed
256 to the dialect
257
258 To enable the DEFAULT handler, use a key of DEFAULT
259
260
261
262
263 @type provide_feeds: None or list
264 @param provide_feeds: If empty, provides atom, json, and rss feeds.
265 Otherwise, a dictionary of strings of
266 the type of feeds provided. Current
267 possibilities are "atom", "json", and "rss"
268 """
269
270 service.MultiService.__init__(self)
271 if type(http_port) is int:
272 http_port = "tcp:%d" % http_port
273 self.http_port = http_port
274 if distrib_port is not None:
275 if type(distrib_port) is int:
276 distrib_port = "tcp:%d" % distrib_port
277 if distrib_port[0] in "/~.":
278 distrib_port = "unix:%s" % distrib_port
279 self.distrib_port = distrib_port
280 self.num_events = num_events
281 if num_events_max:
282 assert num_events_max >= num_events
283 self.num_events_max = num_events_max
284 self.public_html = public_html
285
286
287 if authz:
288 if allowForce is not None:
289 raise ValueError("cannot use both allowForce and authz parameters")
290 if auth:
291 raise ValueError("cannot use both auth and authz parameters (pass "
292 "auth as an Authz parameter)")
293 else:
294
295 if allowForce and auth:
296 authz = Authz(auth=auth, default_action="auth")
297 elif allowForce:
298 authz = Authz(default_action=True)
299 else:
300 if auth:
301 log.msg("Warning: Ignoring authentication. Search for 'authorization'"
302 " in the manual")
303 authz = Authz()
304
305 self.authz = authz
306
307 self.orderConsoleByTime = order_console_by_time
308
309
310 self.site = site
311
312
313 self.logRotateLength = logRotateLength
314 self.maxRotatedFiles = maxRotatedFiles
315
316
317 self.childrenToBeAdded = {}
318 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events,
319 num_events_max=num_events_max)
320
321
322 self.templates = createJinjaEnv(revlink, changecommentlink,
323 repositories, projects)
324
325
326
327 self.channels = weakref.WeakKeyDictionary()
328
329
330 self.change_hook_dialects = {}
331 if change_hook_dialects:
332 self.change_hook_dialects = change_hook_dialects
333 self.putChild("change_hook", ChangeHookResource(dialects = self.change_hook_dialects))
334
335
336 if provide_feeds is None:
337 self.provide_feeds = ["atom", "json", "rss"]
338 else:
339 self.provide_feeds = provide_feeds
340
341 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
342
343 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events,
344 num_events_max=num_events_max))
345 self.putChild("grid", GridStatusResource())
346 self.putChild("console", ConsoleStatusResource(
347 orderByTime=self.orderConsoleByTime))
348 self.putChild("tgrid", TransposedGridStatusResource())
349 self.putChild("builders", BuildersResource())
350 self.putChild("one_box_per_builder", Redirect("builders"))
351 self.putChild("changes", ChangesResource())
352 self.putChild("buildslaves", BuildSlavesResource())
353 self.putChild("buildstatus", BuildStatusStatusResource())
354 self.putChild("one_line_per_build",
355 OneLinePerBuild(numbuilds=numbuilds))
356 self.putChild("about", AboutBuildbot())
357 self.putChild("authfail", AuthFailResource())
358
359
361 if self.http_port is None:
362 return "<WebStatus on path %s at %s>" % (self.distrib_port,
363 hex(id(self)))
364 if self.distrib_port is None:
365 return "<WebStatus on port %s at %s>" % (self.http_port,
366 hex(id(self)))
367 return ("<WebStatus on port %s and path %s at %s>" %
368 (self.http_port, self.distrib_port, hex(id(self))))
369
371 service.MultiService.setServiceParent(self, parent)
372
373
374
375
376
377 self.master = parent
378
379 def either(a,b):
380 if a:
381 return a
382 else:
383 return b
384
385 rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength)
386 maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles)
387
388 if not self.site:
389
390 class RotateLogSite(server.Site):
391 def _openLogFile(self, path):
392 try:
393 from twisted.python.logfile import LogFile
394 log.msg("Setting up http.log rotating %s files of %s bytes each" %
395 (maxRotatedFiles, rotateLength))
396 if hasattr(LogFile, "fromFullPath"):
397 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles)
398 else:
399 log.msg("WebStatus: rotated http logs are not supported on this version of Twisted")
400 except ImportError, e:
401 log.msg("WebStatus: Unable to set up rotating http.log: %s" % e)
402
403
404 return server.Site._openLogFile(self, path)
405
406
407
408 root = static.Data("placeholder", "text/plain")
409 httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log"))
410 self.site = RotateLogSite(root, logPath=httplog)
411
412
413
414 self.site.buildbot_service = self
415
416 if self.http_port is not None:
417 s = strports.service(self.http_port, self.site)
418 s.setServiceParent(self)
419 if self.distrib_port is not None:
420 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site))
421 s = strports.service(self.distrib_port, f)
422 s.setServiceParent(self)
423
424 self.setupSite()
425
458
459 - def putChild(self, name, child_resource):
460 """This behaves a lot like root.putChild() . """
461 self.childrenToBeAdded[name] = child_resource
462
464 self.channels[channel] = 1
465
467 for channel in self.channels:
468 try:
469 channel.transport.loseConnection()
470 except:
471 log.msg("WebStatus.stopService: error while disconnecting"
472 " leftover clients")
473 log.err()
474 return service.MultiService.stopService(self)
475
478
481
483
484 s = list(self)[0]
485 return s._port.getHost().port
486
487
488
489
490
491
492
493
494
495
496
497