Browse Source

Stackbrew alpha

shin- 12 years ago
parent
commit
5ee34c94f3
7 changed files with 254 additions and 0 deletions
  1. 70 0
      stackbrew/README.md
  2. 56 0
      stackbrew/app.py
  3. 87 0
      stackbrew/lib/db.py
  4. 26 0
      stackbrew/lib/periodic.py
  5. 10 0
      stackbrew/lib/utils.py
  6. 3 0
      stackbrew/requirements.txt
  7. 2 0
      stackbrew/wsgi.py

+ 70 - 0
stackbrew/README.md

@@ -0,0 +1,70 @@
+# Stackbrew
+
+Stackbrew is a web-application that performs continuous building of the docker
+standard library using docker-brew.
+
+## Builds
+
+Builds are performed regularly and pushed to the public index.
+
+## API
+
+A small JSON API allows users to check the status of past builds.
+
+### Latest build summary
+
+* `GET /summary` or `GET /status`
+
+    GET /summary
+
+    {
+        "build_date": "2013-10-04 18:08:45.685881", 
+        "id": 16, 
+        "result": true
+    }
+
+### Summary details
+
+* `GET /summary/<summary_id>`
+
+    GET /summary/16
+
+    [
+        {
+            "commit_id": "7362ff5b812f93eceafbdbf5e5959f676f731f80", 
+            "exception": null, 
+            "source_desc": "git://github.com/dotcloud/hipache@C:7362ff5b812f93eceafbdbf5e5959f676f731f80",
+            "image_id": "5d313f0ec5af",
+            "tag": "0.2.4",
+            "summary_id": 16,
+            "id": 1,
+            "repo_name": "hipache"
+        }, {
+            "commit_id": "7362ff5b812f93eceafbdbf5e5959f676f731f80",
+            "exception": null,
+            "source_desc": "git://github.com/dotcloud/hipache@C:7362ff5b812f93eceafbdbf5e5959f676f731f80",
+            "image_id": "5d313f0ec5af",
+            "tag": "latest",
+            "summary_id": 16,
+            "id": 2,
+            "repo_name": "hipache"
+        }, ...
+    ]
+
+### Latest successful build
+
+* `GET /success/<repo_name>?tag=<tag>`
+* `tag` parameter is optional, defaults to `latest`
+
+    GET /success/ubuntu?tag=12.10
+
+    {
+        "commit_id": "abd58c43ceec4d4a21622a1e3d45f676fe912e745d31",
+        "exception": null,
+        "source_desc": "git://github.com/dotcloud/ubuntu-quantal@B:master",
+        "image_id": "d462fecc33e1",
+        "tag": "12.10",
+        "summary_id": 17,
+        "id": 19,
+        "repo_name": "ubuntu"
+    }

+ 56 - 0
stackbrew/app.py

@@ -0,0 +1,56 @@
+import sys
+
+import flask
+
+sys.path.append('./lib')
+sys.path.append('..')
+
+import brew
+import db
+import periodic
+import utils
+
+app = flask.Flask('stackbrew')
+data = db.DbManager(debug=True)
+
+
[email protected]('/')
+def home():
+    return utils.resp(app, 'Hello World')
+
+
[email protected]('/summary')
[email protected]('/status')
+def latest_summary():
+    result = data.latest_status()
+    return utils.resp(app, result)
+
+
[email protected]('/summary/<int:id>')
+def get_summary(id):
+    result = data.get_summary(id)
+    return utils.resp(app, result)
+
+
[email protected]('/success/<repo_name>')
+def latest_success(repo_name):
+    tag = flask.request.args.get('tag', None)
+    result = data.get_latest_successful(repo_name, tag)
+    return utils.resp(app, result)
+
+
[email protected]('/build/force', method=['POST'])
+def build_task():
+    summary = brew.build_library(
+        'https://github.com/shin-/brew.git', namespace='stackbrew',
+        debug=True, prefill=False, logger=app.logger
+    )
+    data.insert_summary(summary)
+
+
+try:
+    periodic.init_task(build_task, 600, logger=app.logger)
+    app.logger.info('Periodic build task initiated.')
+except RuntimeError:
+    app.logger.info('Periodic build task already locked.')
+app.run(debug=True)

+ 87 - 0
stackbrew/lib/db.py

