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