From 911ac638b206e131281d8b37a66106b92b1e1ac6 Mon Sep 17 00:00:00 2001 From: Kamil Páral Date: Dec 04 2009 12:53:55 +0000 Subject: Merge branch 'master' into kparal/keep-control-files --- diff --git a/Makefile b/Makefile index 15ffc90..8e31daa 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ clean: find . -name "*.py[co]" -exec rm -f {} + rm -f autoqa-$(VERSION).tar.gz rm -rf lib/python/build + rm -rf front-ends/israwhidebroken/build install: build install -d $(PREFIX)/usr/bin @@ -28,11 +29,18 @@ install: build install -d $(PREFIX)$(AUTOTEST_DIR)/client/bin install -m 0644 lib/autotest/site_utils.py $(PREFIX)$(AUTOTEST_DIR)/client/bin ( cd lib/python; $(PYTHON) setup.py install -O1 --skip-build --root $(PREFIX)/ ) + ## front-ends/israwhidebroken + install -d $(PREFIX)/usr/sbin + install -d $(PREFIX)/usr/share/autoqa-israwhidebroken + install -d $(PREFIX)/etc/httpd/conf.d + install -d $(PREFIX)/var/lib/israwhidebroken + ( cd front-ends/israwhidebroken; $(PYTHON) setup.py install -O1 --skip-build --root $(PREFIX)/ ) build: lib/python/build lib/python/build: ( cd lib/python; $(PYTHON) setup.py build ) + ( cd front-ends/israwhidebroken; $(PYTHON) setup.py build ) tarball: autoqa-$(VERSION).tar.gz diff --git a/autoqa b/autoqa index 10f53f9..5bf8cab 100755 --- a/autoqa +++ b/autoqa @@ -60,6 +60,7 @@ def prep_controlfile(controlfile, extradata): ''' controldata = open(controlfile) (fd, name) = tempfile.mkstemp(prefix='autoqa-control.') + os.write(fd, '# -*- coding: utf-8 -*-\n\n') try: cfgdata = StringIO.StringIO() cfg_parser.write(cfgdata) @@ -123,7 +124,7 @@ parser = optparse.OptionParser(usage="%prog HOOKNAME [options] ...", parser.add_option('-h', '--help', action='help', help='show this help message (or hook help message if HOOKNAME given) and \ exit') -parser.add_option('-a', '--arch', action='append', +parser.add_option('-a', '--arch', action='append', default=[], help='arch to run the test(s) on. can be used multiple times') # XXX TODO '-d', '--distro', help='distro label to schedule this test against' parser.add_option('-t', '--test', action='append', diff --git a/autoqa.spec b/autoqa.spec index 63d092c..fe04944 100644 --- a/autoqa.spec +++ b/autoqa.spec @@ -7,7 +7,7 @@ Name: autoqa Summary: Automated quality assurance framework Version: 0.3 -Release: 1%{?dist} +Release: 2%{?dist} Source0: http://fedorahosted.org/projects/autoqa/releases/%{name}-%{version}.tar.gz License: GPLv2+ Group: Applications/Internet @@ -22,6 +22,29 @@ tests, the tests, and a series of watcher scripts designed to execute tests under certain conditions. +%package israwhidebroken +Summary: A TurboGears front-end for 'is rawhide broken' +Group: Applications/Internet +Requires: TurboGears +Requires: python-toscawidgets +Requires: python-tw-forms >= 0.9.8 +Requires: python-cherrypy +Requires: python-fedora +Requires: mod_wsgi +BuildRequires: TurboGears +BuildRequires: python-devel +%if 0%{?fedora} >= 8 +BuildRequires: python-setuptools-devel +%else +BuildRequires: python-setuptools +%endif + + +%description israwhidebroken +'Is rawhide broken' is a TurboGears front-end intended for AutoQA automated +rawhide test results. + + %prep %setup -q @@ -34,6 +57,11 @@ make build PYTHON=%{__python} rm -rf $RPM_BUILD_ROOT make install PREFIX=$RPM_BUILD_ROOT TEST_DIR=%{testdir} HOOK_DIR=%{hookdir} PYTHON=%{__python} install -m 644 autoqa.conf repoinfo.conf $RPM_BUILD_ROOT%{_sysconfdir} +# front-ends/israwhidebroken +mv %{buildroot}%{_bindir}/start-israwhidebroken %{buildroot}%{_sbindir}/ +mv %{buildroot}%{_bindir}/israwhidebroken.wsgi %{buildroot}%{_sbindir}/ +mv %{buildroot}%{_bindir}/populate-israwhidebroken-db %{buildroot}%{_sbindir}/ +mv %{buildroot}%{python_sitelib}/israwhidebroken* %{buildroot}%{_datadir}/%{name}-israwhidebroken/ %clean @@ -43,7 +71,7 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %doc README LICENSE TODO -%dir %attr(0775,root,autotest) /var/cache/autoqa +%dir %attr(0775,root,autotest) %{_localstatedir}/cache/autoqa %{_bindir}/autoqa %{_sysconfdir}/cron.d/autoqa %config(noreplace) %{_sysconfdir}/autoqa.conf @@ -51,10 +79,26 @@ rm -rf $RPM_BUILD_ROOT %{testdir} %{hookdir} %{_datadir}/autotest/client/bin/site_utils.py* -%{python_sitelib}/* +%{python_sitelib}/autoqa* + + +%files israwhidebroken +%defattr(-,root,root,-) +%doc front-ends/israwhidebroken/README.txt +%dir %{_localstatedir}/lib/israwhidebroken +%config(noreplace) %{_sysconfdir}/israwhidebroken.cfg +%config(noreplace) %{_sysconfdir}/httpd/conf.d/israwhidebroken.conf +%{_sbindir}/start-israwhidebroken +%{_sbindir}/israwhidebroken.wsgi +%{_sbindir}/populate-israwhidebroken-db +%{_datadir}/%{name}-israwhidebroken %changelog +* Wed Dec 2 2009 James Laska - 0.3-2 +- Add autoqa-israwhidebroken TurboGears application +- Remove .py extension from israwhidebroken _sbindir scripts + * Fri Nov 6 2009 Will Woods - 0.3-1 - Rearrange a bunch of test/watcher code into autoqa python lib - Add post-koji-build hook and watcher diff --git a/front-ends/israwhidebroken/.gitignore b/front-ends/israwhidebroken/.gitignore new file mode 100644 index 0000000..814fe35 --- /dev/null +++ b/front-ends/israwhidebroken/.gitignore @@ -0,0 +1,10 @@ +*.gz +*.pyc +*.pyo +*.swp +build +dist +rpm-build +MANIFEST +devdata.sqlite +israwhidebroken.egg-info diff --git a/front-ends/israwhidebroken/MANIFEST.in b/front-ends/israwhidebroken/MANIFEST.in new file mode 100644 index 0000000..7ecc9c0 --- /dev/null +++ b/front-ends/israwhidebroken/MANIFEST.in @@ -0,0 +1,5 @@ +include israwhidebroken.conf +include populate-israwhidebroken-db +include start-israwhidebroken +include israwhidebroken.cfg +include README.txt diff --git a/front-ends/israwhidebroken/README.txt b/front-ends/israwhidebroken/README.txt new file mode 100644 index 0000000..68d6f9f --- /dev/null +++ b/front-ends/israwhidebroken/README.txt @@ -0,0 +1,20 @@ +israwhidebroken + +This is a TurboGears (http://www.turbogears.org) project. It can be +started by running the start-israwhidebroken script, or by using the +provided httpd wsgi configuration. + +First, you must create a database. The example below will create a sqlite +database as specified by the default configuration in +/etc/israwhidebroken.cfg. + + $ cd /usr/share/autoqa-israwhidebroken + $ tg-admin -c /etc/israwhidebroken.cfg sql create + +Next, populate the database with initial data, run the populate-db.py script: + + $ /usr/sbin/populate-israwhidebroken-db /etc/israwhidebroken.cfg + +You should now have a database with the test list (required) and a set of 3 +Trees (which can be deleted once you have added newer data). + diff --git a/front-ends/israwhidebroken/dev.cfg b/front-ends/israwhidebroken/dev.cfg new file mode 100644 index 0000000..f7d0caf --- /dev/null +++ b/front-ends/israwhidebroken/dev.cfg @@ -0,0 +1,73 @@ +[global] +# This is where all of your settings go for your development environment +# Settings that are the same for both development and production +# (such as template engine, encodings, etc.) all go in +# israwhidebroken/config/app.cfg + +# DATABASE + +# pick the form for your database +# sqlobject.dburi="postgres://username@hostname/databasename" +# sqlobject.dburi="mysql://username:password@hostname:port/databasename" +# sqlobject.dburi="sqlite:///file_name_and_path" + +# If you have sqlite, here's a simple default to get you started +# in development +sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite" + + +# if you are using a database or table type without transactions +# (MySQL default, for example), you should turn off transactions +# by prepending notrans_ on the uri +# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename" + +# for Windows users, sqlite URIs look like: +# sqlobject.dburi="sqlite:///drive_letter:/path/to/file" + +# SERVER + +# Some server parameters that you may want to tweak +# server.socket_port=8080 + +# Enable the debug output at the end on pages. +# log_debug_info_filter.on = False + +server.environment="development" +autoreload.package="israwhidebroken" + +# Auto-Reload after code modification +# autoreload.on = True + +# Set to True if you'd like to abort execution if a controller gets an +# unexpected parameter. False by default +tg.strict_parameters = True + +# LOGGING +# Logging configuration generally follows the style of the standard +# Python logging module configuration. Note that when specifying +# log format messages, you need to use *() for formatting variables. +# Deployment independent log configuration is in israwhidebroken/config/log.cfg +[logging] + +[[loggers]] +[[[israwhidebroken]]] +level='DEBUG' +qualname='israwhidebroken' +handlers=['debug_out'] + +[[[allinfo]]] +level='INFO' +handlers=['debug_out'] + +[[[access]]] +level='INFO' +qualname='turbogears.access' +handlers=['access_out'] +propagate=0 + +[[[identity]]] +level='INFO' +qualname='turbogears.identity' +handlers=['access_out'] +propagate=0 + diff --git a/front-ends/israwhidebroken/israwhidebroken.cfg b/front-ends/israwhidebroken/israwhidebroken.cfg new file mode 100644 index 0000000..1c2f407 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken.cfg @@ -0,0 +1,85 @@ +[global] +# This is where all of your settings go for your production environment. +# You'll copy this file over to your production server and provide it +# as a command-line option to your start script. +# Settings that are the same for both development and production +# (such as template engine, encodings, etc.) all go in +# israwhidebroken/config/app.cfg + +# DATABASE + +# pick the form for your database +# sqlobject.dburi="postgres://username@hostname/databasename" +# sqlobject.dburi="mysql://username:password@hostname:port/databasename" +# sqlobject.dburi="sqlite:///file_name_and_path" + +# If you have sqlite, here's a simple default to get you started +# in development +# sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite" +sqlobject.dburi="sqlite:///var/lib/israwhidebroken/db.sqlite" + + +# if you are using a database or table type without transactions +# (MySQL default, for example), you should turn off transactions +# by prepending notrans_ on the uri +# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename" + +# for Windows users, sqlite URIs look like: +# sqlobject.dburi="sqlite:///drive_letter:/path/to/file" + + +# SERVER + +server.environment="production" + +# Sets the number of threads the server uses +# server.thread_pool = 1 + +# if this is part of a larger site, you can set the path +# to the TurboGears instance here +# server.webpath="" + +# Set to True if you are deploying your App behind a proxy +# e.g. Apache using mod_proxy +# base_url_filter.on = False + +# Set to True if your proxy adds the x_forwarded_host header +# base_url_filter.use_x_forwarded_host = True + +# If your proxy does not add the x_forwarded_host header, set +# the following to the *public* host url. +# (Note: This will be overridden by the use_x_forwarded_host option +# if it is set to True and the proxy adds the header correctly. +# base_url_filter.base_url = "http://www.example.com" + +# Set to True if you'd like to abort execution if a controller gets an +# unexpected parameter. False by default +# tg.strict_parameters = False + +# LOGGING +# Logging configuration generally follows the style of the standard +# Python logging module configuration. Note that when specifying +# log format messages, you need to use *() for formatting variables. +# Deployment independent log configuration is in israwhidebroken/config/log.cfg +[logging] + +[[handlers]] + +[[[access_out]]] +# set the filename as the first argument below +args="(sys.stdout,)" +class='StreamHandler' +level='INFO' +formatter='message_only' + +[[loggers]] +[[[israwhidebroken]]] +level='ERROR' +qualname='israwhidebroken' +handlers=['error_out'] + +[[[access]]] +level='INFO' +qualname='turbogears.access' +handlers=['access_out'] +propagate=0 diff --git a/front-ends/israwhidebroken/israwhidebroken.conf b/front-ends/israwhidebroken/israwhidebroken.conf new file mode 100644 index 0000000..220035d --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken.conf @@ -0,0 +1,15 @@ +Alias /israwhidebroken/static /usr/share/autoqa-israwhidebroken/israwhidebroken/static + +WSGIDaemonProcess israwhidebroken user=apache group=apache maximum-requests=1000 display-name=israwhidebroken processes=4 threads=1 +WSGISocketPrefix run/wsgi +WSGIRestrictStdout On +WSGIRestrictSignal Off +WSGIPythonOptimize 1 + +WSGIScriptAlias /israwhidebroken /usr/sbin/israwhidebroken.wsgi + + + WSGIProcessGroup %{GLOBAL} + Order deny,allow + Allow from all + diff --git a/front-ends/israwhidebroken/israwhidebroken.wsgi b/front-ends/israwhidebroken/israwhidebroken.wsgi new file mode 100644 index 0000000..2401fff --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken.wsgi @@ -0,0 +1,36 @@ +#!/usr/bin/python -tt +__requires__ = 'autoqa' + +import sys +import os +sys.stdout = sys.stderr + +import pkg_resources +pkg_resources.require("CherryPy<3.0") + +os.environ['PYTHON_EGG_CACHE'] = '/var/www/.python-eggs' + +import atexit +import cherrypy +import cherrypy._cpwsgi +import turbogears +import fedora.tg.util + +if os.path.isdir("/usr/share/autoqa-israwhidebroken"): + sys.path.append("/usr/share/autoqa-israwhidebroken") + +turbogears.update_config(configfile=os.path.join('/etc', 'israwhidebroken.cfg'), + modulename="israwhidebroken.config") +# This must always be off when used with mod_wsgi +turbogears.config.update({'global': {'autoreload.on': False}}) + +turbogears.startup.call_on_startup.append(fedora.tg.util.enable_csrf) + +import israwhidebroken.controllers +cherrypy.root = israwhidebroken.controllers.Root() + +if cherrypy.server.state == 0: + atexit.register(cherrypy.server.stop) + cherrypy.server.start(init_only=True, server_class=None) + +application = cherrypy._cpwsgi.wsgiApp diff --git a/front-ends/israwhidebroken/israwhidebroken/__init__.py b/front-ends/israwhidebroken/israwhidebroken/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/__init__.py diff --git a/front-ends/israwhidebroken/israwhidebroken/client.py b/front-ends/israwhidebroken/israwhidebroken/client.py new file mode 100644 index 0000000..3c4c6af --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/client.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# uses the fedora.client library to talk to israwhidebroken +# +# Copyright 2009, Red Hat, Inc. +# +# This program 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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. +# +# Author: Will Woods + +from fedora.client import BaseClient, AppError, ServerError +import os, sys + +# A couple of useful helper methods +def todays_compose_id(serial=1): + import datetime + '''Return the default compose_id for today''' + return int(datetime.datetime.today().strftime('%Y%m%d') + '%02u' % serial) + +def treedata_from_url(url): + import urllib2, ConfigParser + import xml.etree.cElementTree as ElementTree + '''Given the URL of a rawhide tree, return a dict of tree data''' + treedata = dict() + # this one's a gimme + treedata['compose_id'] = todays_compose_id() + try: + # fetch treeinfo + treeinfo = ConfigParser.SafeConfigParser() + treeinfo.readfp(urllib2.urlopen(url+'/.treeinfo')) + treedata['arch'] = treeinfo.get('general','arch') + treedata['tree_time'] = int(float(treeinfo.get('general','timestamp'))) + except: + pass + try: + # fetch repomd.xml + repomd = urllib2.urlopen(url+'/repodata/repomd.xml') + repomdtree = ElementTree.parse(repomd) + ns = '{http://linux.duke.edu/metadata/repo}' + revision = repomdtree.getroot().find(ns+'revision') + treedata['repodata_time'] = int(revision.text) + except: + pass + return treedata + +# XXX TODO: if 'exc' in r: raise appropriate error? +class IRBClient(BaseClient): + def get_tests(self): + '''Returns a list of known tests''' + r = self.send_request('/get_tests') + return r.get('tests') + + def get_trees(self, *args, **kw): + '''Get a list of trees that match all the given parameters: + id, compose_id, arch, tree_time, repodata_time''' + r = self.send_request('/get_trees', req_params=kw) + return r.get('trees') + + def add_tree(self, *args, **kw): + '''Add a new tree to israwhidebroken. + Required arguments: arch, compose_id + Optional: tree_time, repodata_time + Returns the new tree.''' + for field in ('arch', 'compose_id', 'tree_time', 'repodata_time'): + if field not in kw: + raise ValueError, "Missing required arg %s" % field + if type(kw['arch']) is not str: + raise ValueError, 'arch must be str' + r = self.send_request('/add_tree', auth=True, req_params=kw) + return r.get('tree') + + def update_tree(self, *args, **kw): + '''Add a new tree, or update an existing tree. + Required arguments: id, or arch and compose_id + Optional: tree_time, repodata_time + Returns the tree data.''' + if 'id' in kw: + treelist = self.get_trees(id=kw['id']) + else: + treelist = self.get_trees(arch=kw['arch'], + compose_id=kw['compose_id']) + if len(treelist) == 0: + return self.add_tree(*args, **kw) + + kw['treeid'] = treelist[0]['id'] + r = self.send_request('/update_tree', auth=True, req_params=kw) + return r.get('tree') + + def add_result(self, treeid, testid, result, detail_url=None): + '''Add a test result to the database. Returns result id on success.''' + params = {'treeid':treeid, 'testid':testid, 'result': result} + if detail_url: + params['detail_url'] = detail_url + r = self.send_request('/add_result', auth=True, req_params=params) + return r.get('id') + + def report_result(self, testid, tree_data, result, add_tree=False): + '''Convenience method that will look up the tree using tree_data and + then add the given result for the given testid. + + tree_data should be a dict containing enough data to look up exactly + one tree. Typically that means either specifying the exact treeid if + it's already known (e.g. {'id':treeid}) or a dict with 'arch' and one or + more of (compose_id, tree_time, repodata_time) + + If add_tree is True and tree_data does not match any known tree, a new + tree will be added. + + Returns result id on success.''' + treelist = self.get_trees(**tree_data) + if not treelist: + if not add_tree: + print 'Could not find a matching tree' + return None + tree = self.add_tree(**tree_data) + # FIXME else check for error + elif len(treelist) > 1: + print 'Ambiguous tree data - too many matching trees' + return False + else: + tree = treelist.pop() + return self.add_result(treeid=tree['id'], testid=testid, result=result) diff --git a/front-ends/israwhidebroken/israwhidebroken/commands.py b/front-ends/israwhidebroken/israwhidebroken/commands.py new file mode 100644 index 0000000..fa4410a --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/commands.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +"""This module contains functions called from console script entry points.""" + +import sys +from os import getcwd +from os.path import dirname, exists, join + +import pkg_resources +pkg_resources.require("TurboGears>=1.0.8") +pkg_resources.require("SQLObject>=0.9.9") + +import cherrypy +import turbogears + +cherrypy.lowercase_api = True + + +class ConfigurationError(Exception): + pass + + +def start(): + """Start the CherryPy application server.""" + + setupdir = dirname(dirname(__file__)) + curdir = getcwd() + + # First look on the command line for a desired config file, + # if it's not on the command line, then look for 'setup.py' + # in the current directory. If there, load configuration + # from a file called 'dev.cfg'. If it's not there, the project + # is probably installed and we'll look first for a file called + # 'prod.cfg' in the current directory and then for a default + # config file called 'default.cfg' packaged in the egg. + if len(sys.argv) > 1: + configfile = sys.argv[1] + elif exists(join(setupdir, "setup.py")): + configfile = join(setupdir, "dev.cfg") + elif exists(join(curdir, "prod.cfg")): + configfile = join(curdir, "prod.cfg") + else: + try: + configfile = pkg_resources.resource_filename( + pkg_resources.Requirement.parse("israwhidebroken"), + "config/default.cfg") + except pkg_resources.DistributionNotFound: + raise ConfigurationError("Could not find default configuration.") + + turbogears.update_config(configfile=configfile, + modulename="israwhidebroken.config") + + from israwhidebroken.controllers import Root + + turbogears.start_server(Root()) diff --git a/front-ends/israwhidebroken/israwhidebroken/config/__init__.py b/front-ends/israwhidebroken/israwhidebroken/config/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/config/__init__.py diff --git a/front-ends/israwhidebroken/israwhidebroken/config/app.cfg b/front-ends/israwhidebroken/israwhidebroken/config/app.cfg new file mode 100644 index 0000000..06197bf --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/config/app.cfg @@ -0,0 +1,140 @@ +[global] +# The settings in this file should not vary depending on the deployment +# environment. dev.cfg and prod.cfg are the locations for +# the different deployment settings. Settings in this file will +# be overridden by settings in those other files. + +# The commented out values below are the defaults + +# APPLICATION PACKAGE + +package = "israwhidebroken" + +# VIEW + +# which view (template engine) to use if one is not specified in the +# template name +tg.defaultview = "genshi" + +# The sitetemplate is used for overall styling of a site that +# includes multiple TurboGears applications +# tg.sitetemplate="" + +# Allow every exposed function to be called as json, +# tg.allow_json = False + +# Suppress the inclusion of the shipped MochiKit version. Setting this to True +# and listing 'turbogears.mochikit' in 'tg.include_widgets' is a contradiction. +# This option will overrule the default-inclusion to prevent version mismatch. +# tg.mochikit_suppress = True +# By default, in order to be backward compatible, TurboGears uses a rather +# outdated MochiKit 1.3 version. But it now also includes the latest MochiKit +# 1.4 version which will be used if you specify this version number here. +# tg.mochikit_version = '1.4' + +# Widgetry! +toscawidgets.on = True + +# List of Widgets to include on every page. +# for example ['turbogears.mochikit'] +#tg.include_widgets = ['turbogears.mochikit'] + +# Set to True if the scheduler should be started +# tg.scheduler = False + +# Set to True to allow paginate decorator redirects when page number gets +# out of bound. Useful for getting the real page id in the url +# paginate.redirect_on_out_of_range = True + +# Set to True to allow paginate decorator redirects when last page is requested. +# This is useful for getting the real last page id in the url +# paginate.redirect_on_last_page = True + +# Set session or cookie +# session_filter.on = True + +# VISIT TRACKING +# -------------- +# Each visit to your application will be assigned a unique visit ID tracked via +# a cookie sent to the visitor's browser. + +# Enable Visit tracking +visit.on=True + +# Number of minutes a visit may be idle before it expires. +# visit.timeout=20 + +# Where to look for the key of an existing visit in the request and in which +# order. Comma-separated list of possible values: 'cookie', 'form'. +# By default only use visit key in session cookie. +# visit.source="cookie" + +# The name of the cookie to transmit to the visitor's browser. +# visit.cookie.name="tg-visit" + +# The name of the request parameter with the session key (for when +# 'visit.source' includes 'form'). Name MUST NOT contain dashes or dots! +# visit.form.name="tg_visit" + +# Domain name to specify when setting the cookie (must begin with . according to +# RFC 2109). The default (None) should work for most cases and will default to +# the machine to which the request was made. NOTE: localhost is NEVER a valid +# value and will NOT WORK. +# visit.cookie.domain=None + +# Specific path for the cookie +# visit.cookie.path="/" + +# The name of the VisitManager plugin to use for visitor tracking. +visit.manager="jsonfas2" + +# Allow the cookies to be transported over regular http +# XXX FIXME CHANGE THIS FOR DEPLOYMENT +visit.cookie.secure = False + +# IDENTITY +# -------- +# General configuration of the TurboGears Identity management module + +# Switch to turn on or off the Identity management module +identity.on=True + +# [REQUIRED] URL to which CherryPy will internally redirect when an access +# control check fails. If Identity management is turned on, a value for this +# option must be specified. +identity.failure_url="/login" + +# If force_external_redirect is set to True, then the identity +# framework will use an external redirection. +# This is mainly used to make sure that if you use +# an https:// url in the failure_url, this will be respected. +identity.force_external_redirect=False + +identity.provider='jsonfas2' + +# The names of the fields on the login form containing the visitor's user ID +# and password. In addition, the submit button is specified simply so its +# existence may be stripped out prior to passing the form data to the target +# controller. +# identity.form.user_name="user_name" +# identity.form.password="password" +# identity.form.submit="login" + +# What sources should the identity provider consider when determining the +# identity associated with a request? Comma separated list of identity sources. +# Valid sources: form, visit, http_auth +# identity.source="form,http_auth,visit" + +# compress the data sends to the web browser +# [/] +# gzip_filter.on = True +# gzip_filter.mime_types = ["application/json", "application/x-javascript", +# "text/javascript", "text/html", "text/css", "text/plain"] + +[/static] +static_filter.on = True +static_filter.dir = "%(top_level_dir)s/static" + +[/favicon.ico] +static_filter.on = True +static_filter.file = "%(top_level_dir)s/static/images/favicon.ico" diff --git a/front-ends/israwhidebroken/israwhidebroken/config/log.cfg b/front-ends/israwhidebroken/israwhidebroken/config/log.cfg new file mode 100644 index 0000000..ce776f8 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/config/log.cfg @@ -0,0 +1,29 @@ +# LOGGING +# Logging is often deployment specific, but some handlers and +# formatters can be defined here. + +[logging] +[[formatters]] +[[[message_only]]] +format='*(message)s' + +[[[full_content]]] +format='*(asctime)s *(name)s *(levelname)s *(message)s' + +[[handlers]] +[[[debug_out]]] +class='StreamHandler' +level='DEBUG' +args='(sys.stdout,)' +formatter='full_content' + +[[[access_out]]] +class='StreamHandler' +level='INFO' +args='(sys.stdout,)' +formatter='message_only' + +[[[error_out]]] +class='StreamHandler' +level='ERROR' +args='(sys.stdout,)' diff --git a/front-ends/israwhidebroken/israwhidebroken/controllers.py b/front-ends/israwhidebroken/israwhidebroken/controllers.py new file mode 100644 index 0000000..da59367 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/controllers.py @@ -0,0 +1,180 @@ +import turbogears as tg +import fedora +from turbogears import controllers, expose, flash +from israwhidebroken.model import Test, Tree, TestResult, hub +from turbogears import identity, redirect +from cherrypy import request, response +from tw.forms import Form, SingleSelectField, HiddenField +from tw.api import WidgetsList +# from israwhidebroken import json +# import logging +# log = logging.getLogger("israwhidebroken.controllers") + +# Simple select widget for submitting test results +# TODO: ajaxify +class ResultForm(Form): + show_errors = True + class fields(WidgetsList): + result = SingleSelectField(options=['none','pass','fail'], + suppress_label=True) + testid = HiddenField() + treeid = HiddenField() + +fdict = {'none': None, 'pass': 1, 'fail': 0} +def to_int(fstr): + '''Returns fstr converted to int, or None if fstr is None''' + if fstr in fdict: + return fdict[fstr] + return fstr and int(float(fstr)) + +archval = {'i386':'0', 'x86_64':'1', 'ppc':'2', 'ppc64':'3'} +def archsort(a,b): + return cmp(archval.get(a,a), archval.get(b,b)) + +class Root(controllers.RootController): + # Main index page + @expose(template="israwhidebroken.templates.index") + def index(self, c=None, *args, **kw): + if not c: + c = Tree.select().max('compose_id') + next = None + else: + c = to_int(c) + next = Tree.select(Tree.q.compose_id > c).min('compose_id') + prev = Tree.select(Tree.q.compose_id < c).max('compose_id') + + treesort = lambda a,b: archsort(a.arch, b.arch) + trees = sorted(Tree.selectBy(compose_id=c), treesort) + tests = Test.select(Test.q.id <= 14) + results = {} + for tree in trees: + rdict = dict([(t.id, None) for t in tests]) + for r in tree.results: + rdict[r.testID] = r + results[tree.id] = rdict + + result_form = ResultForm('resultform', action=tg.url('/add_result', kw)) + return dict(c=c, prev=prev, next=next, + result_form=result_form, + in_qa='qa' in identity.current.groups, + admin='qa-admin' in identity.current.groups, + tests=tests, trees=trees, results=results) + + # JSON RPC calls + @expose(allow_json=True) + def get_tests(self): + tests = Test.select() + return dict(tests=list(tests)) + + @expose(allow_json=True) + def get_trees(self, *args, **kw): + tree_results = Tree.selectBy(**kw) + return dict(trees=list(tree_results)) + + @expose(allow_json=True) + def get_results(self, *args, **kw): + results_results = TestResults.selectBy(**kw) + return dict(results=list(results_results)) + + # JSON calls that require authentication + @identity.require(identity.in_group("qa")) + @expose(allow_json=True) + def add_tree(self, arch, compose_id, tree_time=None, repodata_time=None): + # check to see if tree already exists + tree_result = Tree.selectBy(arch=arch, compose_id=compose_id, + tree_time=tree_time, repodata_time=repodata_time) + if tree_result.count(): + return dict(exc='ValueError', tg_flash='Tree exists') + t = Tree(arch=arch, + compose_id=to_int(compose_id), + tree_time=to_int(tree_time), + repodata_time=to_int(repodata_time)) + hub.commit() + return dict(tree=t) + + @identity.require(identity.in_group("qa")) + @expose(allow_json=True) + def update_tree(self, treeid, arch=None, compose_id=None, tree_time=None, repodata_time=None): + tr = list(Tree.selectBy(id=treeid)) + if len(tr) == 0: + return dict(exc='ValueError', tg_flash='Tree %u not found' % treeid) + tree = tr[0] + if arch: + tree.arch = arch + if tree_time: + tree.tree_time = to_int(tree_time) + if repodata_time: + tree.repodata_time = to_int(repodata_time) + if compose_id: + tree.compose_id = to_int(compose_id) + hub.commit() + return dict(tree=tree) + + @identity.require(identity.in_group("qa")) + @expose(allow_json=True) + def add_result(self, treeid, testid, result, overwrite=False, *args, **kw): + if not overwrite: + # TODO return an exception if there's already a result here + pass + tr = TestResult(tree=to_int(treeid), + test=to_int(testid), + result=to_int(result), + userid=identity.current.user.id, + bug_id=None) + hub.commit() # XXX necessary? + if 'json' in fedora.tg.util.request_format(): + return dict(id=tr.id) + redirect(tg.url("/", kw)) + + @identity.require(identity.in_group("qa")) + @expose(allow_json=True) + def delete_result(self, id, *args, **kw): + tr = TestResult.get(id) + tr.destroySelf() + if 'json' in fedora.tg.util.request_format(): + return dict(deleted=id) + redirect(tg.url("/", kw)) + + # XXX: delete_tree()? (would require qa-admin) + + # Identity stuff (login/logout) + @expose(template="israwhidebroken.templates.login") + @expose(allow_json=True) + def login(self, forward_url=None, *args, **kw): + + if forward_url: + if isinstance(forward_url, list): + forward_url = forward_url.pop(0) + else: + del request.params['forward_url'] + + # If the login was successful... + if not identity.current.anonymous and identity.was_login_attempted() \ + and not identity.get_identity_errors(): + if 'json' == fedora.tg.util.request_format(): + return dict(user=identity.current.user) + flash("Welcome, %s" % identity.current.user_name) + redirect(tg.url(forward_url or '/', kw)) + + if identity.was_login_attempted(): + msg = _("The credentials you supplied were not correct or " + "did not grant access to this resource.") + elif identity.get_identity_errors(): + msg = _("You must provide your credentials before accessing " + "this resource.") + else: + msg = _("Please log in.") + if not forward_url: + forward_url = request.headers.get("Referer", "/") + + response.status = 401 + return dict(logging_in=True, message=msg, + forward_url=forward_url, previous_url=request.path_info, + original_parameters=request.params) + + @expose(allow_json=True) + def logout(self): + identity.current.logout() + if 'json' in fedora.tg.util.request_format(): + return dict() + redirect("/") diff --git a/front-ends/israwhidebroken/israwhidebroken/json.py b/front-ends/israwhidebroken/israwhidebroken/json.py new file mode 100644 index 0000000..9e47648 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/json.py @@ -0,0 +1,11 @@ +# A JSON-based API(view) for your app. +# Most rules would look like: +# @jsonify.when("isinstance(obj, YourClass)") +# def jsonify_yourclass(obj): +# return [obj.val1, obj.val2] +# @jsonify can convert your objects to following types: +# lists, dicts, numbers and strings + +from turbojson.jsonify import jsonify + +from turbojson.jsonify import jsonify_sqlobject diff --git a/front-ends/israwhidebroken/israwhidebroken/model.py b/front-ends/israwhidebroken/israwhidebroken/model.py new file mode 100644 index 0000000..da9aad8 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/model.py @@ -0,0 +1,55 @@ +from datetime import datetime +import pkg_resources +pkg_resources.require("SQLObject>=0.9.9") +from turbogears.database import PackageHub +# import some basic SQLObject classes for declaring the data model +# (see http://www.sqlobject.org/SQLObject.html#declaring-the-class) +from sqlobject import SQLObject, SQLObjectNotFound, MultipleJoin, RelatedJoin +# import some datatypes for table columns from SQLObject +# (see http://www.sqlobject.org/SQLObject.html#column-types for more) +from sqlobject import StringCol, IntCol, DateTimeCol, ForeignKey +from turbogears import identity + +__connection__ = hub = PackageHub('israwhidebroken') + + +# your data model + +class Tree(SQLObject): + arch = StringCol(length=10, notNone=True) + # The compose ID links together a given day's rawhide trees, even if + # everything else is missing - so if there's no PPC tree_time/repo_time + # we can still have a ppc Tree for that day + compose_id = IntCol(notNone=True) + # These could be null if repodata/images are missing. + # They're IntCol because I don't want to deal with converting + # between the representation in the file(s) and weirdo datetime + # or timestamp columns. + tree_time = IntCol() + repodata_time = IntCol() + # A tree can have many results you might want to look at + results = MultipleJoin('TestResult') + +class Test(SQLObject): + name = StringCol(notNone=True, unique=True) + short_desc = StringCol(notNone=True) + uri = StringCol(notNone=True) + # Tests also have many results but we don't usually care about viewing + # every single test result ever for a given test. Hence no join here. + +class TestResult(SQLObject): + # References to other objects - each test result comes from running one + # Test on a given Tree. + test = ForeignKey('Test') + tree = ForeignKey('Tree') + # result is just an int - 0 for fail, >= 1 for pass. + # It's not a float 'cuz we don't have any performance tests in RATS. + # XXX - How do we want to handle WARN or ERROR? + result = IntCol(notNone=True) + # userid of the user who submitted the result + userid = IntCol(notNone=True) + # bug_id for further information, esp. if the test is a fail. + # XXX list of bug IDs? + bug_id = IntCol() + # timestamp this result was entered + timestamp = DateTimeCol(notNone=True, default=datetime.now) diff --git a/front-ends/israwhidebroken/israwhidebroken/release.py b/front-ends/israwhidebroken/israwhidebroken/release.py new file mode 100644 index 0000000..13c5b3a --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/release.py @@ -0,0 +1,17 @@ +# Release information about israwhidebroken + +version = "0.1" + +description = "A simple web dashboard for Fedora Rawhide autoqa results." +long_description = """A simple web dashboard to collect and displays the +results of Fedora's Rawhide Acceptance Test Plan. Accepts automated result +submission (authenticated against FAS, using JSON RPC) and allows manual +submission of results (for non-automated tests).""" +author = "Will Woods" +email = "wwoods@redhat.com" +copyright = "Copyright (c) 2009 Red Hat, Inc." + +# if it's open source, you might want to specify these +# url = "http://yourcool.site/" +# download_url = "http://yourcool.site/download" +license = "GPLv2+" # XXX AGPL? diff --git a/front-ends/israwhidebroken/israwhidebroken/static/css/layout.css b/front-ends/israwhidebroken/israwhidebroken/static/css/layout.css new file mode 100644 index 0000000..393f603 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/static/css/layout.css @@ -0,0 +1,627 @@ +/* $Id: layout.css,v 1.3 2007/01/06 08:03:27 lmacken Exp $ */ + +body { + font-size: 0.9em; + font-family: liberation, veranda, sans-serif; + color: #4F4F4F; + margin: 0; + padding: 0; + background-color: #d9d9d9; +} + +#form_builds_text { + width: 20em; +} + +a:link { + color: #22437F; +} +a:visited { + color: #48468f; +} +a:hover { + color: #f20; +} +a[name] { + color: inherit; + text-decoration: inherit; +} + +#page-main { + padding-right: 20px; + padding-left: 20px; +} + +table#results { + margin: auto; + border-spacing: 0; + border-top: 1px solid #000; +} +table#results td { + border-bottom: 1px solid #000; + background-color: #ddd; + padding: 10px 10px; +} +table#results td.pass { + background-color: #9f9; +} +table#results td.fail { + background-color: #f99; +} + +#fedora-header { + background-color: #fff; + height: 62px; +} +#fedora-header img { + border: 0; + vertical-align: middle; +} +#fedora-header-logo { + /* position is offset by the header padding amount */ + position: absolute; + left: 26px; + top: 13px; + z-index: 3; +} +#fedora-header-logo img { + /* + width: 110px; + height: 40; */ +} +#fedora-header-items { + /* position is offset by the header padding amount */ + position: absolute; + right: 10px; + top: 8px; + text-align: right; + display: inline; +} +#fedora-header-items a { + color: #000; + text-decoration: none; + padding: 7pt; + font-size: 0.8em; +} +#fedora-header-items a:hover, #fedora-header-search-button:hover { + color: #f20; + cursor: pointer; +} +#fedora-header-items img { + margin-right: 1px; + /* + width: 36px; + height: 36px;*/ +} +#fedora-header-search { + height: 25px; +} +#fedora-header-search-entry { + vertical-align: top; + margin: 0.65em 4px 0 10px; + padding: 2px 4px; + background-color: #f5f5f5; + border: 1px solid #999; + font-size: 0.8em !important; +} +#fedora-header-search-entry:focus { + background-color: #fff; + border: 1px solid #555; +} +#fedora-header-search-button { + font-size: 0.8em !important; + vertical-align: top; + margin-top: 0.2em; + border: 0; + padding: 7px; + padding-left: 21px; +} +#fedora-header-items form { + float: right; +} +#fedora-header-items input { + font-size: 0.85em; +} +#fedora-nav { + margin: 0; + padding: 0; + background-color: #22437f; + font-size: 0; + height: 5px; + border-top: 1px solid #000; + border-bottom: 1px solid #f5f5f5; +} +#fedora-nav ul { + margin: 0; + padding: 0; +} +#fedora-nav li { + display: inline; + list-style: none; + padding: 0 5pt; +} +#fedora-nav li + li { + padding-left: 8pt; + border-left: 1px solid #99a5bf; +} +#fedora-nav a { + color: #c5ccdb; + text-decoration: none; +} +#fedora-nav a:hover { + color: #fff; +} + +#fedora-side-left { + position: absolute; + z-index: 2; + width: 12em; + /* Space down for the approx line height (fonts) */ + left: 12px; +} +#fedora-side-right { + position: absolute; + z-index: 1; + width: 13em; + right: 12px; + padding-top: 3px; + } +#fedora-side-left, #fedora-side-right { + top: 2px; + /* add to the top margin to compensate for the fixed sizes */ + margin-top: 75px; + color: #555; + font-size: 0.9em; +} +#fedora-side-right ul { + list-style: square inside; + padding: 0; + margin: 0; +} + +/* Left-side naviagation */ +#fedora-side-nav-label { + display: none; +} +#fedora-side-nav { + list-style: none; + margin: 0; + padding: 0; + border: 1px solid #5976b2; + border-top: 0; + background-color: #22437f; +} +#fedora-side-nav li { + margin: 0; + padding: 0; + border-top: 1px solid #5976b2; + /* IE/Win gets upset if there is no bottom border... Go figure. */ + border-bottom: 1px solid #22437f; +} +#fedora-side-nav a { + margin: 0; + color: #c5ccdb; + display: block; + text-decoration: none; + padding: 4px 6px; +} +#fedora-side-nav a:hover { + background-color: #34548f; + color: #fff; +} +#fedora-side-nav ul { + list-style: none; + margin: 0; + padding: 0; +} +#fedora-side-nav ul li { + border-top: 1px solid #34548e; + background-color: #34548e; + /* IE/Win gets upset if there is no bottom border... Go figure. */ + border-bottom: 1px solid #34548e; +} +#fedora-side-nav ul li:hover { + border-bottom: 1px solid #34548f; +} +#fedora-side-nav ul li a { + padding-left: 18px; + color: #a7b2c9; +} +#fedora-side-nav ul li a:hover { + /* + background-color: #46659e; + */ +} +#fedora-side-nav ul ul li a { + padding-left: 36px; +} +#fedora-side-nav strong a { + font-weight: normal; + color: #fff !important; + background-color: #10203b; +} +#fedora-side-nav strong a:hover { + background-color: #172e56 !important; +} + +/* content containers */ +#fedora-middle-one, #fedora-middle-two, #fedora-middle-three { + font-size: 0.9em; + /* position: relative; */ /* relative to utilize z-index */ + width: auto; + min-width: 120px; + margin: 10px; + z-index: 3; /* content can overlap when the browser is narrow */ +} +#fedora-middle-two, #fedora-middle-three { + margin-left: 12em; + padding-left: 24px; +} +#fedora-middle-three { + margin-right: 13em; +} + +#fedora-content { + padding-top: 24px; + padding-bottom: 24px; + border: 1px solid #aaa; + background-color: #fff; +} + +#fedora-content > .fedora-corner-bottom { top: 0 } + +.fedora-corner-tl, .fedora-corner-tr, .fedora-corner-bl, .fedora-corner-br { + background-color: #d9d9d9; + position: relative; + width: 19px; + height: 19px; + /* The following line is to render PNGs with alpha transparency within IE/Win, using DirectX */ + /* Work-around for IE6/Mac borkage (Part 1) */ + display: none; +} + +.fedora-corner-tl, .fedora-corner-bl { float: left; left: 0px; } +.fedora-corner-tr, .fedora-corner-br { float: right; right: 0px; } +.fedora-corner-tl, .fedora-corner-tr { top: 0px; } +.fedora-corner-bl, .fedora-corner-br { bottom: 0px; margin-top: -19px; /* Opera fix (part 1) */ top: -18px;} + +html>body .fedora-corner-tl { background: #d9d9d9 url("../images/corner-tl.png") no-repeat left top; } +html>body .fedora-corner-tr { background: #d9d9d9 url("../images/corner-tr.png") no-repeat right top; } +html>body .fedora-corner-bl { background: #d9d9d9 url("../images/corner-bl.png") no-repeat left bottom; } +html>body .fedora-corner-br { background: #d9d9d9 url("../images/corner-br.png") no-repeat right bottom; } + +.fedora-corner-tl, .fedora-corner-tr, .fedora-corner-bl, .fedora-corner-br { + /* Restore the view for everything but IE6/Mac (part 2 of the "IE/Mac fix") */ + display: block; +} + +.fedora-corner-bl, .fedora-corner-br { + top: 0px; +} + +.content { margin: 0 1em } + +#fedora-sidelist { + position: relative; + bottom: 3px; + margin: 0; + padding: 3px !important; + border: 1px solid #bbb; + background-color: #ccc; + -moz-border-radius: 2.5px; +} +#fedora-sidelist strong a { + font-weight: normal; + background-color: #555; + color: #fff; +} +#fedora-sidelist strong a:hover { + background-color: #333; + color: #fff; +} +#fedora-sidelist li { + list-style-position: outside; + font-size: 0.9em; + list-style: none; + border: 1px solid #ccc; + border-width: 1px 0; + padding: 0; + list-style: none; +} +#fedora-sidelist li a { + text-decoration: none; + display: block; + padding: 6px 8px; + -moz-border-radius: 2.5px; +} +#fedora-sidelist li a:hover { + background-color: #999; + color: #eee; +} + +#fedora-footer { + font-size: 0.75em; + text-align: right; + color: #777; + margin-bottom: 2em; + margin-right: 2em; +} +#loginlogout { + font-size: 0.75em; + text-align: right; + vertical-align: bottom; + margin-right: 24px; +} +#fedora-printable { + text-align: center; + margin: 1em 0; + font-size: 0.85em; +} +#fedora-printable a { + text-decoration: none; + padding: 5px 0; + padding-left: 18px; + background: transparent url("/images/printable.png") no-repeat left; +} +#fedora-printable a:hover { + text-decoration: underline; +} + +input.c1 { + font-size:12px; + border: 1px solid #4F4F4F; +} + +textarea.c2 { + font-size: 12px; + border: 1px solid #4F4F4F; +} + +option.c3 { + font-size:12px; + /* border: 1px solid #4F4F4F; */ +} + +select.c4 { + font-size:12px; + border: 1px solid #4F4F4F; +} + +input.button { + font-size:12px; + border: 1px solid #4F4F4F; +} + + +/* +** The update list (list.kid) +*/ +table.list { + border-collapse: collapse; + width: 100%; +} + +th.list { + padding-left: 25px; + padding-top: 5px; + padding-bottom: 5px; + text-align: left; + color: #FFFFFF; + background-color: #4f4f4f; +} + +tr.list { + border-bottom: 1px solid #bcbcbc; +} + +td.list { + padding-left: 25px; + padding-top: 2px; + padding-bottom: 2px; +} + +a.list { + text-decoration: none; +} + +div.list { + text-align: right; + padding-right: 10px; + padding-bottom: 5px; +} + +/* +** CSS for displaying package updates (show.kid) +*/ + +table.show { + vertical-align: middle; + border-collapse: collapse; + width: 100%; + border-top: 1px solid #bcbcbc; + border-bottom: 1px solid #bcbcbc; +} + +/* +td.title { + text-align: right; + vertical-align: top; + background-color: #f1f1f1; + width: 25%; + padding-top: 5px; + padding-right: 5px; +} + */ + +td.value { + text-align: left; + padding-top: 5px; + padding-left: 5px; +} + +div.show { + padding-bottom: 5px; + font-size: 1.5em; +} + +div.alert { + padding-left: 20px; +} + +div.flash { + padding-top: 4px; + padding-bottom: 4px; + margin-bottom: 10px; + width: 75%; + font-weight: bold; + background: #ffb200; +} + +/* +** Login CSS (login.kid) +*/ + +table.login { + border-collapse: collapse; + border-bottom: 1px solid #bcbcbc; + border-top: 1px solid #bcbcbc; + width: 100%; +} + + +td.title { + text-align: right; + vertical-align: top; + background-color: #f1f1f1; + width: 175px; + padding-top: 5px; + padding-right: 5px; + font-weight: bold; + padding: 5px; +} + +td.value { + text-align: left; + padding-top: 5px; + padding-left: 5px; + padding: 5px; +} + +h1.padded { + padding-left: 10px; +} + +p.padded { + padding-left: 10px; +} + +/* +td.title { + text-align: right; + vertical-align: top; + background-color: #f1f1f1; + width: 25%; + padding-right: 5px; +} + */ + +td.value { + text-align: left; + padding-left: 5px; +} + +.release +{ + overflow: auto; +} + +.release .link +{ + float: left; + margin: 0; + padding: 0; +} + +.release .rsslink +{ + float: right; + width: 18px; + overflow:hidden; + padding-left: 0!important; + padding-right: 4px!important; +} + +.release .rsslink:hover, .release .link:hover +{ + background: none!important; +} + +.rsslink img +{ + display: block; + border: none; + margin: 0; + padding: 0; +} + +#mashstatus +{ + overflow: scroll; + height: 50em; +} + +/* jquery.tooltip.css */ +#tooltip { + position: absolute; + z-index: 3000; + border: 1px solid #111; + background-color: #eee; + padding: 5px; + opacity: 0.85; +} +#tooltip h3, #tooltip div { margin: 0; } + +#tooltip.pretty { + font-family: Arial; + border: none; + width: 210px; + padding:20px; + height: 135px; + opacity: 0.8; + background: url('/updates/static/images/shadow.png'); +} +#tooltip.pretty h3 { + margin-bottom: 0.75em; + font-size: 12pt; + width: 220px; + text-align: center; +} +#tooltip.pretty div { width: 220px; text-align: left; } + +#tooltip.fancy { + background: url('/updates/static/images/shadow2.png'); + padding-top: 4em; + height: 100px; +} + +#main { padding: 1em; } +#banner { padding: 15px; background-color: #06b; color: white; font-size: large; border-bottom: 1px solid #ccc; + background: url(bg.gif) repeat-x; text-align: center } +fieldset { padding: 8px; } +legend { font-weight: bold; } + +.jscom, .mix htcom { color: #4040c2; } +.com { color: green; } +.regexp { color: maroon; } +.string { color: teal; } +.keywords { color: blue; } +.global { color: #008; } +.numbers { color: #880; } +.comm { color: green; } +.tag { color: blue; } +.entity { color: blue; } +.string { color: teal; } +.aname { color: maroon; } +.avalue { color: maroon; } +.jquery { color: #00a; } +.plugin { color: red; } diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/corner-bl.png b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-bl.png new file mode 100644 index 0000000..58d269c Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-bl.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/corner-br.png b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-br.png new file mode 100644 index 0000000..c03dd92 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-br.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tl.png b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tl.png new file mode 100644 index 0000000..08ab7a3 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tl.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tr.png b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tr.png new file mode 100644 index 0000000..b279db2 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/corner-tr.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/favicon.ico b/front-ends/israwhidebroken/israwhidebroken/static/images/favicon.ico new file mode 100644 index 0000000..332557b Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/favicon.ico differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/header-fedora_logo.png b/front-ends/israwhidebroken/israwhidebroken/static/images/header-fedora_logo.png new file mode 100644 index 0000000..55300d2 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/header-fedora_logo.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/header_inner.png b/front-ends/israwhidebroken/israwhidebroken/static/images/header_inner.png new file mode 100644 index 0000000..2b2d87d Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/header_inner.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/info.png b/front-ends/israwhidebroken/israwhidebroken/static/images/info.png new file mode 100644 index 0000000..329c523 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/info.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/ok.png b/front-ends/israwhidebroken/israwhidebroken/static/images/ok.png new file mode 100644 index 0000000..fee6751 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/ok.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/tg_under_the_hood.png b/front-ends/israwhidebroken/israwhidebroken/static/images/tg_under_the_hood.png new file mode 100644 index 0000000..bc9c79c Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/tg_under_the_hood.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/static/images/under_the_hood_blue.png b/front-ends/israwhidebroken/israwhidebroken/static/images/under_the_hood_blue.png new file mode 100644 index 0000000..90e84b7 Binary files /dev/null and b/front-ends/israwhidebroken/israwhidebroken/static/images/under_the_hood_blue.png differ diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/__init__.py b/front-ends/israwhidebroken/israwhidebroken/templates/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/__init__.py diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/footer.html b/front-ends/israwhidebroken/israwhidebroken/templates/footer.html new file mode 100644 index 0000000..f7b5f62 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/footer.html @@ -0,0 +1,21 @@ + + +
+ + TurboGears + +
+
+ + + + + diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/header.html b/front-ends/israwhidebroken/israwhidebroken/templates/header.html new file mode 100644 index 0000000..407f2b9 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/header.html @@ -0,0 +1,19 @@ + + +
+ +
+

