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.xmlrpc import XMLRPCServer
26 from buildbot.status.web.about import AboutBuildbot
27 from buildbot.status.web.authz import Authz
28 from buildbot.status.web.auth import AuthFailResource
29 from buildbot.status.web.root import RootPage
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 /changes : summarize all ChangeSources
71 /changes/CHANGENUM: a page describing a single Change
72 /schedulers/SCHEDULERNAME: a page describing a Scheduler, including
73 a description of its behavior, a list of the
74 Builders it triggers, and list of the Changes
75 that are queued awaiting the tree-stable
76 timer, and controls to accelerate the timer.
77 /buildslaves : list all BuildSlaves
78 /buildslaves/SLAVENAME : describe a single BuildSlave
79 /one_line_per_build : summarize the last few builds, one line each
80 /one_line_per_build/BUILDERNAME : same, but only for a single builder
81 /about : describe this buildmaster (Buildbot and support library versions)
82 /xmlrpc : (not yet implemented) an XMLRPC server with build status
83
84 and more! see the manual.
85
86
87 All URLs for pages which are not defined here are used to look
88 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html.
89 This means that /robots.txt or /favicon.ico can be placed in
90 that directory
91
92 This webserver uses the jinja2 template system to generate the web pages
93 (see http://jinja.pocoo.org/2/) and by default loads pages from the
94 buildbot.status.web.templates package. Any file here can be overridden by placing
95 a corresponding file in the master's 'templates' directory.
96
97 The main customization points are layout.html which loads style sheet
98 (css) and provides header and footer content, and root.html, which
99 generates the root page.
100
101 All of the resources provided by this service use relative URLs to reach
102 each other. The only absolute links are the c['projectURL'] links at the
103 top and bottom of the page, and the buildbot home-page link at the
104 bottom.
105
106 Buildbot uses some generic classes to identify the type of object, and
107 some more specific classes for the various kinds of those types. It does
108 this by specifying both in the class attributes where applicable,
109 separated by a space. It is important that in your CSS you declare the
110 more generic class styles above the more specific ones. For example,
111 first define a style for .Event, and below that for .SUCCESS
112
113 The following CSS class names are used:
114 - Activity, Event, BuildStep, LastBuild: general classes
115 - waiting, interlocked, building, offline, idle: Activity states
116 - start, running, success, failure, warnings, skipped, exception:
117 LastBuild and BuildStep states
118 - Change: box with change
119 - Builder: box for builder name (at top)
120 - Project
121 - Time
122
123 """
124
125
126
127
128
129
130
131 - def __init__(self, http_port=None, distrib_port=None, allowForce=None,
132 public_html="public_html", site=None, numbuilds=20,
133 num_events=200, num_events_max=None, auth=None,
134 order_console_by_time=False, changecommentlink=None,
135 revlink=None, projects=None, repositories=None,
136 authz=None, logRotateLength=None, maxRotatedFiles=None):
137 """Run a web server that provides Buildbot status.
138
139 @type http_port: int or L{twisted.application.strports} string
140 @param http_port: a strports specification describing which port the
141 buildbot should use for its web server, with the
142 Waterfall display as the root page. For backwards
143 compatibility this can also be an int. Use
144 'tcp:8000' to listen on that port, or
145 'tcp:12345:interface=127.0.0.1' if you only want
146 local processes to connect to it (perhaps because
147 you are using an HTTP reverse proxy to make the
148 buildbot available to the outside world, and do not
149 want to make the raw port visible).
150
151 @type distrib_port: int or L{twisted.application.strports} string
152 @param distrib_port: Use this if you want to publish the Waterfall
153 page using web.distrib instead. The most common
154 case is to provide a string that is an absolute
155 pathname to the unix socket on which the
156 publisher should listen
157 (C{os.path.expanduser(~/.twistd-web-pb)} will
158 match the default settings of a standard
159 twisted.web 'personal web server'). Another
160 possibility is to pass an integer, which means
161 the publisher should listen on a TCP socket,
162 allowing the web server to be on a different
163 machine entirely. Both forms are provided for
164 backwards compatibility; the preferred form is a
165 strports specification like
166 'unix:/home/buildbot/.twistd-web-pb'. Providing
167 a non-absolute pathname will probably confuse
168 the strports parser.
169
170 @param allowForce: deprecated; use authz instead
171 @param auth: deprecated; use with authz
172
173 @param authz: a buildbot.status.web.authz.Authz instance giving the authorization
174 parameters for this view
175
176 @param public_html: the path to the public_html directory for this display,
177 either absolute or relative to the basedir. The default
178 is 'public_html', which selects BASEDIR/public_html.
179
180 @type site: None or L{twisted.web.server.Site}
181 @param site: Use this if you want to define your own object instead of
182 using the default.`
183
184 @type numbuilds: int
185 @param numbuilds: Default number of entries in lists at the /one_line_per_build
186 and /builders/FOO URLs. This default can be overriden both programatically ---
187 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder
188 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL.
189
190 @type num_events: int
191 @param num_events: Default number of events to show in the waterfall.
192
193 @type num_events_max: int
194 @param num_events_max: The maximum number of events that are allowed to be
195 shown in the waterfall. The default value of C{None} will disable this
196 check
197
198 @type auth: a L{status.web.auth.IAuth} or C{None}
199 @param auth: an object that performs authentication to restrict access
200 to the C{allowForce} features. Ignored if C{allowForce}
201 is not C{True}. If C{auth} is C{None}, people can force or
202 stop builds without auth.
203
204 @type order_console_by_time: bool
205 @param order_console_by_time: Whether to order changes (commits) in the console
206 view according to the time they were created (for VCS like Git) or
207 according to their integer revision numbers (for VCS like SVN).
208
209 @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None}
210 @param changecommentlink: adds links to ticket/bug ids in change comments,
211 see buildbot.status.web.base.changecommentlink for details
212
213 @type revlink: callable, dict, string or C{None}
214 @param revlink: decorations revision ids with links to a web-view,
215 see buildbot.status.web.base.revlink for details
216
217 @type projects: callable, dict or c{None}
218 @param projects: maps project identifiers to URLs, so that any project listed
219 is automatically decorated with a link to it's front page.
220 see buildbot.status.web.base.dictlink for details
221
222 @type repositories: callable, dict or c{None}
223 @param repositories: maps repository identifiers to URLs, so that any project listed
224 is automatically decorated with a link to it's web view.
225 see buildbot.status.web.base.dictlink for details
226
227 @type logRotateLength: None or int
228 @param logRotateLength: file size at which the http.log is rotated/reset.
229 If not set, the value set in the buildbot.tac will be used,
230 falling back to the BuildMaster's default value (1 Mb).
231
232 @type maxRotatedFiles: None or int
233 @param maxRotatedFiles: number of old http.log files to keep during log rotation.
234 If not set, the value set in the buildbot.tac will be used,
235 falling back to the BuildMaster's default value (10 files).
236
237 """
238
239 service.MultiService.__init__(self)
240 if type(http_port) is int:
241 http_port = "tcp:%d" % http_port
242 self.http_port = http_port
243 if distrib_port is not None:
244 if type(distrib_port) is int:
245 distrib_port = "tcp:%d" % distrib_port
246 if distrib_port[0] in "/~.":
247 distrib_port = "unix:%s" % distrib_port
248 self.distrib_port = distrib_port
249 self.num_events = num_events
250 if num_events_max:
251 assert num_events_max >= num_events
252 self.num_events_max = num_events_max
253 self.public_html = public_html
254
255
256 if authz:
257 if allowForce is not None:
258 raise ValueError("cannot use both allowForce and authz parameters")
259 if auth:
260 raise ValueError("cannot use both auth and authz parameters (pass "
261 "auth as an Authz parameter)")
262 else:
263
264 if allowForce and auth:
265 authz = Authz(auth=auth, default_action="auth")
266 elif allowForce:
267 authz = Authz(default_action=True)
268 else:
269 if auth:
270 log.msg("Warning: Ignoring authentication. Search for 'authorization'"
271 " in the manual")
272 authz = Authz()
273
274 self.authz = authz
275
276 self.orderConsoleByTime = order_console_by_time
277
278
279 self.site = site
280
281
282 self.logRotateLength = logRotateLength
283 self.maxRotatedFiles = maxRotatedFiles
284
285
286 self.childrenToBeAdded = {}
287 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events,
288 num_events_max=num_events_max)
289
290
291 self.templates = createJinjaEnv(revlink, changecommentlink,
292 repositories, projects)
293
294
295
296 self.channels = weakref.WeakKeyDictionary()
297
298
299
300 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
301
302 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events,
303 num_events_max=num_events_max))
304 self.putChild("grid", GridStatusResource())
305 self.putChild("console", ConsoleStatusResource(
306 orderByTime=self.orderConsoleByTime))
307 self.putChild("tgrid", TransposedGridStatusResource())
308 self.putChild("builders", BuildersResource())
309 self.putChild("one_box_per_builder", Redirect("/builders"))
310 self.putChild("changes", ChangesResource())
311 self.putChild("buildslaves", BuildSlavesResource())
312 self.putChild("buildstatus", BuildStatusStatusResource())
313
314 self.putChild("one_line_per_build",
315 OneLinePerBuild(numbuilds=numbuilds))
316 self.putChild("xmlrpc", XMLRPCServer())
317 self.putChild("about", AboutBuildbot())
318 self.putChild("authfail", AuthFailResource())
319
321 if self.http_port is None:
322 return "<WebStatus on path %s at %s>" % (self.distrib_port,
323 hex(id(self)))
324 if self.distrib_port is None:
325 return "<WebStatus on port %s at %s>" % (self.http_port,
326 hex(id(self)))
327 return ("<WebStatus on port %s and path %s at %s>" %
328 (self.http_port, self.distrib_port, hex(id(self))))
329
331 service.MultiService.setServiceParent(self, parent)
332
333
334
335
336
337 self.master = parent
338
339 def either(a,b):
340 if a:
341 return a
342 else:
343 return b
344
345 rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength)
346 maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles)
347
348 if not self.site:
349
350 class RotateLogSite(server.Site):
351 def _openLogFile(self, path):
352 try:
353 from twisted.python.logfile import LogFile
354 log.msg("Setting up http.log rotating %s files of %s bytes each" %
355 (maxRotatedFiles, rotateLength))
356 if hasattr(LogFile, "fromFullPath"):
357 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles)
358 else:
359 log.msg("WebStatus: rotated http logs are not supported on this version of Twisted")
360 except ImportError, e:
361 log.msg("WebStatus: Unable to set up rotating http.log: %s" % e)
362
363
364 return server.Site._openLogFile(self, path)
365
366
367
368 root = static.Data("placeholder", "text/plain")
369 httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log"))
370 self.site = RotateLogSite(root, logPath=httplog)
371
372
373
374 self.site.buildbot_service = self
375
376 if self.http_port is not None:
377 s = strports.service(self.http_port, self.site)
378 s.setServiceParent(self)
379 if self.distrib_port is not None:
380 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site))
381 s = strports.service(self.distrib_port, f)
382 s.setServiceParent(self)
383
384 self.setupSite()
385
412
413 - def putChild(self, name, child_resource):
414 """This behaves a lot like root.putChild() . """
415 self.childrenToBeAdded[name] = child_resource
416
418 self.channels[channel] = 1
419
421 for channel in self.channels:
422 try:
423 channel.transport.loseConnection()
424 except:
425 log.msg("WebStatus.stopService: error while disconnecting"
426 " leftover clients")
427 log.err()
428 return service.MultiService.stopService(self)
429
432
435
437
438 s = list(self)[0]
439 return s._port.getHost().port
440
441
442
443
444
445
446
447
448
449
450
451