@@ -0,0 +1,87 @@
+import datetime
+
+import sqlalchemy as sql
+
+
+metadata = sql.MetaData()
+summary = sql.Table(
+    'summary', metadata,
+    sql.Column('id', sql.Integer, primary_key=True),
+    sql.Column('result', sql.Boolean),
+    sql.Column('build_date', sql.String)
+)
+
+summary_item = sql.Table(
+    'summary_item', metadata,
+    sql.Column('id', sql.Integer, primary_key=True),
+    sql.Column('repo_name', sql.String),
+    sql.Column('exception', sql.String),
+    sql.Column('commit_id', sql.String),
+    sql.Column('image_id', sql.String),
+    sql.Column('source_desc', sql.String),
+    sql.Column('tag', sql.String),
+    sql.Column('summary_id', None, sql.ForeignKey('summary.id'))
+)
+
+
+class DbManager(object):
+    def __init__(self, db='/opt/stackbrew/data.db', debug=False):
+        self._engine = sql.create_engine('sqlite:///' + db, echo=debug)
+
+    def generate_tables(self):
+        metadata.create_all(self._engine)
+
+    def insert_summary(self, s):
+        c = self._engine.connect()
+        summary_id = None
+        with c.begin():
+            ins = summary.insert().values(
+                result=not s.exit_code(),
+                build_date=str(datetime.datetime.now()))
+            r = c.execute(ins)
+            summary_id = r.inserted_primary_key[0]
+            for item in s.items():
+                ins = summary_item.insert().values(
+                    repo_name=item.repository,
+                    exception=item.exc,
+                    commit_id=item.commit_id,
+                    image_id=item.image_id,
+                    source_desc=item.source,
+                    tag=item.tag,
+                    summary_id=summary_id
+                )
+                c.execute(ins)
+        return summary_id
+
+    def latest_status(self):
+        c = self._engine.connect()
+        s = sql.select([summary]).order_by(summary.c.id.desc()).limit(1)
+        res = c.execute(s)
+        row = res.fetchone()
+        if row is not None:
+            return dict(row)
+        return None
+
+    def get_summary(self, id):
+        c = self._engine.connect()
+        s = sql.select([summary_item]).where(summary_item.c.summary_id == id)
+        res = c.execute(s)
+        return [dict(row) for row in res]
+
+    def get_latest_successful(self, repo, tag=None):
+        c = self._engine.connect()
+        tag = tag or 'latest'
+        s = sql.select([summary_item]).where(
+            summary_item.c.repo_name == repo
+        ).where(
+            summary_item.c.tag == tag
+        ).where(
+            summary_item.c.image_id is not None
+        ).order_by(
+            summary_item.c.summary_id.desc()
+        ).limit(1)
+        res = c.execute(s)
+        row = res.fetchone()
+        if row is not None:
+            return dict(row)
+        return None

+ 26 - 0
stackbrew/lib/periodic.py

@@ -0,0 +1,26 @@
+#import atexit
+import os
+import threading
+
+lockfiles = []
+
+
+def init_task(fn, period, lockfile='/opt/stackbrew/brw.lock', logger=None):
+    def periodic(logger):
+        if logger is not None:
+            logger.info('Periodic task started')
+        threading.Timer(period, periodic, [logger]).start()
+        fn()
+    if os.path.exists(lockfile):
+        raise RuntimeError('Lockfile already present.')
+    open(lockfile, 'w').close()
+    lockfiles.append(lockfile)
+    threading.Timer(0, periodic, [logger]).start()
+
+
+def clear_lockfiles(lockfiles):
+    for lock in lockfiles:
+        os.remove(lock)
+    lockfiles = []
+
+#atexit.register(clear_lockfiles, lockfiles)

+ 10 - 0
stackbrew/lib/utils.py

@@ -0,0 +1,10 @@
+import json
+
+
+def resp(app, data=None, code=200, headers=None):
+    if not headers:
+        headers = {}
+    if 'Content-Type' not in headers:
+        headers['Content-Type'] = 'application/json'
+        data = json.dumps(data)
+    return app.make_response((data, code, headers))

+ 3 - 0
stackbrew/requirements.txt

@@ -0,0 +1,3 @@
+Flask==0.9
+SQLAlchemy==0.8.2
+-r ../requirements.txt

+ 2 - 0
stackbrew/wsgi.py

@@ -0,0 +1,2 @@
+#!/usr/bin/env python
+import app as application