[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[PATCH] a new cgi script



Hi,

I'd like to add a new cgi script into udd.
The goal of this script is to display FTBFS packages on a given architecture.
For every package, it gives the name of the package, its version, the time since the latest build and links to the bts, the tracker and the logs of the latest build. It also shows the related bugs (bug id + bug title) if any.

Let me know if I'm doing wrong.

Thanks in advance,
Erwan.

From 9cf994e38331f895fae15772225925e25d304391 Mon Sep 17 00:00:00 2001
From: Erwan Prioul <erwan@linux.vnet.ibm.com>
Date: Wed, 15 Feb 2017 15:43:32 +0100
Subject: [PATCH] add first version of ftbfs.cgi

script that displays the FTBFS packages on a given architecture, with related bugs if any.
---
 web/cgi-bin/ftbfs.cgi | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 247 insertions(+)
 create mode 100755 web/cgi-bin/ftbfs.cgi

diff --git a/web/cgi-bin/ftbfs.cgi b/web/cgi-bin/ftbfs.cgi
new file mode 100755
index 0000000..b340e59
--- /dev/null
+++ b/web/cgi-bin/ftbfs.cgi
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+# Display FTBFS packages on given arch
+# Copyright (C) 2017, Erwan Prioul <erwan@linux.vnet.ibm.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import psycopg2
+import cgi
+import cgitb
+
+DATABASE = {
+    'database': 'udd',
+    'port': 5452,
+    'host': 'localhost',
+    'user': 'guest'
+}
+
+class AttrDict(dict):
+    def __init__(self, **kwargs):
+        for key, value in kwargs.iteritems():
+            self[key] = value
+
+    def __getattr__(self, name):
+        try:
+            return self[name]
+        except KeyError, e:
+            raise AttributeError(e)
+
+def query(query, cols, *parameters):
+    try:
+        conn = psycopg2.connect(**DATABASE)
+        cursor = conn.cursor()
+        cursor.execute(query, parameters)
+    except:
+        exit(1)
+    for row in cursor.fetchall():
+        yield AttrDict(**dict(zip(cols, row)))
+    cursor.close()
+    conn.close()
+
+def pretty_time_delta(when):
+    seconds = (datetime.datetime.now() - when).total_seconds()
+    days, seconds = divmod(seconds, 86400)
+    hours, seconds = divmod(seconds, 3600)
+    minutes, seconds = divmod(seconds, 60)
+    if days > 0:
+        return '%dd' % (days)
+    elif hours > 0:
+        return '%dh' % (hours)
+    elif minutes > 0:
+        return '%dm' % (minutes)
+    else:
+        return '%ds' % (seconds)
+
+def packageLine(packages, package, pending = False):
+    u = package.replace('+', '%2B')
+    bugs = "&nbsp;"
+    what = 'nopatch'
+    if pending:
+        what = 'patch'
+    if len(packages[package][what]) > 0:
+        bugs = ''.join('<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d";>#%d</a> - %s<br>' % (x[0], x[0], x[1]) for x in sorted(packages[package][what]))
+    return \
+        '<tr><td class="package">%s_%s</td>' % (package, packages[package]['version']) + \
+        '<td class="links">' + \
+        '<a href="https://buildd.debian.org/status/package.php?p=%s&suite=sid"; title="Debian buildd status">%s</a> ' % (u, pretty_time_delta(packages[package]['state_change'])) + \
+        '[<a href="http://bugs.debian.org/cgi-bin/pkgreport.cgi?src=%s"; title="Debian bugs in source package">B</a>]' % (u) + \
+        '[<a href="http://buildd.debian.org/status/logs.php?pkg=%s"; title="Debian build logs">L</a>]' % (u) + \
+        '[<a href="https://tracker.debian.org/pkg/%s"; title="Debian Package Tracker">T</a>]</td>' % (u) + \
+        '<td>%s</td></tr>' % bugs
+
+def generatePending(packages, nb, arch):
+    return \
+        '<div class="claim" style="margin-top:20px;">%d FTBFS packages on <span class="arch">%s</span> with a patch pending</div><table>' % (nb, arch) + \
+        ''.join(packageLine(packages, x, True) for x in sorted(packages.keys()) if len(packages[x]['patch']) > 0) + \
+        '</table>'
+
+def generateFailing(packages, failing, nb, arch):
+    html = '<div class="claim">%d FTBFS packages on <span class="arch">%s</span> without patch</div>' % (nb, arch)
+    for c in sorted(failing.keys()):
+        if c > 2:
+	    txt = "and %d other architectures" % (c - 1)
+        elif c == 2:
+            txt = "and 1 other architecture"
+        else:
+            txt = ""
+        html += \
+            '<div class="banner">%d packages are failing on <span class="arch">%s</span> %s</div><table>' % (len(failing[c]), arch, txt) + \
+            ''.join(packageLine(packages, x) for x in sorted(failing[c])) + \
+            '</table>'
+    return html
+
+def getPackages(arch):
+    # Get all FTBFS packages on given architecture
+    q = """
+select distinct source, version, state_change
+from wannabuild
+where architecture = '%s' and
+      distribution = 'sid' and
+      state in ('Failed', 'Build-Attempted') and
+      vancouvered = 'f'
+;
+""" % arch
+    packages = {x['s']: { 'version': x['version'], 'state_change': x['state_change'], 'patch': [], 'nopatch': [] } for x in query(q, ('s', 'version', 'state_change'))}
+    # Get opened bugs with the given architecture as tag
+    q = """
+select distinct source, id, title
+from bugs inner join bugs_usertags using (id)
+where bugs_usertags.tag = '%s' and status != 'done' and source in (%s);
+""" % (arch, ", ".join("'"+x+"'" for x in packages.keys()))
+    bugs = {x['id']: [x['source'], x['title']] for x in query(q, ('source', 'id', 'title'))}
+    # Which bugs have a patch?
+    q = """
+select id
+from bugs_tags
+where tag = 'patch' and id in (%s);
+""" % ", ".join("'"+str(x)+"'" for x in bugs.keys())
+    patched = [x['i'] for x in query(q, ('id'))]
+    for b in bugs.keys():
+        where = 'nopatch'
+        if b in patched:
+            where = 'patch'
+        packages[bugs[b][0]][where].append([b, bugs[b][1]])
+    l = [x for x in packages.keys() if len(packages[x]['patch']) <= 0]
+    countFailing = len(l)
+    countPending = len(packages.keys()) - countFailing
+    # For a FTBFS package, get the number of architectures it also fails on
+    q = """
+select source, count(*) as c
+from wannabuild
+where distribution = 'sid' and state in ('Failed', 'Build-Attempted') and vancouvered = 'f' and source in (%s)
+group by source;
+""" % ", ".join("'"+x+"'" for x in l)
+    failing = {}
+    for r in query(q, ('source', 'c')):
+        if not failing.has_key(r['c']):
+            failing[r['c']] = []
+        failing[r['c']].append(r['source'])
+    return """
+<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>FTBFS packages on %s</title>
+<style>
+table {
+    width: 100%%;
+    border-collapse: collapse;
+    table-layout: fixed;
+}
+tr:nth-child(2n+1) {
+    background-color: #fbfbfb;
+}
+td {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis !important;
+    vertical-align: top;
+    padding-left: 4px;
+}
+.package {
+    width: 375px;
+}
+.links {
+    width: 150px;
+    text-align: right;
+}
+.claim {
+    background-color: #dddddd;
+    padding: 10px;
+    padding-left: 4px !important;
+    font-size: 110%%;
+}
+.banner {
+    background-color: #eeeeee;
+    padding: 4px;
+}
+.arch {
+    font-style: oblique;
+}
+</style>
+</head>
+<body>
+""" % arch + generateFailing(packages, failing, countFailing, arch) + generatePending(packages, countPending, arch) + '</body></html>'
+
+def getArchs():
+    q = """
+select distinct(architecture) as a
+from wannabuild
+where distribution = 'sid' and
+      state in ('Failed', 'Build-Attempted') and
+      vancouvered = 'f'
+order by a;
+"""
+    return [x['a'] for x in query(q, ('a'))]
+
+def getForm(archs):
+    return """
+<!doctype html>
+<html>
+<head>
+<title>FTBFS packages on given architecture</title>
+<style>
+.claim {
+    background-color: #dddddd;
+    padding: 10px;
+    padding-left: 4px !important;
+    font-size: 110%%;
+    margin-bottom: 5px;
+}
+</style>
+</head>
+<body>
+<div class="claim">FTBFS packages</div>
+<form action="?" accept-charset="UTF-8">
+Architecture: <select name="arch" id="arch"><option value=""></option>%s</select> <input type="submit" value="Show packages">
+</form>
+</body>
+</html>
+""" % ''.join('<option value="%s">%s</option>' % (x, x) for x in archs)
+
+def main():
+    print 'Content-Type: text/html\n'
+    archs = getArchs()
+    cgitb.enable()
+    form = cgi.FieldStorage()
+    if 'arch' in form:
+        arch = form.getfirst('arch', '')
+        if arch in archs:
+            print getPackages(arch)
+        else:
+            print getForm(archs)
+    else:
+        print getForm(archs)
+
+if __name__ == '__main__':
+    main()
-- 
2.7.4


Reply to: