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