summaryrefslogtreecommitdiff
path: root/webapp.py
diff options
context:
space:
mode:
Diffstat (limited to 'webapp.py')
-rw-r--r--webapp.py155
1 files changed, 155 insertions, 0 deletions
diff --git a/webapp.py b/webapp.py
new file mode 100644
index 0000000..cfdf434
--- /dev/null
+++ b/webapp.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python3
+
+import datetime
+import lzma
+
+import apt_pkg
+apt_pkg.init()
+version_compare = apt_pkg.version_compare
+
+import flask
+import flask_sqlalchemy
+import jinja2
+import sqlalchemy
+import werkzeug
+
+app = flask.Flask("crossqa")
+app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///db'
+db = flask_sqlalchemy.SQLAlchemy(app)
+
+src_template = """<!DOCTYPE html>
+<html>
+ <head>
+ </head>
+ <body>
+ <title>{{ sourcepackage|e }} - Debian cross build</title>
+ <h1>{{ sourcepackage|e }}</h1>
+ <h3>Cross satisfiability</h3>
+ <table>
+ <tr>
+ <th>state</th>
+ <th>architectures</th>
+ </tr>
+ {%- set okarchs = depresult.pop(None, None) -%}
+ {%- for reason, archs in depresult.items()|sort -%}
+ <tr>
+ <td>{{ reason|e }}</td>
+ <td>{{ archs|arch_format }}</td>
+ </tr>
+ {%- endfor -%}
+ {%- if okarchs -%}
+ <tr>
+ <td>ok</td>
+ <td>{{ okarchs|arch_format }}</td>
+ </tr>
+ {%- endif -%}
+ </table>
+ <h5>See also</h5>
+ <ul>
+ <li>
+ <a href="https://bootstrap.debian.net/cross_all/{{ sourcepackage|e }}.html">bootstrap.debian.net</a>
+ </li>
+ <li>
+ <a href="https://qa.debian.org/dose/debcheck/cross_unstable_main_amd64/">debcheck</a>
+ </li>
+ </ul>
+ {%- if builds -%}
+ <h3>Cross builds</h3>
+ <table>
+ <tr>
+ <th>started</th>
+ <th>version</th>
+ <th>architecture</th>
+ <th>result</th>
+ </tr>
+ {%- for build in builds|sort(attribute='starttime', reverse=true) -%}
+ <tr>
+ <td>
+ <span title="{{ build.starttime|sqltimestamp }}">
+ {{- build.starttime|sqltimestamp|formatts -}}
+ </span>
+ </td>
+ <td>{{ build.version|e }}</td>
+ <td>{{ build.architecture|e }}</td>
+ <td>
+ <a href="{{ url_for("show_log", filename=build.filename[:-3]) }}">
+ {{- "ok" if build.success else "failed" -}}
+ </a>
+ <a href="{{ url_for("show_log", filename=build.filename) }}">xz</a>
+ </td>
+ </tr>
+ {%- endfor -%}
+ </table>
+ {%- endif -%}
+ </body>
+</html>
+"""
+
+@app.template_filter("sqltimestamp")
+def sqltimestamp_filter(s):
+ return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f")
+
+@app.template_filter("formatts")
+def formatts_filter(ts):
+ assert isinstance(ts, datetime.datetime)
+ dt = datetime.datetime.utcnow() - ts
+ if dt < datetime.timedelta(seconds=1):
+ return "now"
+ if dt < datetime.timedelta(seconds=100):
+ return "%d s" % dt.seconds
+ if dt < datetime.timedelta(minutes=100):
+ return "%d m" % (dt.seconds // 60)
+ if dt < datetime.timedelta(days=1):
+ return "%d h" % (dt.seconds // (60 * 60))
+ return "%d d" % dt.days
+
+@app.template_filter('arch_format')
+@jinja2.contextfilter
+def arch_format_filter(context, some_archs):
+ if context["architectures"] == some_archs:
+ return "any"
+ if len(some_archs) > 3:
+ return jinja2.Markup('<span title="%s">%d architectures</span>' %
+ (", ".join(sorted(some_archs)), len(some_archs)))
+ return ", ".join(sorted(some_archs))
+
+def collect_depstate(conn, source):
+ version = None
+ depstate = None
+ query = sqlalchemy.text("""
+ SELECT version, architecture, satisfiable, reason
+ FROM depstate WHERE source = :source;""")
+ for row in conn.execute(query, source=source):
+ if version is None or version_compare(version, row.version) > 0:
+ version = row.version
+ depstate = {}
+ depstate[row.architecture] = None if row.satisfiable else row.reason
+ if version is None:
+ raise werkzeug.exceptions.NotFound()
+ depresult = {}
+ for arch, reason in depstate.items():
+ depresult.setdefault(reason, set()).add(arch)
+ return version, depresult
+
+@app.route("/src/<source>")
+def show_source(source):
+ context = dict(sourcepackage=source)
+ with db.engine.connect() as conn:
+ query = sqlalchemy.text("SELECT architecture FROM depcheck;")
+ context["architectures"] = set(row[0] for row in conn.execute(query))
+ context["version"], context["depresult"] = collect_depstate(conn,
+ source)
+ query = sqlalchemy.text("""
+ SELECT version, architecture, success, starttime, filename
+ FROM builds WHERE source = :source;""")
+ context["builds"] = list(conn.execute(query, source=source))
+ return flask.render_template_string(src_template, **context)
+
+@app.route("/build/<path:filename>")
+def show_log(filename):
+ if filename.endswith(".xz"):
+ return flask.send_from_directory("logs", filename,
+ mimetype="application/octet-stream")
+ filename += ".xz"
+ return flask.send_file(lzma.open(flask.safe_join("logs", filename), "rb"),
+ mimetype="text/plain")