Package buildbot :: Package status :: Package web :: Module builder
[hide private]
[frames] | no frames]

Source Code for Module buildbot.status.web.builder

  1   
  2  from twisted.web.error import NoResource 
  3  from twisted.web import html, static 
  4  from twisted.web.util import Redirect 
  5   
  6  import re, urllib, time 
  7  from twisted.python import log 
  8  from buildbot import interfaces 
  9  from buildbot.status.web.base import HtmlResource, make_row, \ 
 10       make_force_build_form, OneLineMixin, path_to_build, path_to_slave, \ 
 11       path_to_builder, path_to_change 
 12  from buildbot.process.base import BuildRequest 
 13  from buildbot.sourcestamp import SourceStamp 
 14   
 15  from buildbot.status.web.build import BuildsResource, StatusResourceBuild 
 16  from buildbot import util 
 17   
 18  # /builders/$builder 
19 -class StatusResourceBuilder(HtmlResource, OneLineMixin):
20 addSlash = True 21
22 - def __init__(self, builder_status, builder_control):
23 HtmlResource.__init__(self) 24 self.builder_status = builder_status 25 self.builder_control = builder_control
26
27 - def getTitle(self, request):
28 return "Buildbot: %s" % html.escape(self.builder_status.getName())
29
30 - def build_line(self, build, req):
31 buildnum = build.getNumber() 32 buildurl = path_to_build(req, build) 33 data = '<a href="%s">#%d</a> ' % (buildurl, buildnum) 34 35 when = build.getETA() 36 if when is not None: 37 when_time = time.strftime("%H:%M:%S", 38 time.localtime(time.time() + when)) 39 data += "ETA %ds (%s) " % (when, when_time) 40 step = build.getCurrentStep() 41 if step: 42 data += "[%s]" % step.getName() 43 else: 44 data += "[waiting for Lock]" 45 # TODO: is this necessarily the case? 46 47 if self.builder_control is not None: 48 stopURL = path_to_build(req, build) + '/stop' 49 data += ''' 50 <form method="post" action="%s" class="command stopbuild" style="display:inline"> 51 <input type="submit" value="Stop Build" /> 52 </form>''' % stopURL 53 return data
54
55 - def request_line(self, build_request, req):
56 when = time.strftime("%b %d %H:%M:%S", time.localtime(build_request.getSubmitTime())) 57 delay = util.formatInterval(util.now() - build_request.getSubmitTime()) 58 changes = build_request.source.changes 59 if changes: 60 change_strings = [] 61 for c in changes: 62 change_strings.append("<a href=\"%s\">%s</a>" % (path_to_change(req, c), c.who)) 63 if len(change_strings) == 1: 64 reason = "change by %s" % change_strings[0] 65 else: 66 reason = "changes by %s" % ", ".join(change_strings) 67 elif build_request.source.revision: 68 reason = build_request.source.revision 69 else: 70 reason = "no changes specified" 71 72 if self.builder_control is not None: 73 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuild' 74 cancelButton = ''' 75 <form action="%s" class="command cancelbuild" style="display:inline" method="post"> 76 <input type="hidden" name="id" value="%s" /> 77 <input type="submit" value="Cancel Build" /> 78 </form>''' % (cancelURL, id(build_request)) 79 else: 80 cancelButton = "" 81 return "<font size=\"-1\">(%s, waiting %s)</font>%s%s" % (when, delay, cancelButton, reason)
82
83 - def body(self, req):
84 b = self.builder_status 85 control = self.builder_control 86 status = self.getStatus(req) 87 88 slaves = b.getSlaves() 89 connected_slaves = [s for s in slaves if s.isConnected()] 90 91 projectName = status.getProjectName() 92 93 data = '<a href="%s">%s</a>\n' % (self.path_to_root(req), projectName) 94 95 data += "<h1>Builder: %s</h1>\n" % html.escape(b.getName()) 96 97 # the first section shows builds which are currently running, if any. 98 99 current = b.getCurrentBuilds() 100 if current: 101 data += "<h2>Currently Building:</h2>\n" 102 data += "<ul>\n" 103 for build in current: 104 data += " <li>" + self.build_line(build, req) + "</li>\n" 105 data += "</ul>\n" 106 else: 107 data += "<h2>no current builds</h2>\n" 108 109 pending = b.getPendingBuilds() 110 if pending: 111 data += "<h2>Pending Builds:</h2>\n" 112 data += "<ul>\n" 113 for request in pending: 114 data += " <li>" + self.request_line(request, req) + "</li>\n" 115 data += "</ul>\n" 116 117 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuild' 118 data += ''' 119 <form action="%s" class="command cancelbuild" style="display:inline" method="post"> 120 <input type="hidden" name="id" value="all" /> 121 <input type="submit" value="Cancel All" /> 122 </form>''' % cancelURL 123 else: 124 data += "<h2>no pending builds</h2>\n" 125 126 # Then a section with the last 5 builds, with the most recent build 127 # distinguished from the rest. 128 129 data += "<h2>Recent Builds:</h2>\n" 130 data += "(<a href=\"%s\">view in waterfall</a>)\n" % (self.path_to_root(req)+"waterfall?show="+html.escape(b.getName())) 131 data += "<ul>\n" 132 numbuilds = req.args.get('numbuilds', ['5'])[0] 133 for i,build in enumerate(b.generateFinishedBuilds(num_builds=int(numbuilds))): 134 data += " <li>" + self.make_line(req, build, False) + "</li>\n" 135 if i == 0: 136 data += "<br />\n" # separator 137 # TODO: or empty list? 138 data += "</ul>\n" 139 140 141 data += "<h2>Buildslaves:</h2>\n" 142 data += "<ol>\n" 143 for slave in slaves: 144 slaveurl = path_to_slave(req, slave) 145 data += "<li><b><a href=\"%s\">%s</a></b>: " % (html.escape(slaveurl), html.escape(slave.getName())) 146 if slave.isConnected(): 147 data += "CONNECTED\n" 148 if slave.getAdmin(): 149 data += make_row("Admin:", html.escape(slave.getAdmin())) 150 if slave.getHost(): 151 data += "<span class='label'>Host info:</span>\n" 152 data += html.PRE(slave.getHost()) 153 else: 154 data += ("NOT CONNECTED\n") 155 data += "</li>\n" 156 data += "</ol>\n" 157 158 if control is not None and connected_slaves: 159 forceURL = path_to_builder(req, b) + '/force' 160 data += make_force_build_form(forceURL, self.isUsingUserPasswd(req)) 161 elif control is not None: 162 data += """ 163 <p>All buildslaves appear to be offline, so it's not possible 164 to force this build to execute at this time.</p> 165 """ 166 167 if control is not None: 168 pingURL = path_to_builder(req, b) + '/ping' 169 data += """ 170 <form method="post" action="%s" class='command pingbuilder'> 171 <p>To ping the buildslave(s), push the 'Ping' button</p> 172 173 <input type="submit" value="Ping Builder" /> 174 </form> 175 """ % pingURL 176 177 data += self.footer(status, req) 178 179 return data
180
181 - def force(self, req):
182 """ 183 184 Custom properties can be passed from the web form. To do 185 this, subclass this class, overriding the force() method. You 186 can then determine the properties (usually from form values, 187 by inspecting req.args), then pass them to this superclass 188 force method. 189 190 """ 191 name = req.args.get("username", ["<unknown>"])[0] 192 reason = req.args.get("comments", ["<no reason specified>"])[0] 193 branch = req.args.get("branch", [""])[0] 194 revision = req.args.get("revision", [""])[0] 195 196 r = "The web-page 'force build' button was pressed by '%s': %s\n" \ 197 % (name, reason) 198 log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'" 199 " by user '%s'" % (self.builder_status.getName(), branch, 200 revision, name)) 201 202 if not self.builder_control: 203 # TODO: tell the web user that their request was denied 204 log.msg("but builder control is disabled") 205 return Redirect("..") 206 207 if self.isUsingUserPasswd(req): 208 if not self.authUser(req): 209 return Redirect("../../authfail") 210 211 # keep weird stuff out of the branch and revision strings. TODO: 212 # centralize this somewhere. 213 if not re.match(r'^[\w\.\-\/]*$', branch): 214 log.msg("bad branch '%s'" % branch) 215 return Redirect("..") 216 if not re.match(r'^[\w\.\-\/]*$', revision): 217 log.msg("bad revision '%s'" % revision) 218 return Redirect("..") 219 if not branch: 220 branch = None 221 if not revision: 222 revision = None 223 224 # TODO: if we can authenticate that a particular User pushed the 225 # button, use their name instead of None, so they'll be informed of 226 # the results. 227 # TODO2: we can authenticate that a particular User pushed the button 228 # now, so someone can write this support. but it requires a 229 # buildbot.changes.changes.Change instance which is tedious at this 230 # stage to compute 231 s = SourceStamp(branch=branch, revision=revision) 232 req = BuildRequest(r, s, builderName=self.builder_status.getName()) 233 try: 234 self.builder_control.requestBuildSoon(req) 235 except interfaces.NoSlaveError: 236 # TODO: tell the web user that their request could not be 237 # honored 238 pass 239 # send the user back to the builder page 240 return Redirect(".")
241
242 - def ping(self, req):
243 log.msg("web ping of builder '%s'" % self.builder_status.getName()) 244 self.builder_control.ping() # TODO: there ought to be an ISlaveControl 245 # send the user back to the builder page 246 return Redirect(".")
247
248 - def cancel(self, req):
249 try: 250 request_id = req.args.get("id", [None])[0] 251 if request_id == "all": 252 cancel_all = True 253 else: 254 cancel_all = False 255 request_id = int(request_id) 256 except: 257 request_id = None 258 if request_id: 259 for build_req in self.builder_control.getPendingBuilds(): 260 if cancel_all or id(build_req.original_request.status) == request_id: 261 log.msg("Cancelling %s" % build_req) 262 build_req.cancel() 263 if not cancel_all: 264 break 265 return Redirect(".")
266
267 - def getChild(self, path, req):
268 if path == "force": 269 return self.force(req) 270 if path == "ping": 271 return self.ping(req) 272 if path == "events": 273 num = req.postpath.pop(0) 274 req.prepath.append(num) 275 num = int(num) 276 # TODO: is this dead code? .statusbag doesn't exist,right? 277 log.msg("getChild['path']: %s" % req.uri) 278 return NoResource("events are unavailable until code gets fixed") 279 filename = req.postpath.pop(0) 280 req.prepath.append(filename) 281 e = self.builder_status.getEventNumbered(num) 282 if not e: 283 return NoResource("No such event '%d'" % num) 284 file = e.files.get(filename, None) 285 if file == None: 286 return NoResource("No such file '%s'" % filename) 287 if type(file) == type(""): 288 if file[:6] in ("<HTML>", "<html>"): 289 return static.Data(file, "text/html") 290 return static.Data(file, "text/plain") 291 return file 292 if path == "cancelbuild": 293 return self.cancel(req) 294 if path == "builds": 295 return BuildsResource(self.builder_status, self.builder_control) 296 297 return HtmlResource.getChild(self, path, req)
298 299 300 # /builders/_all
301 -class StatusResourceAllBuilders(HtmlResource, OneLineMixin):
302
303 - def __init__(self, status, control):
304 HtmlResource.__init__(self) 305 self.status = status 306 self.control = control
307
308 - def getChild(self, path, req):
309 if path == "force": 310 return self.force(req) 311 if path == "stop": 312 return self.stop(req) 313 314 return HtmlResource.getChild(self, path, req)
315
316 - def force(self, req):
317 for bname in self.status.getBuilderNames(): 318 builder_status = self.status.getBuilder(bname) 319 builder_control = None 320 c = self.getControl(req) 321 if c: 322 builder_control = c.getBuilder(bname) 323 build = StatusResourceBuilder(builder_status, builder_control) 324 build.force(req) 325 # back to the welcome page 326 return Redirect("../..")
327
328 - def stop(self, req):
329 for bname in self.status.getBuilderNames(): 330 builder_status = self.status.getBuilder(bname) 331 builder_control = None 332 c = self.getControl(req) 333 if c: 334 builder_control = c.getBuilder(bname) 335 (state, current_builds) = builder_status.getState() 336 if state != "building": 337 continue 338 for b in current_builds: 339 build_status = builder_status.getBuild(b.number) 340 if not build_status: 341 continue 342 if builder_control: 343 build_control = builder_control.getBuild(b.number) 344 else: 345 build_control = None 346 build = StatusResourceBuild(build_status, build_control, 347 builder_control) 348 build.stop(req) 349 # go back to the welcome page 350 return Redirect("../..")
351 352 353 # /builders
354 -class BuildersResource(HtmlResource):
355 title = "Builders" 356 addSlash = True 357
358 - def body(self, req):
359 s = self.getStatus(req) 360 data = "" 361 data += "<h1>Builders</h1>\n" 362 363 # TODO: this is really basic. It should be expanded to include a 364 # brief one-line summary of the builder (perhaps with whatever the 365 # builder is currently doing) 366 data += "<ol>\n" 367 for bname in s.getBuilderNames(): 368 data += (' <li><a href="%s">%s</a></li>\n' % 369 (req.childLink(urllib.quote(bname, safe='')), 370 bname)) 371 data += "</ol>\n" 372 373 data += self.footer(s, req) 374 375 return data
376
377 - def getChild(self, path, req):
378 s = self.getStatus(req) 379 if path in s.getBuilderNames(): 380 builder_status = s.getBuilder(path) 381 builder_control = None 382 c = self.getControl(req) 383 if c: 384 builder_control = c.getBuilder(path) 385 return StatusResourceBuilder(builder_status, builder_control) 386 if path == "_all": 387 return StatusResourceAllBuilders(self.getStatus(req), 388 self.getControl(req)) 389 390 return HtmlResource.getChild(self, path, req)
391