|
# This file is part of Buildbot. Buildbot is free software: you can # redistribute it and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation, version 2. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 51 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # Copyright Buildbot Team Members
"""I represent a box in the top row of the waterfall display: the one which shows the status of the last build for each builder.""" """Return a Box instance, which can produce a <td> cell. """
"""I represent the 'current activity' box, just above the builder name.""" """Return a Box instance, which can produce a <td> cell. """
"""I represent a box in the waterfall display.""" """Return a Box instance, which wraps an Event and can produce a <td> cell. """
pass
WARNINGS: "warnings", FAILURE: "failure", SKIPPED: "skipped", EXCEPTION: "exception", RETRY: "retry", None: "", }
""" Fetch custom build properties from the HTTP request of a "Force build" or "Resubmit build" HTML form. Check the names for valid strings, and return None if a problem is found. Return a new Properties object containing each property found in req. """ master = req.site.buildbot_service.master pname_validate = master.config.validation['property_name'] pval_validate = master.config.validation['property_value'] properties = Properties() i = 1 while True: pname = req.args.get("property%dname" % i, [""])[0] pvalue = req.args.get("property%dvalue" % i, [""])[0] if not pname: break if not pname_validate.match(pname) \ or not pval_validate.match(pvalue): log.msg("bad property name='%s', value='%s'" % (pname, pvalue)) return None properties.setProperty(pname, pvalue, "Force Build Form") i = i + 1
return properties
""" Return the class to use for a finished build or buildstep, based on the result. """ # FIXME: this getResults duplicity might need to be fixed result = b.getResults() if isinstance(b, build.BuildStatus): result = b.getResults() elif isinstance(b, buildstep.BuildStepStatus): result = b.getResults()[0] # after forcing a build, b.getResults() returns ((None, []), []), ugh if isinstance(result, tuple): result = result[0] else: raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
if result == None: # FIXME: this happens when a buildstep is running ? return "running" return builder.Results[result]
# /waterfall : ['waterfall'] -> '' # /somewhere/lower : ['somewhere', 'lower'] -> '../' # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../' # / : [] -> '' if request.prepath: segs = len(request.prepath) - 1 else: segs = 0 root = "../" * segs return root
return path_to_root(request) + "authfail"
return path_to_root(request) + "authzfail"
return (path_to_root(request) + "builders/" + urllib.quote(builderstatus.getName(), safe=''))
return (path_to_builder(request, buildstatus.getBuilder()) + "/builds/%d" % buildstatus.getNumber())
return (path_to_build(request, stepstatus.getBuild()) + "/steps/%s" % urllib.quote(stepstatus.getName(), safe=''))
return (path_to_root(request) + "buildslaves/" + urllib.quote(slave.getName(), safe=''))
return (path_to_root(request) + "changes/%s" % change.number)
# a Box wraps an Event. The Box has HTML <td> parameters that Events # lack, and it has a base URL to which each File's name is relative. # Events don't know about HTML. **parms): self.text = text self.class_ = class_ self.urlbase = urlbase self.show_idle = 0 if parms.has_key('show_idle'): del parms['show_idle'] self.show_idle = 1
self.parms = parms # parms is a dict of HTML parameters for the <td> element that will # represent this Event in the waterfall display.
props.update(self.parms) text = self.text if not text and self.show_idle: text = ["[idle]"] props['class'] = self.class_ props['text'] = text; return props
return request.site.buildbot_service.getStatus()
return self.pageTitle
return request.site.buildbot_service.authz
return request.site.buildbot_service.master
status = self.getStatus(request) rootpath = path_to_root(request) locale_enc = locale.getdefaultlocale()[1] if locale_enc is not None: locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc) else: locale_tz = unicode(time.tzname[time.localtime()[-1]]) return dict(title_url = status.getTitleURL(), title = status.getTitle(), stylesheet = rootpath + 'default.css', path_to_root = rootpath, version = version, time = time.strftime("%a %d %b %Y %H:%M:%S", time.localtime(util.now())), tz = locale_tz, metatags = [], pageTitle = self.getPageTitle(request), welcomeurl = rootpath, authz = self.getAuthz(request), request = request, alert_msg = request.args.get("alert_msg", [""])[0], )
"""A resource that performs some action, then redirects to a new URL."""
return self
""" Perform the action, and return the URL to redirect to
@param request: the web request @returns: URL via Deferred can also return (URL, alert_msg) to display simple feedback to user in case of failure """
url, alert_msg = url if alert_msg: url += "?alert_msg="+urllib.quote(alert_msg, safe='') except RuntimeError: # this occurs when the client has already disconnected; ignore # it (see #2027) log.msg("http client disconnected before results were sent")
# this is a cheap sort of template thingy
if self.addSlash and path == "" and len(request.postpath) == 0: return self return resource.Resource.getChild(self, path, request)
""" Generate content using the standard layout and the result of the C{body} method.
This is suitable for the case where a resource just wants to generate the body of a page. It depends on another method, C{body}, being defined to accept the request object and return a C{str}. C{render} will call this method and to generate the response body. """ body = self.body(req) context['content'] = body template = req.site.buildbot_service.templates.get_template( "empty.html") return template.render(**context)
# tell the WebStatus about the HTTPChannel that got opened, so they # can close it if we get reconfigured and the WebStatus goes away. # They keep a weakref to this, since chances are good that it will be # closed by the browser or by us before we get reconfigured. See # ticket #102 for details. if hasattr(request, "channel"): # web.distrib.Request has no .channel request.site.buildbot_service.registerChannel(request.channel)
# Our pages no longer require that their URL end in a slash. Instead, # they all use request.childLink() or some equivalent which takes the # last path component into account. This clause is left here for # historical and educational purposes. if False and self.addSlash and request.prepath[-1] != '': # this is intended to behave like request.URLPath().child('') # but we need a relative URL, since we might be living behind a # reverse proxy # # note that the Location: header (as used in redirects) are # required to have absolute URIs, and my attempt to handle # reverse-proxies gracefully violates rfc2616. This frequently # works, but single-component paths sometimes break. The best # strategy is to avoid these redirects whenever possible by using # HREFs with trailing slashes, and only use the redirects for # manually entered URLs. url = request.prePathURL() scheme, netloc, path, query, fragment = urlparse.urlsplit(url) new_url = request.prepath[-1] + "/" if query: new_url += "?" + query request.redirect(new_url) return ''
ctx = self.getContext(request)
d = defer.maybeDeferred(lambda : self.content(request, ctx)) def handle(data): if isinstance(data, unicode): data = data.encode("utf-8") request.setHeader("content-type", self.contentType) if request.method == "HEAD": request.setHeader("content-length", len(data)) return '' return data d.addCallback(handle) def ok(data): request.write(data) try: request.finish() except RuntimeError: # this occurs when the client has already disconnected; ignore # it (see #2027) log.msg("http client disconnected before results were sent") def fail(f): request.processingFailed(f) return None # processingFailed will log this for us d.addCallbacks(ok, fail) return server.NOT_DONE_YET
HtmlResource.__init__(self) self.bodyHTML = body self.pageTitle = pageTitle cxt['content'] = self.bodyHTML cxt['pageTitle'] = self.pageTitle template = request.site.buildbot_service.templates.get_template("empty.html") return template.render(**cxt)
"""This variant of the static.DirectoryLister uses a template for rendering."""
cxt = self.getContext(request)
if self.dirs is None: directory = os.listdir(self.path) directory.sort() else: directory = self.dirs
dirs, files = self._getFilesAndDirectories(directory)
cxt['path'] = cgi.escape(urllib.unquote(request.uri)) cxt['directories'] = dirs cxt['files'] = files template = request.site.buildbot_service.templates.get_template("directory.html") data = template.render(**cxt) if isinstance(data, unicode): data = data.encode("utf-8") return data
"""This class adds support for templated directory views."""
return DirectoryLister(self.path, self.listNames(), self.contentTypes, self.contentEncodings, self.defaultType)
else:
''' Collect the data needed for each line display ''' builder_name = build.getBuilder().getName() results = build.getResults() text = build.getText() all_got_revision = build.getAllGotRevisions() css_class = css_classes.get(results, "") ss_list = build.getSourceStamps() if ss_list: repo = ss_list[0].repository if all_got_revision: if len(ss_list) == 1: rev = all_got_revision.get(ss_list[0].codebase, "??") else: rev = "multiple rev." else: rev = "??" else: repo = 'unknown, no information in build' rev = 'unknown'
if type(text) == list: text = " ".join(text)
values = {'class': css_class, 'builder_name': builder_name, 'buildnum': build.getNumber(), 'results': css_class, 'text': " ".join(build.getText()), 'buildurl': path_to_build(req, build), 'builderurl': path_to_builder(req, build.getBuilder()), 'rev': rev, 'rev_repo' : repo, 'time': time.strftime(self.LINE_TIME_FORMAT, time.localtime(build.getTimes()[0])), 'text': text, 'include_builder': include_builder } return values
# when the query args say "trunk", present that to things like # IBuilderStatus.generateFinishedBuilds as None, since that's the # convention in use. But also include 'trunk', because some VC systems # refer to it that way. In the long run we should clean this up better, # maybe with Branch objects or something. if "trunk" in branches: return branches + [None] return branches
# jinja utilities
repositories=None, projects=None, jinja_loaders=None): ''' Create a jinja environment changecommentlink is used to render HTML in the WebStatus and for mail changes
@type changecommentlink: C{None}, tuple (2 or 3 strings), dict (string -> 2- or 3-tuple) or callable @param changecommentlink: see changelinkfilter()
@type revlink: C{None}, format-string, dict (repository -> format string) or callable @param revlink: see revlinkfilter()
@type repositories: C{None} or dict (string -> url) @param repositories: an (optinal) mapping from repository identifiers (as given by Change sources) to URLs. Is used to create a link on every place where a repository is listed in the WebStatus.
@type projects: C{None} or dict (string -> url) @param projects: similar to repositories, but for projects. '''
# See http://buildbot.net/trac/ticket/658
all_loaders.extend(jinja_loaders)
extensions=['jinja2.ext.i18n'], trim_blocks=True, undefined=AlmostStrictUndefined)
urlencode = urllib.quote, email = emailfilter, user = userfilter, shortrev = shortrevfilter(revlink, env), revlink = revlinkfilter(revlink, env), changecomment = changelinkfilter(changecommentlink), repolink = dictlinkfilter(repositories), projectlink = dictlinkfilter(projects) ))
''' Escape & obfuscate e-mail addresses
replacing @ with <span style="display:none> reportedly works well against web-spiders and the next level is to use rot-13 (or something) and decode in javascript '''
''' Hide e-mail address from user name when viewing changes
We still include the (obfuscated) e-mail so that we can show it on mouse-over or similar etc ''' else:
'''Helper function that returns suitable macros and functions for building revision links depending on replacement mechanism '''
isinstance(replace, str) or isinstance(replace, unicode)
else: else: return None
else:
assert False, '_replace has a bad type, but we should never get here'
'''return macros for use with revision links, depending on whether revlinks are configured or not'''
else:
''' Returns a function which shortens the revisison string to 12-chars (chosen as this is the Mercurial short-id length) and add link if replacement string is set.
(The full id is still visible in HTML, for mouse-over events etc.)
@param replace: see revlinkfilter() @param templates: a jinja2 environment '''
else: else: else: return shortrev + '...'
''' Returns a function which adds an url link to a revision identifiers.
Takes same params as shortrevfilter()
@param replace: either a python format string with an %s, or a dict mapping repositories to format strings, or a callable taking (revision, repository) arguments and return an URL (or None, if no URL is available), or None, in which case revisions do not get decorated with links
@param templates: a jinja2 environment '''
else:
''' Returns function that does regex search/replace in comments to add links to bug ids and similar.
@param changelink: Either C{None} or: a tuple (2 or 3 elements) 1. a regex to match what we look for 2. an url with regex refs (\g<0>, \1, \2, etc) that becomes the 'href' attribute 3. (optional) an title string with regex ref regex or: a dict mapping projects to above tuples (no links will be added if the project isn't found) or: a callable taking (changehtml, project) args (where the changetext is HTML escaped in the form of a jinja2.Markup instance) and returning another jinja2.Markup instance with the same change text plus any HTML tags added to it. '''
isinstance(changelink, tuple) or callable(changelink)
else:
# expand things *after* application of the regular expressions else:
# now, we need to split the string into matched and unmatched portions, # quoting the unmatched portions directly and quoting the components of # the 'a' element for the matched portions. We can't use re.split here, # because the user-supplied patterns may have multiple groups.
# TODO: Optimize and cache return value from replace_from_tuple so # we only compile regex once per project, not per view
else:
assert False, 'changelink has unsupported type, but that is checked before'
'''A filter that encloses the given value in a link tag given that the value exists in the dictionary'''
else:
else:
''' An undefined that allows boolean testing but fails properly on every other use.
Much better than the default Undefined, but not fully as strict as StrictUndefined ''' return False
"""Get the charset for an x-www-form-urlencoded request""" # per http://stackoverflow.com/questions/708915/detecting-the-character-encoding-of-an-http-post-request |