From 94479b345119d18eac4fb8966bc1f515de3fca3a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 4 Apr 2012 02:47:23 -0400 Subject: [PATCH] hmmm --- Procfile | 2 + Readme.rst | 25 ++++++ manage.py | 31 ++++++++ patchmaster/core.py | 181 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 7 ++ 5 files changed, 246 insertions(+) create mode 100644 Procfile create mode 100755 manage.py create mode 100644 patchmaster/core.py diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..9736f4a --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: gunicorn springcreek:app -b 0.0.0.0:$PORT -k gevent -w 4 +celeryd: ./manage.py celeryd --events --loglevel info \ No newline at end of file diff --git a/Readme.rst b/Readme.rst index f5b4c0a..36c95f2 100644 --- a/Readme.rst +++ b/Readme.rst @@ -13,6 +13,31 @@ Plans URI Schema:: /user + username + email + bio + website + location + date_joined /users/ /users/:user/packs/ + name + .. vanity_name? + description + patches /users/:user/patches/ + name + .. vanity_name? + description + distribution + device + /distributions/:id + hash + file_name + /devices/:slug + slug + vanity_name + make + model + approved + diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..d71071b --- /dev/null +++ b/manage.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from flaskext.script import Manager +from flask.ext.celery import install_commands as install_celery + +from springcreek import app +from springcreek.core import db, heroku + + +manager = Manager(app) +install_celery(manager) + +@manager.command +def syncdb(): + """Initializes the database.""" + db.create_all() + +@manager.command +def destroy_all_software(): + + for i, app in enumerate(heroku.apps): + + if i != 0: + app.destroy() + print 'destroyed {0}'.format(app.name) + + + +if __name__ == "__main__": + manager.run() \ No newline at end of file diff --git a/patchmaster/core.py b/patchmaster/core.py new file mode 100644 index 0000000..42ae5ba --- /dev/null +++ b/patchmaster/core.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +""" +springcreek.core +~~~~~~~~~~~~~~~~ + +This module contains the main application of SpringCreek. +""" + +import os +import tempfile +from datetime import datetime, timedelta + +import heroku +import envoy +from flask import Flask, render_template, request, redirect, url_for, Response +from flask.views import MethodView + +from flask_debugtoolbar import DebugToolbarExtension +from flask_heroku import Heroku +from flask_googlefed import GoogleAuth +from raven.contrib.flask import Sentry +from flask.ext.celery import Celery +from sqlalchemy import desc + +from .models import db, BuildRequest, BuildResult +from .hacks import ContextTask, setup_ssh_keys, gen_lines + + +app = Flask(__name__) + +# Don't ask. +setup_ssh_keys() + +app.secret_key = 'some-secret-key' + +# Use gevent workers for celery. +app.config['CELERYD_POOL'] = 'eventlet' +app.config['CELERYD_CONCURRENCY'] = 1000 + + +# Heroku API Key. +app.config['HEROKU_API_KEY'] = os.environ.get('HEROKU_API_KEY') +app.config['DEBUG_TB_ENABLED'] = True +app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False + +# Bootstrap Heroku environment variables. +heroku_env = Heroku(app) + +# Intialize databse configuration. +db.init_app(app) + +sentry = Sentry(app) +auth = GoogleAuth(app) +toolbar = DebugToolbarExtension(app) +celery = Celery(app) + +heroku = heroku.from_key(app.config['HEROKU_API_KEY'] ) + + +@celery.task(base=ContextTask) +def build_task(request_id, tail=False): + + request = BuildRequest.query.filter_by(id=request_id).first() + result = request.result + + app.logger.info('Starting build for {0}.'.format(request)) + + # Replace # w/ @ + _split_url = request.application_url.split('#') + clone_url = _split_url[0] + clone_ref = _split_url[1] if len(_split_url) > 1 else 'HEAD' + + + h_app = heroku.apps.add(stack='cedar') + h_app.config['BUILDPACK_URL'] = request.buildpack_url + result.heroku_app = h_app.name + result.save() + + app.logger.info('Heroku app {0} created for {1}.'.format(h_app, request)) + + pwd = tempfile.mkdtemp(prefix='springcreek') + app.logger.info('Temp dir {0} created for {1}.'.format(pwd, request)) + os.chdir(pwd) + + app.logger.info('Cloning {0} created for {1}.'.format(clone_url, request)) + envoy.run('git clone {0} repo'.format(clone_url)) + os.chdir('repo') + + app.logger.info('Pushing app to {0} for {1}.'.format(h_app, request)) + + git_push = r'git push {result.git_url} {ref}:master'.format(result=result, ref=clone_ref) + app.logger.info(git_push) + + if tail: + c = envoy.connect(git_push) + return c + + c = envoy.run(git_push) + app.logger.info('Push exit code: {c.status_code}'.format(c=c)) + + result.success = (c.status_code == 0) + result.install_log = c.std_err + result.active = True + result.save() + + if not request.keep: + later = datetime.now() + timedelta(hours=2) + destroy_app.apply_async(args=[request_id], eta=later) + + +@celery.task(base=ContextTask) +def destroy_app(request_id): + + request = BuildRequest.query.filter_by(id=request_id).first() + result = request.result + + app.logger.info('Destroying app {0}.'.format(result.heroku_app)) + + # Destroy the app. + h_app = heroku.apps[result.heroku_app] + h_app.destroy() + + result.active = False + result.save() + + + +@app.route('/') +@auth.required +def landing_page(): + return render_template('index.html') + +class Builds(MethodView): + + @auth.required + def get(self): + builds = BuildRequest.query.order_by(desc(BuildRequest.created)).all() + return render_template('builds.html', builds=builds) + + @auth.required + def post(self): + """Create a new BuildRequest.""" + + r = BuildRequest( + buildpack_url=request.form.get('buildpack_url'), + application_url=request.form.get('application_url'), + keep=('keep' in request.form) + ) + db.session.add(r) + db.session.commit() + + if request.args.get('tail'): + c = build_task(r.id, tail=True) + return Response(gen_lines(c)) + + else: + + # Send the build task off to work. + build_task.delay(r.id, tail=False) + return redirect(r.url) + +app.add_url_rule('/builds', view_func=Builds.as_view('builds')) + +@app.route('/builds/') +@auth.required +def view_build(id): + b_request = BuildRequest.query.filter_by(id=id).first() + + context = dict( + request=b_request + ) + + if b_request: + return render_template('build.html', **context) + else: + return redirect(url_for('builds')) + + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index afdf66c..1afac44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ Flask==0.8 Flask-Principal==0.2 Flask-Script==0.3.3 Jinja2==2.6 +Logbook==0.3 Werkzeug==0.8.3 argparse==1.2.1 blinker==1.2 @@ -11,7 +12,13 @@ flask-heroku==0.1.2 gevent==0.13.6 greenlet==0.3.4 gunicorn==0.14.2 +procname==0.3 +python-dateutil==1.5 +pytz==2012b raven==1.5.0 +redis==2.4.11 +rq==0.1.0 setproctitle==1.1.5 simplejson==2.5.0 +times==0.4 wsgiref==0.1.2