def test_default(self): env = wb.createJinjaEnv() f = env.filters['changecomment'] self.assertNotSubstring('<a', f(None, '')) self.assertNotSubstring('<a', f(None, 'There is no ticket #123')) self.assertNotSubstring('<a', f('project', '')) self.assertNotSubstring('<a', f('project', 'There is no ticket #123'))
def test_jinjaenv(self): env = wb.createJinjaEnv(repositories={'a': 'http://a.net'}, projects={'b': 'http://b.net'}) self.assertSubstring('<a href="http://a.net">', env.filters['repolink']('a')) self.assertSubstring('<a href="http://b.net">', env.filters['projectlink']('b'))
def test_dict(self): env = wb.createJinjaEnv({ None: 'http://default.net/repo/%s', 'repo': 'http://myserver.net/repo/%s', 'repo2': 'http://myserver2.net/repo/%s', 'sub/repo': 'http://otherserver.com/%s' }) self._test(env)
def setServiceParent(self, parent): # this class keeps a *separate* link to the buildmaster, rather than # just using self.parent, so that when we are "disowned" (and thus # parent=None), any remaining HTTP clients of this WebStatus will still # be able to get reasonable results. self.master = parent.master # set master in IAuth instance if self.authz.auth: self.authz.auth.master = self.master def either(a,b): # a if a else b for py2.4 if a: return a else: return b rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) # Set up the jinja templating engine. if self.revlink: revlink = self.revlink else: revlink = self.master.config.revlink # Set up the jinja templating engine. if self.shortrev: shortrev = self.shortrev else: shortrev = self.master.config.shortrev self.templates = createJinjaEnv(revlink, self.changecommentlink, self.repositories, self.projects, self.jinja_loaders, shortrev) if not self.site: class RotateLogSite(server.Site): def _openLogFile(self, path): try: from twisted.python.logfile import LogFile log.msg("Setting up http.log rotating %s files of %s bytes each" % (maxRotatedFiles, rotateLength)) if hasattr(LogFile, "fromFullPath"): # not present in Twisted-2.5.0 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) else: log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") except ImportError, e: log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) # if all else fails, just call the parent method return server.Site._openLogFile(self, path) # this will be replaced once we've been attached to a parent (and # thus have a basedir and can reference BASEDIR) root = static.Data("placeholder", "text/plain") httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) self.site = RotateLogSite(root, logPath=httplog)
def test_template(self): template_str = '''{{ rev|revlink('repo') }} - {{ rev|shortrev('repo') }}''' env = wb.createJinjaEnv(revlink='http://myserver.net/repo/%s') template = env.from_string(template_str) rev = '1234567890' * 4 # reasonably long html = template.render(rev=rev) self.assertSubstring('http://myserver.net/repo/%s' % rev, html) self.assertSubstring('...', html) # did it get shortened? self.assertEquals(html.count('<a'), 3) # one in revlink, two in shortrev
def test_dict_3tuple(self): env = wb.createJinjaEnv( changecommentlink={ None: (r'#(\d+)', r'http://server/trac/ticket/\1', r'Ticket #\1'), 'project1': (r'#(\d+)', r'http://server/trac/p1/ticket/\1', r'Ticket #\1'), 'project2': (r'#(\d+)', r'http://server/bugzilla/p2/ticket/\1', r'Bug #\1') }) self._test(env) f = env.filters['changecomment'] self.assertNotSubstring('<a', f('fixed #123', 'nonexistingproject'))
def test_callable(self): def my_revlink(rev, repo): import urllib if not rev: return None if not repo: repo = 'main' rev = urllib.quote(rev) repo = urllib.quote(repo) return 'http://myserver.net/repos/%s/rev/%s' % (repo, rev) env = wb.createJinjaEnv(my_revlink) self._test(env)
def setServiceParent(self, parent): # this class keeps a *separate* link to the buildmaster, rather than # just using self.parent, so that when we are "disowned" (and thus # parent=None), any remaining HTTP clients of this WebStatus will still # be able to get reasonable results. self.master = parent.master # set master in IAuth instance if self.authz.auth: self.authz.auth.master = self.master def either(a,b): # a if a else b for py2.4 if a: return a else: return b rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) # Set up the jinja templating engine. if self.revlink: revlink = self.revlink else: revlink = self.master.config.revlink self.templates = createJinjaEnv(revlink, self.changecommentlink, self.repositories, self.projects, self.jinja_loaders) if not self.site: class RotateLogSite(server.Site): def _openLogFile(self, path): try: from twisted.python.logfile import LogFile log.msg("Setting up http.log rotating %s files of %s bytes each" % (maxRotatedFiles, rotateLength)) if hasattr(LogFile, "fromFullPath"): # not present in Twisted-2.5.0 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) else: log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") except ImportError, e: log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) # if all else fails, just call the parent method return server.Site._openLogFile(self, path) # this will be replaced once we've been attached to a parent (and # thus have a basedir and can reference BASEDIR) root = static.Data("placeholder", "text/plain") httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) self.site = RotateLogSite(root, logPath=httplog)
def test_callable(self): r1 = re.compile(r'#(\d+)') r2 = re.compile(r'bug ([a-eA-E0-9]+)') r1_sub = jinja2.Markup(r'<a href="\1" title="Ticket #\1">\g<0></a>') r2_sub = jinja2.Markup(r'<a href="\1" title="Bug \1"><img src="\bug.png">\g<0></a>') def my_changelink(changehtml, project): if project == 'nonexistingproject': return changehtml html1 = r1.sub(r1_sub, changehtml) html2 = r2.sub(r2_sub, html1) return html2 env = wb.createJinjaEnv(changecommentlink=my_changelink) self._test(env) f = env.filters['changecomment'] self.assertNotSubstring('<a', f('fixed #123', 'nonexistingproject'))
def __init__(self, http_port=None, distrib_port=None, allowForce=None, public_html="public_html", site=None, numbuilds=20, num_events=200, num_events_max=None, auth=None, order_console_by_time=False, changecommentlink=None, revlink=None, projects=None, repositories=None, authz=None, logRotateLength=None, maxRotatedFiles=None, change_hook_dialects = {}): """Run a web server that provides Buildbot status. @type http_port: int or L{twisted.application.strports} string @param http_port: a strports specification describing which port the buildbot should use for its web server, with the Waterfall display as the root page. For backwards compatibility this can also be an int. Use 'tcp:8000' to listen on that port, or 'tcp:12345:interface=127.0.0.1' if you only want local processes to connect to it (perhaps because you are using an HTTP reverse proxy to make the buildbot available to the outside world, and do not want to make the raw port visible). @type distrib_port: int or L{twisted.application.strports} string @param distrib_port: Use this if you want to publish the Waterfall page using web.distrib instead. The most common case is to provide a string that is an absolute pathname to the unix socket on which the publisher should listen (C{os.path.expanduser(~/.twistd-web-pb)} will match the default settings of a standard twisted.web 'personal web server'). Another possibility is to pass an integer, which means the publisher should listen on a TCP socket, allowing the web server to be on a different machine entirely. Both forms are provided for backwards compatibility; the preferred form is a strports specification like 'unix:/home/buildbot/.twistd-web-pb'. Providing a non-absolute pathname will probably confuse the strports parser. @param allowForce: deprecated; use authz instead @param auth: deprecated; use with authz @param authz: a buildbot.status.web.authz.Authz instance giving the authorization parameters for this view @param public_html: the path to the public_html directory for this display, either absolute or relative to the basedir. The default is 'public_html', which selects BASEDIR/public_html. @type site: None or L{twisted.web.server.Site} @param site: Use this if you want to define your own object instead of using the default.` @type numbuilds: int @param numbuilds: Default number of entries in lists at the /one_line_per_build and /builders/FOO URLs. This default can be overriden both programatically --- by passing the equally named argument to constructors of OneLinePerBuildOneBuilder and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. @type num_events: int @param num_events: Default number of events to show in the waterfall. @type num_events_max: int @param num_events_max: The maximum number of events that are allowed to be shown in the waterfall. The default value of C{None} will disable this check @type auth: a L{status.web.auth.IAuth} or C{None} @param auth: an object that performs authentication to restrict access to the C{allowForce} features. Ignored if C{allowForce} is not C{True}. If C{auth} is C{None}, people can force or stop builds without auth. @type order_console_by_time: bool @param order_console_by_time: Whether to order changes (commits) in the console view according to the time they were created (for VCS like Git) or according to their integer revision numbers (for VCS like SVN). @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None} @param changecommentlink: adds links to ticket/bug ids in change comments, see buildbot.status.web.base.changecommentlink for details @type revlink: callable, dict, string or C{None} @param revlink: decorations revision ids with links to a web-view, see buildbot.status.web.base.revlink for details @type projects: callable, dict or c{None} @param projects: maps project identifiers to URLs, so that any project listed is automatically decorated with a link to it's front page. see buildbot.status.web.base.dictlink for details @type repositories: callable, dict or c{None} @param repositories: maps repository identifiers to URLs, so that any project listed is automatically decorated with a link to it's web view. see buildbot.status.web.base.dictlink for details @type logRotateLength: None or int @param logRotateLength: file size at which the http.log is rotated/reset. If not set, the value set in the buildbot.tac will be used, falling back to the BuildMaster's default value (1 Mb). @type maxRotatedFiles: None or int @param maxRotatedFiles: number of old http.log files to keep during log rotation. If not set, the value set in the buildbot.tac will be used, falling back to the BuildMaster's default value (10 files). @type change_hook_dialects: None or dict @param change_hook_dialects: If empty, disables change_hook support, otherwise whitelists valid dialects. In the format of {"dialect1": "Option1", "dialect2", None} Where the values are options that will be passed to the dialect To enable the DEFAULT handler, use a key of DEFAULT """ service.MultiService.__init__(self) if type(http_port) is int: http_port = "tcp:%d" % http_port self.http_port = http_port if distrib_port is not None: if type(distrib_port) is int: distrib_port = "tcp:%d" % distrib_port if distrib_port[0] in "/~.": # pathnames distrib_port = "unix:%s" % distrib_port self.distrib_port = distrib_port self.num_events = num_events if num_events_max: assert num_events_max >= num_events self.num_events_max = num_events_max self.public_html = public_html # make up an authz if allowForce was given if authz: if allowForce is not None: raise ValueError("cannot use both allowForce and authz parameters") if auth: raise ValueError("cannot use both auth and authz parameters (pass " "auth as an Authz parameter)") else: # invent an authz if allowForce and auth: authz = Authz(auth=auth, default_action="auth") elif allowForce: authz = Authz(default_action=True) else: if auth: log.msg("Warning: Ignoring authentication. Search for 'authorization'" " in the manual") authz = Authz() # no authorization for anything self.authz = authz self.orderConsoleByTime = order_console_by_time # If we were given a site object, go ahead and use it. (if not, we add one later) self.site = site # store the log settings until we create the site object self.logRotateLength = logRotateLength self.maxRotatedFiles = maxRotatedFiles # create the web site page structure self.childrenToBeAdded = {} self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, num_events_max=num_events_max) # Set up the jinja templating engine. self.templates = createJinjaEnv(revlink, changecommentlink, repositories, projects) # keep track of cached connections so we can break them when we shut # down. See ticket #102 for more details. self.channels = weakref.WeakKeyDictionary() # do we want to allow change_hook self.change_hook_dialects = {} if change_hook_dialects: self.change_hook_dialects = change_hook_dialects self.putChild("change_hook", ChangeHookResource(dialects = self.change_hook_dialects))
def __init__(self, http_port=None, distrib_port=None, allowForce=False, public_html="public_html", site=None, numbuilds=20, num_events=200, num_events_max=None, auth=None, order_console_by_time=False, changecommentlink=None, revlink=None): """Run a web server that provides Buildbot status. @type http_port: int or L{twisted.application.strports} string @param http_port: a strports specification describing which port the buildbot should use for its web server, with the Waterfall display as the root page. For backwards compatibility this can also be an int. Use 'tcp:8000' to listen on that port, or 'tcp:12345:interface=127.0.0.1' if you only want local processes to connect to it (perhaps because you are using an HTTP reverse proxy to make the buildbot available to the outside world, and do not want to make the raw port visible). @type distrib_port: int or L{twisted.application.strports} string @param distrib_port: Use this if you want to publish the Waterfall page using web.distrib instead. The most common case is to provide a string that is an absolute pathname to the unix socket on which the publisher should listen (C{os.path.expanduser(~/.twistd-web-pb)} will match the default settings of a standard twisted.web 'personal web server'). Another possibility is to pass an integer, which means the publisher should listen on a TCP socket, allowing the web server to be on a different machine entirely. Both forms are provided for backwards compatibility; the preferred form is a strports specification like 'unix:/home/buildbot/.twistd-web-pb'. Providing a non-absolute pathname will probably confuse the strports parser. @param allowForce: boolean, if True then the webserver will allow visitors to trigger and cancel builds @param public_html: the path to the public_html directory for this display, either absolute or relative to the basedir. The default is 'public_html', which selects BASEDIR/public_html. @type site: None or L{twisted.web.server.Site} @param site: Use this if you want to define your own object instead of using the default.` @type numbuilds: int @param numbuilds: Default number of entries in lists at the /one_line_per_build and /builders/FOO URLs. This default can be overriden both programatically --- by passing the equally named argument to constructors of OneLinePerBuildOneBuilder and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. @type num_events: int @param num_events: Default number of events to show in the waterfall. @type num_events_max: int @param num_events_max: The maximum number of events that are allowed to be shown in the waterfall. The default value of C{None} will disable this check @type auth: a L{status.web.auth.IAuth} or C{None} @param auth: an object that performs authentication to restrict access to the C{allowForce} features. Ignored if C{allowForce} is not C{True}. If C{auth} is C{None}, people can force or stop builds without auth. @type order_console_by_time: bool @param order_console_by_time: Whether to order changes (commits) in the console view according to the time they were created (for VCS like Git) or according to their integer revision numbers (for VCS like SVN). @type changecommentlink: tuple (3 strings) or C{None} @param changecommentlink: a regular expression and replacement string that is applied to all change comments. The first element represents what to search for, the second should yield an url (for use in the href attribute of a link) and the third gives the title attribute I.e. for Trac: (r'#(\d+)', r'http://buildbot.net/trac/ticket/\1', r'Ticket \g<0>') @type revlink: string or C{None} @param revlink: a replacement string that is applied to all revisions and will, if set, create a link to the result of the replacement. Use %s to insert the revision id in the url. I.e. for Buildbot on github: ('http://github.com/djmitche/buildbot/tree/%s') (The revision id will be URL encoded before inserted in the replacement string) """ service.MultiService.__init__(self) if type(http_port) is int: http_port = "tcp:%d" % http_port self.http_port = http_port if distrib_port is not None: if type(distrib_port) is int: distrib_port = "tcp:%d" % distrib_port if distrib_port[0] in "/~.": # pathnames distrib_port = "unix:%s" % distrib_port self.distrib_port = distrib_port self.allowForce = allowForce self.num_events = num_events if num_events_max: assert num_events_max >= num_events self.num_events_max = num_events_max self.public_html = public_html if self.allowForce and auth: assert IAuth.providedBy(auth) self.auth = auth else: if auth: log.msg("Warning: Ignoring authentication. allowForce must be" " set to True use this") self.auth = None self.orderConsoleByTime = order_console_by_time # If we were given a site object, go ahead and use it. if site: self.site = site else: # this will be replaced once we've been attached to a parent (and # thus have a basedir and can reference BASEDIR) root = static.Data("placeholder", "text/plain") self.site = server.Site(root) self.childrenToBeAdded = {} self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, num_events_max=num_events_max) # the following items are accessed by HtmlResource when it renders # each page. self.site.buildbot_service = self # Set up the jinja templating engine. self.templates = createJinjaEnv(revlink, changecommentlink) # keep track of cached connections so we can break them when we shut # down. See ticket #102 for more details. self.channels = weakref.WeakKeyDictionary() if self.http_port is not None: s = strports.service(self.http_port, self.site) s.setServiceParent(self) if self.distrib_port is not None: f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) s = strports.service(self.distrib_port, f) s.setServiceParent(self)
def __init__(self, http_port=None, distrib_port=None, allowForce=None, public_html="public_html", site=None, numbuilds=20, num_events=200, num_events_max=None, auth=None, order_console_by_time=False, changecommentlink=None, revlink=None, projects=None, repositories=None, authz=None, logRotateLength=None, maxRotatedFiles=None, change_hook_dialects={}, provide_feeds=None): """Run a web server that provides Buildbot status. @type http_port: int or L{twisted.application.strports} string @param http_port: a strports specification describing which port the buildbot should use for its web server, with the Waterfall display as the root page. For backwards compatibility this can also be an int. Use 'tcp:8000' to listen on that port, or 'tcp:12345:interface=127.0.0.1' if you only want local processes to connect to it (perhaps because you are using an HTTP reverse proxy to make the buildbot available to the outside world, and do not want to make the raw port visible). @type distrib_port: int or L{twisted.application.strports} string @param distrib_port: Use this if you want to publish the Waterfall page using web.distrib instead. The most common case is to provide a string that is an absolute pathname to the unix socket on which the publisher should listen (C{os.path.expanduser(~/.twistd-web-pb)} will match the default settings of a standard twisted.web 'personal web server'). Another possibility is to pass an integer, which means the publisher should listen on a TCP socket, allowing the web server to be on a different machine entirely. Both forms are provided for backwards compatibility; the preferred form is a strports specification like 'unix:/home/buildbot/.twistd-web-pb'. Providing a non-absolute pathname will probably confuse the strports parser. @param allowForce: deprecated; use authz instead @param auth: deprecated; use with authz @param authz: a buildbot.status.web.authz.Authz instance giving the authorization parameters for this view @param public_html: the path to the public_html directory for this display, either absolute or relative to the basedir. The default is 'public_html', which selects BASEDIR/public_html. @type site: None or L{twisted.web.server.Site} @param site: Use this if you want to define your own object instead of using the default.` @type numbuilds: int @param numbuilds: Default number of entries in lists at the /one_line_per_build and /builders/FOO URLs. This default can be overriden both programatically --- by passing the equally named argument to constructors of OneLinePerBuildOneBuilder and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. @type num_events: int @param num_events: Default number of events to show in the waterfall. @type num_events_max: int @param num_events_max: The maximum number of events that are allowed to be shown in the waterfall. The default value of C{None} will disable this check @type auth: a L{status.web.auth.IAuth} or C{None} @param auth: an object that performs authentication to restrict access to the C{allowForce} features. Ignored if C{allowForce} is not C{True}. If C{auth} is C{None}, people can force or stop builds without auth. @type order_console_by_time: bool @param order_console_by_time: Whether to order changes (commits) in the console view according to the time they were created (for VCS like Git) or according to their integer revision numbers (for VCS like SVN). @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None} @param changecommentlink: adds links to ticket/bug ids in change comments, see buildbot.status.web.base.changecommentlink for details @type revlink: callable, dict, string or C{None} @param revlink: decorations revision ids with links to a web-view, see buildbot.status.web.base.revlink for details @type projects: callable, dict or c{None} @param projects: maps project identifiers to URLs, so that any project listed is automatically decorated with a link to it's front page. see buildbot.status.web.base.dictlink for details @type repositories: callable, dict or c{None} @param repositories: maps repository identifiers to URLs, so that any project listed is automatically decorated with a link to it's web view. see buildbot.status.web.base.dictlink for details @type logRotateLength: None or int @param logRotateLength: file size at which the http.log is rotated/reset. If not set, the value set in the buildbot.tac will be used, falling back to the BuildMaster's default value (1 Mb). @type maxRotatedFiles: None or int @param maxRotatedFiles: number of old http.log files to keep during log rotation. If not set, the value set in the buildbot.tac will be used, falling back to the BuildMaster's default value (10 files). @type change_hook_dialects: None or dict @param change_hook_dialects: If empty, disables change_hook support, otherwise whitelists valid dialects. In the format of {"dialect1": "Option1", "dialect2", None} Where the values are options that will be passed to the dialect To enable the DEFAULT handler, use a key of DEFAULT @type provide_feeds: None or list @param provide_feeds: If empty, provides atom, json, and rss feeds. Otherwise, a dictionary of strings of the type of feeds provided. Current possibilities are "atom", "json", and "rss" """ service.MultiService.__init__(self) if type(http_port) is int: http_port = "tcp:%d" % http_port self.http_port = http_port if distrib_port is not None: if type(distrib_port) is int: distrib_port = "tcp:%d" % distrib_port if distrib_port[0] in "/~.": # pathnames distrib_port = "unix:%s" % distrib_port self.distrib_port = distrib_port self.num_events = num_events if num_events_max: assert num_events_max >= num_events self.num_events_max = num_events_max self.public_html = public_html # make up an authz if allowForce was given if authz: if allowForce is not None: raise ValueError( "cannot use both allowForce and authz parameters") if auth: raise ValueError( "cannot use both auth and authz parameters (pass " "auth as an Authz parameter)") else: # invent an authz if allowForce and auth: authz = Authz(auth=auth, default_action="auth") elif allowForce: authz = Authz(default_action=True) else: if auth: log.msg( "Warning: Ignoring authentication. Search for 'authorization'" " in the manual") authz = Authz() # no authorization for anything self.authz = authz self.orderConsoleByTime = order_console_by_time # If we were given a site object, go ahead and use it. (if not, we add one later) self.site = site # store the log settings until we create the site object self.logRotateLength = logRotateLength self.maxRotatedFiles = maxRotatedFiles # create the web site page structure self.childrenToBeAdded = {} self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, num_events_max=num_events_max) # Set up the jinja templating engine. self.templates = createJinjaEnv(revlink, changecommentlink, repositories, projects) # keep track of cached connections so we can break them when we shut # down. See ticket #102 for more details. self.channels = weakref.WeakKeyDictionary() # do we want to allow change_hook self.change_hook_dialects = {} if change_hook_dialects: self.change_hook_dialects = change_hook_dialects self.putChild( "change_hook", ChangeHookResource(dialects=self.change_hook_dialects)) # Set default feeds if provide_feeds is None: self.provide_feeds = ["atom", "json", "rss"] else: self.provide_feeds = provide_feeds
def test_default(self): env = wb.createJinjaEnv() self._test(env, False)
def test_dict(self): env = wb.createJinjaEnv({None: 'http://default.net/repo/%s', 'repo': 'http://myserver.net/repo/%s', 'repo2': 'http://myserver2.net/repo/%s', 'sub/repo': 'http://otherserver.com/%s'}) self._test(env)
def test_format(self): env = wb.createJinjaEnv('http://myserver.net/repo/%s') self._test(env)
def test_tuple3(self): env = wb.createJinjaEnv( changecommentlink=(r'#(\d+)', r'http://buildbot.net/trac/ticket/\1', r'Ticket #\1')) self._test(env)