allow scheduling builds via webapp
authorHelmut Grohne <helmut@subdivi.de>
Sat, 23 Mar 2019 09:06:16 +0000 (10:06 +0100)
committerHelmut Grohne <helmut@subdivi.de>
Sat, 23 Mar 2019 09:06:16 +0000 (10:06 +0100)
build.py
schema.sql
webapp.py

index 876e21b..8e1d763 100755 (executable)
--- a/build.py
+++ b/build.py
@@ -45,20 +45,42 @@ def main():
     args = argp.parse_args()
     db = sqlite3.connect("db", detect_types=sqlite3.PARSE_DECLTYPES)
     with contextlib.closing(db.cursor()) as cur:
-        cur.execute("SELECT source, version, depstate.architecture FROM depstate JOIN depcheck ON depstate.architecture = depcheck.architecture WHERE satisfiable = 1 AND giveback = 0 ORDER BY random() LIMIT 1;")
+        cur.execute("""
+            SELECT d.source, d.version, d.architecture, r.rowid
+                FROM depstate AS d
+                    JOIN buildrequests AS r
+                        ON d.architecture = ifnull(r.architecture, d.architecture)
+                            AND d.source = r.source
+                    JOIN depcheck
+                        ON d.architecture = depcheck.architecture
+                WHERE d.satisfiable = 1 AND depcheck.giveback = 0
+                ORDER BY r.priority DESC, random() LIMIT 1;""")
         row = cur.fetchone()
+        if not row:
+            cur.execute("""
+                SELECT source, version, depstate.architecture, NULL
+                    FROM depstate JOIN depcheck
+                        ON depstate.architecture = depcheck.architecture
+                    WHERE satisfiable = 1 AND giveback = 0
+                    ORDER BY random() LIMIT 1;""")
+            row = cur.fetchone()
     if not row:
         print("no package satisfiable")
         time.sleep(60)
         return
-    source, version, architecture = row
-    print("building %s_%s for %s" % (source, version, architecture))
+    source, version, architecture, requestid = row
+    print("building %s_%s for %s%s" %
+          (source, version, architecture,
+           "" if requestid is None else " (request %d)" % requestid))
     timestamp, success, filename, giveback = \
         do_build(source, version, architecture, args.server)
     with contextlib.closing(db.cursor()) as cur:
         cur.execute("INSERT INTO builds (source, version, architecture, success, starttime, filename) VALUES (?, ?, ?, ?, ?, ?);",
                     (source, version, architecture, success, timestamp,
                      filename))
+        if requestid is not None:
+            cur.execute("DELETE FROM buildrequests WHERE rowid = ?;",
+                        (requestid,))
         if giveback:
             cur.execute("UPDATE depcheck SET giveback = 1 WHERE architecture = ?;",
                         (architecture,))
index e76dde0..62ce9c4 100644 (file)
@@ -30,3 +30,9 @@ CREATE TABLE builds (
        success BOOLEAN NOT NULL CHECK (success in (0, 1)),
        starttime TIMESTAMP NOT NULL,
        filename TEXT NOT NULL);
+
+CREATE TABLE buildrequests (
+       source TEXT NOT NULL,
+       architecture TEXT,
+       requesttime TIMESTAMP NOT NULL,
+        priority INTEGER NOT NULL DEFAULT 0);
index bf82bda..d37dbda 100644 (file)
--- a/webapp.py
+++ b/webapp.py
@@ -104,9 +104,9 @@ footer {
     </li>
    </ul>
   </section>
-  {%- if builds -%}
-   <section>
-    <h3>Cross builds</h3>
+  <section>
+   <h3>Cross builds</h3>
+   {%- if builds -%}
     <table>
      <thead>
       <tr>
@@ -134,8 +134,21 @@ footer {
       {%- endfor -%}
      </tbody>
     </table>
-   </section>
-  {%- endif -%}
+   {%- else -%}
+    <p>No build performed yet.</p>
+   {%- endif -%}
+   <form method="POST" action="{{ url_for("request_schedule")|e }}">
+    <input type="submit" name="schedule" value="cross build" />
+    {{ sourcepackage|e }} for
+    <input type="hidden" name="source" value="{{ sourcepackage|e }}" />
+    <select name="architecture">
+     <option value="any">any</option>
+     {%- for architecture in architectures -%}
+      <option value="{{ architecture|e }}">{{ architecture|e }}</option>
+     {%- endfor -%}
+    </select>
+   </form>
+  </section>
   <footer>
    <h3>Details about this service</h3>
    <ul>
@@ -147,6 +160,18 @@ footer {
 </html>
 """
 
+schedule_template = """<!DOCTYPE html>
+<html>
+ <body>
+  <p>Scheduled a build of {{ request.form["source"]|e }}
+   {%- if request.form["architecture"] != "any" %}
+    for {{ request.form["architecture"]|e -}}
+   {%- endif %}.
+  <p>
+ </body>
+</html>
+"""
+
 @app.template_filter("sqltimestamp")
 def sqltimestamp_filter(s):
     strptime = datetime.datetime.strptime
@@ -228,3 +253,26 @@ def show_log(filename):
                                mimetype="text/plain")
     except FileNotFoundError:
         raise werkzeug.exceptions.NotFound()
+
+
+@app.route("/schedule", methods=["POST"])
+def request_schedule():
+    source = flask.request.form["source"]
+    architecture = flask.request.form["architecture"]
+    with db.engine.connect() as conn:
+        query = sqlalchemy.text("""
+            SELECT 1 FROM depstate WHERE source = :source;""")
+        if not conn.execute(query, source=source).first():
+            raise werkzeug.exceptions.BadRequest()
+        if architecture == "any":
+            architecture = None
+        else:
+            query = sqlalchemy.text("""
+                SELECT 1 FROM depcheck WHERE architecture = :architecture;""")
+            if not conn.execute(query, architecture=architecture).first():
+                raise werkzeug.exceptions.BadRequest()
+        query = sqlalchemy.text("""
+            INSERT INTO buildrequests (source, architecture, requesttime)
+                VALUES (:source, :architecture, datetime('now'));""")
+        conn.execute(query, source=source, architecture=architecture)
+    return flask.render_template_string(schedule_template)