+ Is Rawhide Broken? + + + +

+
+
+
+ diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/index.html b/front-ends/israwhidebroken/israwhidebroken/templates/index.html new file mode 100644 index 0000000..8222d2d --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/index.html @@ -0,0 +1,45 @@ + + + + + + + + Is Rawhide Broken? + + + + + + + + + + + + +
« + Rawhide compose ${c} + »${tree.arch}
${test.name} + + + ${result_form(dict(result=rclass(r) or 'none', + testid=test.id, treeid=tree.id, c=c))} + + + ${rclass(r) or 'none'} + + + x +
+ + diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/login.html b/front-ends/israwhidebroken/israwhidebroken/templates/login.html new file mode 100644 index 0000000..c8c1d08 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/login.html @@ -0,0 +1,18 @@ + + + + + + + + +Login Form + + + +${message} + + diff --git a/front-ends/israwhidebroken/israwhidebroken/templates/master.html b/front-ends/israwhidebroken/israwhidebroken/templates/master.html new file mode 100644 index 0000000..9fb5567 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/templates/master.html @@ -0,0 +1,44 @@ + + + + + + + + + Your title goes here + + + + + + + + ${header()} + +
+
+
+ +
+ +
+ + ${fedora_footer()} + + diff --git a/front-ends/israwhidebroken/israwhidebroken/tests/__init__.py b/front-ends/israwhidebroken/israwhidebroken/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/tests/__init__.py diff --git a/front-ends/israwhidebroken/israwhidebroken/tests/test_controllers.py b/front-ends/israwhidebroken/israwhidebroken/tests/test_controllers.py new file mode 100644 index 0000000..7a0648a --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/tests/test_controllers.py @@ -0,0 +1,37 @@ +import unittest +import turbogears +from turbogears import testutil +from israwhidebroken.controllers import Root +import cherrypy + +cherrypy.root = Root() + +class TestPages(unittest.TestCase): + + def setUp(self): + turbogears.startup.startTurboGears() + + def tearDown(self): + """Tests for apps using identity need to stop CP/TG after each test to + stop the VisitManager thread. + See http://trac.turbogears.org/turbogears/ticket/1217 for details. + """ + turbogears.startup.stopTurboGears() + + def test_method(self): + "the index method should return a string called now" + import types + result = testutil.call(cherrypy.root.index) + assert type(result["now"]) == types.StringType + + def test_indextitle(self): + "The indexpage should have the right title" + testutil.create_request("/") + response = cherrypy.response.body[0].lower() + assert "welcome to turbogears" in response + + def test_logintitle(self): + "login page should have the right title" + testutil.create_request("/login") + response = cherrypy.response.body[0].lower() + assert "login" in response diff --git a/front-ends/israwhidebroken/israwhidebroken/tests/test_model.py b/front-ends/israwhidebroken/israwhidebroken/tests/test_model.py new file mode 100644 index 0000000..4c00069 --- /dev/null +++ b/front-ends/israwhidebroken/israwhidebroken/tests/test_model.py @@ -0,0 +1,22 @@ +# If your project uses a database, you can set up database tests +# similar to what you see below. Be sure to set the db_uri to +# an appropriate uri for your testing database. sqlite is a good +# choice for testing, because you can use an in-memory database +# which is very fast. + +from turbogears import testutil, database +# from israwhidebroken.model import YourDataClass, User + +# database.set_db_uri("sqlite:///:memory:") + +# class TestUser(testutil.DBTest): +# def get_model(self): +# return User +# def test_creation(self): +# "Object creation should set the name" +# obj = User(user_name = "creosote", +# email_address = "spam@python.not", +# display_name = "Mr Creosote", +# password = "Wafer-thin Mint") +# assert obj.display_name == "Mr Creosote" + diff --git a/front-ends/israwhidebroken/populate-israwhidebroken-db b/front-ends/israwhidebroken/populate-israwhidebroken-db new file mode 100644 index 0000000..1e9bf4c --- /dev/null +++ b/front-ends/israwhidebroken/populate-israwhidebroken-db @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Setup the israwhidebroken database with some initial values""" + +import sys +from os import getcwd +from os.path import dirname, exists, join + +# Enumerate the basic test cases defined in the test plan (see URL) +# https://fedoraproject.org/wiki/QA:Rawhide_Acceptance_Test_Plan#Test_Cases +rats_tests = [ +('Repodata validity', + 'Checks that the yum metadata for a given repository is valid', + 'http://fedoraproject.org/wiki/QA:Repodata_validity_test_case'), + +('comps.xml validity', + 'Verifies that comps.xml is usable by the installer and other system tools', + 'http://fedoraproject.org/wiki/QA:Comps_Validity_Test_Case'), + +('Core package dependency closure', + 'Ensure that the Critical Path Packages have no conflicts or missing deps', + 'http://fedoraproject.org/wiki/QA:Core_package_dependency_closure_test_case'), + +('Core package existence', + 'Ensure that the Critical Path Packages are present and not corrupted', + 'http://fedoraproject.org/wiki/QA:Core_package_existence_test_case'), + +('Installer image existence', + 'Check that installer images and metadata are present and not corrupt', + 'http://fedoraproject.org/wiki/QA:Installer_image_presence_test_case'), + +('Kernel boot', + 'Check that the installer kernel boots', + 'http://fedoraproject.org/wiki/QA:Kernel_simple_boot_test_case'), + +('Anaconda loader fetching stage2', + 'Ensure that anaconda\'s first stage can fetch stage2 (install.img)', + 'http://fedoraproject.org/wiki/QA:Anaconda_stage2_fetch_test_case'), + +('Anaconda stage2 disk probe', + 'Check that anaconda can detect the presence of disk devices', + 'http://fedoraproject.org/wiki/QA:Anaconda_storage_probe_test_case'), + +('Anaconda package installation', + 'Check that anaconda is able to install packages to disk', + 'http://fedoraproject.org/wiki/QA:Anaconda_package_install_test_case'), + +('Anaconda bootloader setup', + 'Check that anaconda can set up the bootloader', + 'http://fedoraproject.org/wiki/QA:Anaconda_bootloader_setup_test_case'), + +('X startup/basic display configuration', + 'Check that Xorg can detect and configure the video controller and monitor', + 'http://fedoraproject.org/wiki/QA:X_basic_display_test_case'), + +('X basic input handling', + 'Ensure that the X server can receive and process input', + 'http://fedoraproject.org/wiki/QA:X_basic_input_handling_test_case'), + +('Basic network connectivity', + 'A very simple networking test', + 'http://fedoraproject.org/wiki/QA:Network_basic_test_case'), + +('yum update functionality', + 'A very simple check of \'yum update\' functionality', + 'http://fedoraproject.org/wiki/QA:Yum_simple_update_test_case'), +] + +treedata = [ +# These 3 items correspond to the Fedora 11 trees +{'arch':'i386', + 'compose_id':2009060201, 'tree_time':1243980101, 'repodata_time':1243979414}, +{'arch':'x86_64', + 'compose_id':2009060201, 'tree_time':1243980953, 'repodata_time':1243980178}, +{'arch':'ppc', + 'compose_id':2009060201, 'tree_time':1243981037, 'repodata_time':1243979681}, +] + +def setup_database(): + # Load the models + from israwhidebroken import model + + # Add tests to the test table + # XXX force test IDs? + for (n, sd, u) in rats_tests: + exists = model.Test.select(model.Test.q.name==n) + if exists.count() == 0: + print "adding Test(%s)" % n + test = model.Test(name=n, short_desc=sd, uri=u) + else: + print "Test(%s) already exists" % n + # Add example trees + for t in treedata: + exists = model.Tree.selectBy(arch=t['arch'],compose_id=t['compose_id']) + if exists.count() == 0: + print "adding Tree(%u-%s)" % (t['compose_id'], t['arch']) + tree = model.Tree(**t) + else: + print "Tree(%u-%s) already exists" % (t['compose_id'], t['arch']) + + model.hub.commit() + + # TODO: add a couple of example results + + print "Successfully setup" + +if __name__ == '__main__': + import turbogears + setupdir = dirname(__file__) + curdir = getcwd() + if len(sys.argv) > 1: + configfile = sys.argv[1] + elif exists(join(setupdir, "setup.py")): + configfile = join(setupdir, "dev.cfg") + elif exists(join(curdir, "prod.cfg")): + configfile = join(curdir, "prod.cfg") + print "using config %s" % configfile + turbogears.update_config(configfile=configfile, + modulename="israwhidebroken.config") + setup_database() diff --git a/front-ends/israwhidebroken/setup.py b/front-ends/israwhidebroken/setup.py new file mode 100644 index 0000000..2da4f0d --- /dev/null +++ b/front-ends/israwhidebroken/setup.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from setuptools import setup, find_packages +from turbogears.finddata import find_package_data + +import os +execfile(os.path.join("israwhidebroken", "release.py")) + +packages=find_packages() +package_data = find_package_data(where='israwhidebroken', + package='israwhidebroken') +if os.path.isdir('locales'): + packages.append('locales') + package_data.update(find_package_data(where='locales', + exclude=('*.po',), only_in_packages=False)) + +setup( + name="israwhidebroken", + version=version, + # uncomment the following lines if you fill them out in release.py + description=description, + author=author, + author_email=email, + #url=url, + #download_url=download_url, + license=license, + + install_requires=[ + "TurboGears >= 1.0.8", + "SQLObject>=0.9.9" + ], + zip_safe=False, + packages=packages, + package_data=package_data, + keywords=[ + # Use keywords if you'll be adding your package to the + # Python Cheeseshop + + # if this has widgets, uncomment the next line + # 'turbogears.widgets', + + # if this has a tg-admin command, uncomment the next line + # 'turbogears.command', + + # if this has identity providers, uncomment the next line + # 'turbogears.identity.provider', + + # If this is a template plugin, uncomment the next line + # 'python.templating.engines', + + # If this is a full application, uncomment the next line + # 'turbogears.app', + ], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Framework :: TurboGears', + # if this is an application that you'll distribute through + # the Cheeseshop, uncomment the next line + # 'Framework :: TurboGears :: Applications', + + # if this is a package that includes widgets that you'll distribute + # through the Cheeseshop, uncomment the next line + # 'Framework :: TurboGears :: Widgets', + ], + test_suite='nose.collector', + scripts = ["start-israwhidebroken", "israwhidebroken.wsgi", "populate-israwhidebroken-db"], + #entry_points = { + # 'console_scripts': [ + # 'start-israwhidebroken = israwhidebroken.commands:start', + # ], + #}, + # Uncomment next line and create a default.cfg file in your project dir + # if you want to package a default configuration in your egg. + data_files = [('/etc/httpd/conf.d', ['israwhidebroken.conf']), + ('/etc', ['israwhidebroken.cfg']), + ('/var/lib/israwhidebroken', []), + ], + ) diff --git a/front-ends/israwhidebroken/start-israwhidebroken b/front-ends/israwhidebroken/start-israwhidebroken new file mode 100755 index 0000000..8d4b331 --- /dev/null +++ b/front-ends/israwhidebroken/start-israwhidebroken @@ -0,0 +1,28 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Start script for the israwhidebroken TurboGears project. + +This script is only needed during development for running from the project +directory. When the project is installed, easy_install will create a +proper start script. +""" + +import os +import sys + +if os.path.isdir("/usr/share/autoqa-israwhidebroken"): + sys.path.append("/usr/share/autoqa-israwhidebroken") + +from israwhidebroken.commands import start, ConfigurationError + +# jsonfas2 +from turbogears import startup +from fedora.tg.util import enable_csrf +startup.call_on_startup.append(enable_csrf) + +if __name__ == "__main__": + try: + start() + except ConfigurationError, exc: + sys.stderr.write(str(exc)) + sys.exit(1) diff --git a/front-ends/israwhidebroken/test.cfg b/front-ends/israwhidebroken/test.cfg new file mode 100644 index 0000000..5e6365f --- /dev/null +++ b/front-ends/israwhidebroken/test.cfg @@ -0,0 +1,32 @@ +[global] +# You can place test-specific configuration options here (like test db uri, etc) + +# DATABASE + +sqlobject.dburi = "sqlite:///:memory:" + +# LOGGING + +[logging] + +[[formatters]] +[[[full_content]]] +format='*(asctime)s *(name)s *(levelname)s *(message)s' + +[[handlers]] +[[[test_out]]] +class='StreamHandler' +level='DEBUG' +args='(sys.stdout,)' +formatter='full_content' + +[[loggers]] +[[[israwhidebroken]]] +level='DEBUG' +qualname='israwhidebroken' +handlers=['test_out'] + +[[[turbogears]]] +level='INFO' +qualname='turbogears' +handlers=['test_out'] diff --git a/hooks/post-koji-build/watch-koji-builds.py b/hooks/post-koji-build/watch-koji-builds.py index 6a27aee..6cd1fd1 100755 --- a/hooks/post-koji-build/watch-koji-builds.py +++ b/hooks/post-koji-build/watch-koji-builds.py @@ -16,6 +16,7 @@ import time import pickle import xmlrpclib import subprocess +import optparse from autoqa import koji_utils # Directory where we cache info between runs @@ -60,7 +61,7 @@ def save_list(pkglist): pickle.dump(pkglist, out) out.close() -def new_tagged_builds_since(session, taglist, prevtime): +def new_builds_since(session, taglist, prevtime): untagged_builds = [] tagged_builds = {} for tag in taglist: @@ -78,19 +79,25 @@ def new_tagged_builds_since(session, taglist, prevtime): untagged_builds.append(nvr) #print "untagged: %s" % " ".join(untagged_builds) #print "tagged: %s" % " ".join(tagged_builds) - save_list(untagged_builds) - return tagged_builds + return (tagged_builds, untagged_builds) if __name__ == '__main__': - # TODO: optparse for verbose/prevtime/dryrun - verbose = ('--verbose' in sys.argv) - dryrun = ('--dryrun' in sys.argv) + # Set up the option parser + # TODO: optparse for prevtime + parser = optparse.OptionParser(description='Script to watch a set of koji \ +tags for new builds and kick off tests when new builds/packages are found.') + parser.add_option('--dryrun', '--dry-run', action='store_true', + help='Do not actually execute commands, just show what would be done') + parser.add_option('--verbose', action='store_true', + help='Print extra information') + (opts, args) = parser.parse_args() + # load prevtime from some saved timestamp prevtime = get_prevtime() if not prevtime: print "No previous run - checking builds in the past 3 hours" prevtime = time.time() - 10800 - if verbose: + if opts.verbose: print "Looking up builds since %s" % time.ctime(prevtime) # Set up the koji connection kojiopts = {} # Possible items: user, password, debug_xmlrpc, debug.. @@ -99,8 +106,11 @@ if __name__ == '__main__': tagged_builds = [] try: session.ensure_connection() - new_builds = new_tagged_builds_since(session, taglist, prevtime) - for tag, builds in new_builds.items(): + (tagged_builds, untagged_builds) = new_builds_since(session, + taglist, prevtime) + if not opts.dryrun: + save_list(untagged_builds) + for tag, builds in tagged_builds.items(): for b in builds: # Get a list of all package arches in this build arches = [r['arch'] for r in session.listRPMs(b['build_id'])] @@ -116,7 +126,7 @@ if __name__ == '__main__': for arch in testarches: harnesscall += ['--arch', arch] harnesscall.append(b['nvr']) - if dryrun: + if opts.dryrun: print " ".join(harnesscall) continue subprocess.call(harnesscall) diff --git a/hooks/post-repo-update/watch-repos.py b/hooks/post-repo-update/watch-repos.py index 7f96264..1261a22 100755 --- a/hooks/post-repo-update/watch-repos.py +++ b/hooks/post-repo-update/watch-repos.py @@ -13,8 +13,14 @@ import os import sys import subprocess from autoqa.repoinfo import repoinfo +import optparse -dryrun = ('--dryrun' in sys.argv) +# Set up the option parser +parser = optparse.OptionParser(description='A utility to watch a set of repos \ +for changes and to kick off tests if the repo changes.') +parser.add_option('--dryrun', '--dry-run', action='store_true', + help='Do not actually execute commands, just show what would be done') +(opts, args) = parser.parse_args() # Set the default arch to our placeholder, '%a' repoinfo.setarch('%%a') # two %% because of ConfigParser interpolation @@ -69,7 +75,7 @@ for reponame, arches in testable.items(): harnesscall += ['--parent', repoinfo.get(preponame,'url')] harnesscall.append(repo['url']) - if dryrun: + if opts.dryrun: print ' '.join(harnesscall) continue diff --git a/hooks/post-tree-compose/watch-composes.py b/hooks/post-tree-compose/watch-composes.py index 868cd71..b776033 100755 --- a/hooks/post-tree-compose/watch-composes.py +++ b/hooks/post-tree-compose/watch-composes.py @@ -13,8 +13,14 @@ import os import sys import subprocess from autoqa.repoinfo import repoinfo +import optparse -dryrun = ('--dryrun' in sys.argv) +# Set up the option parser +parser = optparse.OptionParser(description='A utility to watch a set of \ +compose trees for changes and to kick off tests if the compose changes.') +parser.add_option('--dryrun', '--dry-run', action='store_true', + help='Do not actually execute commands, just show what would be done') +(opts, args) = parser.parse_args() # Set the default arch to our placeholder, '%a' repoinfo.setarch('%%a') # two %% because of ConfigParser interpolation @@ -70,7 +76,7 @@ for reponame, arches in testable.items(): for arch in arches: harnesscall += ['--arch', arch] harnesscall.append(repo['url']) - if dryrun: + if opts.dryrun: print ' '.join(harnesscall) continue