From cd28ae6b1aecb974713c841667bb0b00acfbdf39 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 24 Oct 2013 00:05:58 -0400 Subject: [PATCH 01/57] First stab at a buildserver that will puppet docker to build dockerfiles remotely. --- buildserver/Readme.md | 1 + buildserver/buildserver.py | 115 +++++++++++++++++++++++++++++ buildserver/requirements-nover.txt | 2 + buildserver/test.html | 8 ++ 4 files changed, 126 insertions(+) create mode 100644 buildserver/Readme.md create mode 100644 buildserver/buildserver.py create mode 100644 buildserver/requirements-nover.txt create mode 100644 buildserver/test.html diff --git a/buildserver/Readme.md b/buildserver/Readme.md new file mode 100644 index 000000000..8cc88d12d --- /dev/null +++ b/buildserver/Readme.md @@ -0,0 +1 @@ +This must be run on Python version 2.7.4 or later (Ubuntu 13.04) for security reasons. \ No newline at end of file diff --git a/buildserver/buildserver.py b/buildserver/buildserver.py new file mode 100644 index 000000000..f9eba8416 --- /dev/null +++ b/buildserver/buildserver.py @@ -0,0 +1,115 @@ +import docker +import logging +import shutil +import os +import re + +from flask import Flask, request, send_file, make_response, jsonify +from zipfile import ZipFile +from tempfile import TemporaryFile, mkdtemp + + +BUFFER_SIZE = 8 * 1024 +LOG_FORMAT = '%(asctime)-15s - %(levelname)s - %(pathname)s - ' + \ + '%(funcName)s - %(message)s' + +app = Flask(__name__) +logger = logging.getLogger(__name__) + + +def count_steps(dockerfileobj): + steps = 0 + for line in dockerfileobj.readlines(): + stripped = line.strip() + if stripped and stripped[0] is not '#': + steps += 1 + return steps + +@app.route('/') +def index(): + return send_file('test.html') + + +@app.route('/start/zip/', methods=['POST']) +def start_build(tag_name): + docker_zip = request.files['dockerfile'] + + build_dir = mkdtemp(prefix='docker-build-') + + # Save the zip file to temp somewhere + with TemporaryFile() as zip_file: + docker_zip.save(zip_file) + to_extract = ZipFile(zip_file) + to_extract.extractall(build_dir) + + docker_cl = docker.Client(version='1.5') + + build_status = docker_cl.build(path=build_dir, tag=tag_name) + + dockerfile_path = os.path.join(build_dir, "Dockerfile") + with open(dockerfile_path, 'r') as dockerfileobj: + num_steps = count_steps(dockerfileobj) + logger.debug('Dockerfile had %s steps' % num_steps) + + current_step = 0 + built_image = None + for status in build_status: + step_increment = re.search(r'Step ([0-9]+) :', status) + if step_increment: + current_step = int(step_increment.group(1)) + logger.debug('Step now: %s/%s' % (current_step, num_steps)) + continue + + complete = re.match(r'Successfully built ([a-z0-9]+)', status) + if complete: + built_image = complete.group(1) + logger.debug('Final image ID is: %s' % built_image) + continue + + shutil.rmtree(build_dir) + + # Get the image count + if not built_image: + abort(500) + + history = docker_cl.history(built_image) + num_images = len(history) + + logger.debug('Pushing to tag name: %s' % tag_name) + resp = docker_cl.push(tag_name) + + current_image = 0 + image_progress = 0 + for status in resp: + if u'status' in status: + status_msg = status[u'status'] + + next_image = r'(Pushing:|Image) [a-z0-9]+( already pushed, skipping)?' + match = re.match(next_image, status_msg) + if match: + current_image += 1 + image_progress = 0 + logger.debug('Now pushing image %s/%s' % (current_image, num_images)) + continue + + if status_msg == u'Pushing' and u'progress' in status: + percent = r'\(([0-9]+)%\)' + match = re.search(percent, status[u'progress']) + if match: + image_progress = int(match.group(1)) + continue + + return jsonify({ + 'images_pushed': num_images, + 'commands_run': num_steps, + }) + + +@app.route('/status') +def get_status(): + return 'building' + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + app.run(host='0.0.0.0', port=5002, debug=True) \ No newline at end of file diff --git a/buildserver/requirements-nover.txt b/buildserver/requirements-nover.txt new file mode 100644 index 000000000..7a7ebd121 --- /dev/null +++ b/buildserver/requirements-nover.txt @@ -0,0 +1,2 @@ +flask +-e git+git://github.com/dotcloud/docker-py.git#egg=docker-py \ No newline at end of file diff --git a/buildserver/test.html b/buildserver/test.html new file mode 100644 index 000000000..472f4dd6d --- /dev/null +++ b/buildserver/test.html @@ -0,0 +1,8 @@ + + +
+ + +
+ + \ No newline at end of file From 5d5d027cc0610e6cff0a10461dd30e7cab337dc4 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 24 Oct 2013 01:29:34 -0400 Subject: [PATCH 02/57] Move the source dockerfile processing to a modular system. Move actual build to a thread. Wire up the status API. Fix a bug with the way current_image was computed. Add untested support for raw dockerfile upload. --- buildserver/buildserver.py | 202 ++++++++++++++++++++--------- buildserver/requirements-nover.txt | 2 +- buildserver/test.html | 3 +- 3 files changed, 143 insertions(+), 64 deletions(-) diff --git a/buildserver/buildserver.py b/buildserver/buildserver.py index f9eba8416..6ebdd5c9b 100644 --- a/buildserver/buildserver.py +++ b/buildserver/buildserver.py @@ -4,9 +4,11 @@ import shutil import os import re -from flask import Flask, request, send_file, make_response, jsonify +from flask import Flask, request, send_file, jsonify, redirect, url_for, abort from zipfile import ZipFile from tempfile import TemporaryFile, mkdtemp +from uuid import uuid4 +from multiprocessing.pool import ThreadPool BUFFER_SIZE = 8 * 1024 @@ -17,97 +19,173 @@ app = Flask(__name__) logger = logging.getLogger(__name__) -def count_steps(dockerfileobj): - steps = 0 - for line in dockerfileobj.readlines(): - stripped = line.strip() - if stripped and stripped[0] is not '#': - steps += 1 - return steps - @app.route('/') def index(): return send_file('test.html') -@app.route('/start/zip/', methods=['POST']) -def start_build(tag_name): - docker_zip = request.files['dockerfile'] +def count_steps(dockerfile_path): + with open(dockerfile_path, 'r') as dockerfileobj: + steps = 0 + for line in dockerfileobj.readlines(): + stripped = line.strip() + if stripped and stripped[0] is not '#': + steps += 1 + return steps + +def prepare_zip(request_file): build_dir = mkdtemp(prefix='docker-build-') # Save the zip file to temp somewhere with TemporaryFile() as zip_file: - docker_zip.save(zip_file) + request_file.save(zip_file) to_extract = ZipFile(zip_file) to_extract.extractall(build_dir) - docker_cl = docker.Client(version='1.5') + return build_dir - build_status = docker_cl.build(path=build_dir, tag=tag_name) +def prepare_dockerfile(request_file): + build_dir = mkdtemp(prefix='docker-build-') dockerfile_path = os.path.join(build_dir, "Dockerfile") - with open(dockerfile_path, 'r') as dockerfileobj: - num_steps = count_steps(dockerfileobj) - logger.debug('Dockerfile had %s steps' % num_steps) + request_file.save(dockerfile_path) - current_step = 0 - built_image = None - for status in build_status: - step_increment = re.search(r'Step ([0-9]+) :', status) - if step_increment: - current_step = int(step_increment.group(1)) - logger.debug('Step now: %s/%s' % (current_step, num_steps)) - continue + return build_dir - complete = re.match(r'Successfully built ([a-z0-9]+)', status) - if complete: - built_image = complete.group(1) - logger.debug('Final image ID is: %s' % built_image) - continue - shutil.rmtree(build_dir) +MIME_PROCESSORS = { + 'application/zip': prepare_zip, + 'text/plain': prepare_dockerfile, +} - # Get the image count - if not built_image: - abort(500) - history = docker_cl.history(built_image) - num_images = len(history) +builds = {} +pool = ThreadPool(1) - logger.debug('Pushing to tag name: %s' % tag_name) - resp = docker_cl.push(tag_name) - current_image = 0 - image_progress = 0 - for status in resp: - if u'status' in status: - status_msg = status[u'status'] +def build_image(build_dir, tag_name, num_steps, result_object): + try: + logger.debug('Does this show up?') + docker_cl = docker.Client(version='1.5') + result_object['status'] = 'building' + build_status = docker_cl.build(path=build_dir, tag=tag_name) - next_image = r'(Pushing:|Image) [a-z0-9]+( already pushed, skipping)?' - match = re.match(next_image, status_msg) - if match: - current_image += 1 - image_progress = 0 - logger.debug('Now pushing image %s/%s' % (current_image, num_images)) + current_step = 0 + built_image = None + for status in build_status: + step_increment = re.search(r'Step ([0-9]+) :', status) + if step_increment: + current_step = int(step_increment.group(1)) + logger.debug('Step now: %s/%s' % (current_step, num_steps)) + result_object['current_command'] = current_step continue - if status_msg == u'Pushing' and u'progress' in status: - percent = r'\(([0-9]+)%\)' - match = re.search(percent, status[u'progress']) + complete = re.match(r'Successfully built ([a-z0-9]+)$', status) + if complete: + built_image = complete.group(1) + logger.debug('Final image ID is: %s' % built_image) + continue + + shutil.rmtree(build_dir) + + # Get the image count + if not built_image: + result_object['status'] = 'error' + result_object['message'] = 'Unable to build dockerfile.' + return + + history = docker_cl.history(built_image) + num_images = len(history) + result_object['total_images'] = num_images + + result_object['status'] = 'pushing' + logger.debug('Pushing to tag name: %s' % tag_name) + resp = docker_cl.push(tag_name) + + current_image = 0 + image_progress = 0 + for status in resp: + logger.debug(status) + + if u'status' in status: + status_msg = status[u'status'] + + next_image = r'(Pushing|Image) [a-z0-9]+( already pushed, skipping)?$' + match = re.match(next_image, status_msg) if match: - image_progress = int(match.group(1)) - continue + current_image += 1 + image_progress = 0 + logger.debug('Now pushing image %s/%s' % + (current_image, num_images)) - return jsonify({ - 'images_pushed': num_images, - 'commands_run': num_steps, - }) + elif status_msg == u'Pushing' and u'progress' in status: + percent = r'\(([0-9]+)%\)' + match = re.search(percent, status[u'progress']) + if match: + image_progress = int(match.group(1)) + + result_object['current_image'] = current_image + result_object['image_completion_percent'] = image_progress + + elif u'errorDetail' in status: + result_object['status'] = 'error' + if u'message' in status[u'errorDetail']: + result_object['message'] = status[u'errorDetail'][u'message'] + return + + result_object['status'] = 'complete' + except Exception as e: + logger.exception('Exception when processing request.') + result_object['status'] = 'error' + result_object['message'] = e.message -@app.route('/status') -def get_status(): - return 'building' +@app.route('/build', methods=['POST']) +def start_build(): + docker_input = request.files['dockerfile'] + c_type = docker_input.content_type + tag_name = request.values['tag'] + + logger.info('Request to build file of type: %s with tag: %s' % + (c_type, tag_name)) + + if c_type not in MIME_PROCESSORS: + logger.error('Invalid dockerfile content type: %s' % c_type) + abort(400) + + build_dir = MIME_PROCESSORS[c_type](docker_input) + + dockerfile_path = os.path.join(build_dir, "Dockerfile") + num_steps = count_steps(dockerfile_path) + logger.debug('Dockerfile had %s steps' % num_steps) + + job_id = str(uuid4()) + logger.info('Sending job to builder pool: %s' % job_id) + + result_object = { + 'id': job_id, + 'total_commands': num_steps, + 'total_images': None, + 'current_command': 0, + 'current_image': 0, + 'image_completion_percent': 0, + 'status': 'waiting', + 'message': None, + } + builds[job_id] = result_object + pool.apply_async(build_image, [build_dir, tag_name, num_steps, + result_object]) + + return redirect(url_for('get_status', job_id=job_id)) + + +@app.route('/status/') +def get_status(job_id): + if job_id not in builds: + abort(400) + + return jsonify(builds[job_id]) if __name__ == '__main__': diff --git a/buildserver/requirements-nover.txt b/buildserver/requirements-nover.txt index 7a7ebd121..e5ec39f3c 100644 --- a/buildserver/requirements-nover.txt +++ b/buildserver/requirements-nover.txt @@ -1,2 +1,2 @@ flask --e git+git://github.com/dotcloud/docker-py.git#egg=docker-py \ No newline at end of file +-e git+git://github.com/DevTable/docker-py.git#egg=docker-py diff --git a/buildserver/test.html b/buildserver/test.html index 472f4dd6d..0137b766b 100644 --- a/buildserver/test.html +++ b/buildserver/test.html @@ -1,7 +1,8 @@ -
+ +
From 9dc9e0c940316161b218a532e704125246127d50 Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 24 Oct 2013 14:57:05 -0400 Subject: [PATCH 03/57] Add a docker-in-docker container that will run the build server. --- buildserver/Dockerfile | 27 +++++++++++++++++++++ buildserver/Readme.md | 14 ++++++++++- buildserver/buildserver.py | 35 +++++++++++++++------------ buildserver/startserver | 48 ++++++++++++++++++++++++++++++++++++++ buildserver/test.html | 2 +- 5 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 buildserver/Dockerfile create mode 100644 buildserver/startserver diff --git a/buildserver/Dockerfile b/buildserver/Dockerfile new file mode 100644 index 000000000..c58e9f0ad --- /dev/null +++ b/buildserver/Dockerfile @@ -0,0 +1,27 @@ +FROM lopter/raring-base +MAINTAINER jake@devtable.com + +RUN echo deb http://archive.ubuntu.com/ubuntu precise universe > /etc/apt/sources.list.d/universe.list +RUN apt-get update -qq +RUN apt-get install -qqy iptables ca-certificates lxc python-virtualenv git python-dev + +# This will use the latest public release. To use your own, comment it out... +ADD https://get.docker.io/builds/Linux/x86_64/docker-latest /usr/local/bin/docker +# ...then uncomment the following line, and copy your docker binary to current dir. +#ADD ./docker /usr/local/bin/docker + +# Install the files +ADD ./startserver /usr/local/bin/startserver +ADD ./buildserver.py ./buildserver.py +ADD ./test.html ./test.html +ADD ./requirements-nover.txt ./requirements-nover.txt + +RUN chmod +x /usr/local/bin/docker /usr/local/bin/startserver + +RUN virtualenv --distribute venv +RUN venv/bin/pip install -r requirements-nover.txt + +VOLUME /var/lib/docker + +EXPOSE 5002:5002 +CMD startserver \ No newline at end of file diff --git a/buildserver/Readme.md b/buildserver/Readme.md index 8cc88d12d..6b36df9ee 100644 --- a/buildserver/Readme.md +++ b/buildserver/Readme.md @@ -1 +1,13 @@ -This must be run on Python version 2.7.4 or later (Ubuntu 13.04) for security reasons. \ No newline at end of file +To build: + +``` +sudo docker build -t quay.io/quay/buildserver . +sudo docker push quay.io/quay/buildserver +``` + +To run: + +``` +sudo docker pull quay.io/quay/buildserver +sudo docker run -d -privileged -lxc-conf="aa_profile=unconfined" quay.io/quay/buildserver +``` \ No newline at end of file diff --git a/buildserver/buildserver.py b/buildserver/buildserver.py index 6ebdd5c9b..94bf2dbe2 100644 --- a/buildserver/buildserver.py +++ b/buildserver/buildserver.py @@ -54,16 +54,6 @@ def prepare_dockerfile(request_file): return build_dir -MIME_PROCESSORS = { - 'application/zip': prepare_zip, - 'text/plain': prepare_dockerfile, -} - - -builds = {} -pool = ThreadPool(1) - - def build_image(build_dir, tag_name, num_steps, result_object): try: logger.debug('Does this show up?') @@ -106,11 +96,8 @@ def build_image(build_dir, tag_name, num_steps, result_object): current_image = 0 image_progress = 0 for status in resp: - logger.debug(status) - if u'status' in status: status_msg = status[u'status'] - next_image = r'(Pushing|Image) [a-z0-9]+( already pushed, skipping)?$' match = re.match(next_image, status_msg) if match: @@ -141,7 +128,18 @@ def build_image(build_dir, tag_name, num_steps, result_object): result_object['message'] = e.message -@app.route('/build', methods=['POST']) +MIME_PROCESSORS = { + 'application/zip': prepare_zip, + 'text/plain': prepare_dockerfile, + 'application/octet-stream': prepare_dockerfile, +} + + +builds = {} +pool = ThreadPool(1) + + +@app.route('/build/', methods=['POST']) def start_build(): docker_input = request.files['dockerfile'] c_type = docker_input.content_type @@ -180,7 +178,7 @@ def start_build(): return redirect(url_for('get_status', job_id=job_id)) -@app.route('/status/') +@app.route('/build/') def get_status(job_id): if job_id not in builds: abort(400) @@ -188,6 +186,13 @@ def get_status(job_id): return jsonify(builds[job_id]) +@app.route('/build/', methods=['GET']) +def get_all_status(): + return jsonify({ + 'builds': builds, + }) + + if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) app.run(host='0.0.0.0', port=5002, debug=True) \ No newline at end of file diff --git a/buildserver/startserver b/buildserver/startserver new file mode 100644 index 000000000..4947a618a --- /dev/null +++ b/buildserver/startserver @@ -0,0 +1,48 @@ +#!/bin/bash + +# First, make sure that cgroups are mounted correctly. +CGROUP=/sys/fs/cgroup + +[ -d $CGROUP ] || + mkdir $CGROUP + +mountpoint -q $CGROUP || + mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || { + echo "Could not make a tmpfs mount. Did you use -privileged?" + exit 1 + } + +# Mount the cgroup hierarchies exactly as they are in the parent system. +for SUBSYS in $(cut -d: -f2 /proc/1/cgroup) +do + [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS + mountpoint -q $CGROUP/$SUBSYS || + mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS +done + +# Note: as I write those lines, the LXC userland tools cannot setup +# a "sub-container" properly if the "devices" cgroup is not in its +# own hierarchy. Let's detect this and issue a warning. +grep -q :devices: /proc/1/cgroup || + echo "WARNING: the 'devices' cgroup should be in its own hierarchy." +grep -qw devices /proc/1/cgroup || + echo "WARNING: it looks like the 'devices' cgroup is not mounted." + +# Now, close extraneous file descriptors. +pushd /proc/self/fd +for FD in * +do + case "$FD" in + # Keep stdin/stdout/stderr + [012]) + ;; + # Nuke everything else + *) + eval exec "$FD>&-" + ;; + esac +done +popd + +docker -d & +exec venv/bin/python buildserver.py 2> buildserver.log \ No newline at end of file diff --git a/buildserver/test.html b/buildserver/test.html index 0137b766b..9a08eb994 100644 --- a/buildserver/test.html +++ b/buildserver/test.html @@ -1,6 +1,6 @@ -
+ From 847e91b696956be2a593d5ef9ec9810672a4505f Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 24 Oct 2013 16:37:03 -0400 Subject: [PATCH 04/57] Add the first part of the build worker that runs on quay. --- buildserver/buildserver.py | 2 +- certs/digital_ocean | 27 ++++++++ certs/digital_ocean.pub | 1 + config.py | 13 +++- data/database.py | 9 ++- data/model.py | 12 ++++ data/queue.py | 1 + test/data/test.db | Bin 75776 -> 77824 bytes workers/diffsworker.py | 6 -- workers/dockerfilebuild.py | 136 +++++++++++++++++++++++++++++++++++++ 10 files changed, 196 insertions(+), 11 deletions(-) create mode 100644 certs/digital_ocean create mode 100644 certs/digital_ocean.pub create mode 100644 workers/dockerfilebuild.py diff --git a/buildserver/buildserver.py b/buildserver/buildserver.py index 94bf2dbe2..b6de17da5 100644 --- a/buildserver/buildserver.py +++ b/buildserver/buildserver.py @@ -195,4 +195,4 @@ def get_all_status(): if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) - app.run(host='0.0.0.0', port=5002, debug=True) \ No newline at end of file + app.run(host='0.0.0.0', port=5002, debug=True) diff --git a/certs/digital_ocean b/certs/digital_ocean new file mode 100644 index 000000000..700f2b586 --- /dev/null +++ b/certs/digital_ocean @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwjlIK0HodmDNrZAmaALtr9RLriRSeeLh76gV8KHmjRweeT7v +dmhKeGP1nOAs17caZkcwsW0tiDbCeIv2MisV405sScjPOxFivWpY8tL72sgVuOAl +ReZauOGZ4M1ZcSa/YbT7tnFCIayYE9pde4ih5LmYZqKsBsaNq3ErcMnAzqG77D95 +8swuVwhz/INioBWwe4FjO76/0DqS357hT5yHDWthJD6UUH12VajPKBtXEvGNUtNL +vdq+drm9omt2y0seMn47fZXiNIulLv7ojsWKwtRMTsGcjnv6VMZAVAuX11u4cJd+ +oPTbDl0D+02B7XYcxABqdMZcOc1/7VTUlFFd4wIDAQABAoIBAAs4V+z3z8AW84rV +SwKzOJvxvbV/r6wO6VJ4+Vt/XtxEBZanhhnnCHZP//5iDPUhRMsnza5SSlEWKMHi +BAT97DPHcgYJLb+Rz4x1ulG80oPfDzIw8LZLCm6nycXs1v/sZx3z4J63iER9vgNX +mBLs371g42b6esmhasm+re3EGflV0LeY1IX0MY40pqGndmW8Fly1QH179TrMzVUJ +btu3i2JrwWmKk5zO5YGm0SYY5QQGCdjPj6SL+idDniAefEvbjJYz2qOaPOF3wj/7 +r8dAnmyaP10Q3JojT01Et5ltMfr0oF2/pic9tWYGrgn/aIuoXUXj0SF3Pfgrb/4L +Et1kzFECgYEA8Tb/9bYzQgtaQTQfzFU/KnsIKKnrxh73rZwnIxG59WvN0Ws41Byf +rv8fEbXWU8Yj0drxRSud9fADr99lZGWFxle8rSW5+qqoUxG8n/fkktzHxyPE/9Mh +pZW7un7a5/glKgUpHpjaOCZj9rhdF1AwdUXLSo1sFc7VBsKvKiKJAT0CgYEAziDt +A9h5lOgiLGf1xdBq3qmLIlARz7fivAcZ5acSGN5k6MFFxjHNqhcXRusqs7g+hvCN +eRupdwfgSdLwrTfvxuY4pCcddfYIZO3uUZYs/glvYRtIxaP2kMBkZTs9KzI02Bjv +zT3NPReR/46SqW0zvYTlRFSY7VZ0eRED/5xnjZ8CgYAZdlrSjyceA6DFXUE2CpGe +ZFpaIIW45i/y7ZbcBtUAaR7SymS3T0Yz7M5UykMTmMjTMC9jw9Tqzyk0eXp0fJsA +cuaByIe3RCh8jFTC9iH0tsWH6eizsI/OsN2eNCHbdsBFjUHn7u6qGrNWqeN5wIc8 ++d8ZwY/1RV4LVqWy5u5baQKBgHLFvJMWluQFuPl2zU9etBLU3ma1pKU/I11EqvPH +afk044UCEKLBml1pzAkt6jH1lcM2798OOvbPCOCyNlaMvdLG36TvLqU+3/+qx7bf +4p90i3LLaWK64BBLP9tp9640n13vzJ5AGiY5GI7uSNVTu6p789hvLlOAfwvmII7T +/IjLAoGBAO6iU8i6pAOaKa7+/uExXx6xwk3vqQtovxByo1/m7NpyUtT+ElDSq+t9 +7f+3TzzPB6ggdMl8d+PSyHR3o7KjVPgOSe7zld7eePhUrLjwZ4lh5ohcvhvYfaRL +0EgRTaTb+zLtCAvJS/ilNnJoIcxUmD8u5uSXpY7vAleSOiQTJRTh +-----END RSA PRIVATE KEY----- diff --git a/certs/digital_ocean.pub b/certs/digital_ocean.pub new file mode 100644 index 000000000..95db83601 --- /dev/null +++ b/certs/digital_ocean.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCOUgrQeh2YM2tkCZoAu2v1EuuJFJ54uHvqBXwoeaNHB55Pu92aEp4Y/Wc4CzXtxpmRzCxbS2INsJ4i/YyKxXjTmxJyM87EWK9aljy0vvayBW44CVF5lq44ZngzVlxJr9htPu2cUIhrJgT2l17iKHkuZhmoqwGxo2rcStwycDOobvsP3nyzC5XCHP8g2KgFbB7gWM7vr/QOpLfnuFPnIcNa2EkPpRQfXZVqM8oG1cS8Y1S00u92r52ub2ia3bLSx4yfjt9leI0i6Uu/uiOxYrC1ExOwZyOe/pUxkBUC5fXW7hwl36g9NsOXQP7TYHtdhzEAGp0xlw5zX/tVNSUUV3j jake@coreserver diff --git a/config.py b/config.py index 3ae57769a..71297a00b 100644 --- a/config.py +++ b/config.py @@ -85,8 +85,15 @@ class GitHubProdConfig(GitHubTestConfig): GITHUB_CLIENT_SECRET = 'f89d8bb28ea3bd4e1c68808500d185a816be53b1' +class DigitalOceanConfig(): + DO_CLIENT_ID = 'LJ44y2wwYj1MD0BRxS6qHA' + DO_CLIENT_SECRET = 'b9357a6f6ff45a33bb03f6dbbad135f9' + DO_SSH_KEY_ID = '46986' + + class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB, - StripeTestConfig, MixpanelTestConfig, GitHubTestConfig): + StripeTestConfig, MixpanelTestConfig, GitHubTestConfig, + DigitalOceanConfig): REGISTRY_SERVER = 'localhost:5000' LOGGING_CONFIG = { 'level': logging.DEBUG, @@ -98,7 +105,7 @@ class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB, class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, StripeLiveConfig, MixpanelTestConfig, - GitHubProdConfig): + GitHubProdConfig, DigitalOceanConfig): REGISTRY_SERVER = 'localhost:5000' LOGGING_CONFIG = { 'level': logging.DEBUG, @@ -109,7 +116,7 @@ class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, class ProductionConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, StripeLiveConfig, MixpanelProdConfig, - GitHubProdConfig): + GitHubProdConfig, DigitalOceanConfig): REGISTRY_SERVER = 'quay.io' LOGGING_CONFIG = { 'stream': sys.stderr, diff --git a/data/database.py b/data/database.py index 0f3aef888..38bc2d2b7 100644 --- a/data/database.py +++ b/data/database.py @@ -150,6 +150,13 @@ class RepositoryTag(BaseModel): ) +class RepositoryBuild(BaseModel): + digitalocean_build_node_id = IntegerField(null=True) + phase = CharField(default='waiting') + status_url = CharField(null=True) + repository = ForeignKeyField(Repository) + + class QueueItem(BaseModel): queue_name = CharField(index=True) body = TextField() @@ -162,7 +169,7 @@ def initialize_db(): create_model_tables([User, Repository, Image, AccessToken, Role, RepositoryPermission, Visibility, RepositoryTag, EmailConfirmation, FederatedLogin, LoginService, - QueueItem]) + QueueItem, RepositoryBuild]) Role.create(name='admin') Role.create(name='write') Role.create(name='read') diff --git a/data/model.py b/data/model.py index 15827cca4..182ee2a21 100644 --- a/data/model.py +++ b/data/model.py @@ -30,6 +30,10 @@ class InvalidTokenException(DataModelException): pass +class InvalidRepositoryBuildException(DataModelException): + pass + + def create_user(username, password, email): if not validate_email(email): raise InvalidEmailAddressException('Invalid email address: %s' % email) @@ -548,3 +552,11 @@ def load_token_data(code): return fetched[0] else: raise InvalidTokenException('Invalid delegate token code: %s' % code) + + +def get_repository_build(request_dbid): + try: + return RepositoryBuild.get(RepositoryBuild == request_dbid) + except RepositoryBuild.DoesNotExist: + msg = 'Unable to locate a build by id: %s' % request_dbid + raise InvalidRepositoryBuildException(msg) diff --git a/data/queue.py b/data/queue.py index 0e1383aa2..3038b8389 100644 --- a/data/queue.py +++ b/data/queue.py @@ -56,3 +56,4 @@ class WorkQueue(object): image_diff_queue = WorkQueue('imagediff') +dockerfile_build_queue = WorkQueue('dockerfilebuild') diff --git a/test/data/test.db b/test/data/test.db index 96e878b95e9124c1618e41e662406d431a5af964..83688fab4d7c9acbd3b2a6402aa7b0b0cc274e8b 100644 GIT binary patch delta 925 zcmZ9KT})D87{||Z&Ou!w&1QqL@<@JwfXG*B^dS&Jf>|dJy>NzdG!n%SN~|rhF1oBe z7l~PGdC|v}aB6c|&CRxMnsm{+s;#=MRX5dIt@XmHHQ(+1?fKZA_y4@lGyR;Me#2g> zEZ?Gq3XrQ)c`m1NGuNCaSkg#t$EF42~F`y#sc8;AqR~ zn9(;F+}ZQ-@CZ^m>0ZfH|uIubZ}5Jl#E0!s!9`Xs6+X zNobw)@Vu`#;TuX?Tus5Qv6eyq$O(Qb86OHXb+`IM{-YzEpGVJhwj)|RV>$N;JiN`9Eb=pVLUn!jz?q3fkZeGTIbd}c&E+h=lCnow6 zv51oEw($NE)CcaJYs4Q4YPW7MkyfpPpLp8pfF@p+A&wpW3G9|vBt;RF6 zmH0Sai_5cm*hwOLpdnum+tOC3!Lw;QX#V)6cr9IqpVGRUmr8EVOJ!wb=nSk_6o1S~fDY8- zjZ78jaV66%%`cUVd5~W!uPD8pVUBp(h}~f&a5odUj0EjE0=t^PR!h)UO|UP&se88& z?5QAV-AK@~fuLDJ69SVXqz*7SqV@?RacG%7K*Lw%mCq6=0%i7IDGLWh>C#ggocQ8_ z2lgYk>;WrA2zQH1%Wk$J-*yqzOTjm~kNySI)OV_v%*)B7w(D>(*9yD-_5Lt-fJw1h ze6?l<3;D$^I-$YPnjXx=*@la2YC5FC@0l8CB8^wzVzvzTXE{*gv8)*kg*tb$-DDyl T)`1aK;;RLAMJlq-e+GU7y+<8n delta 697 zcmXxgOH5Ni6adh7XI^cBrKSXJYe7EpD}~a}E2V-;3*}Q9D}vMzQCcVuY^8k4r);Fb zwUOL~u^1La7r3L(g)uIO5TIbJi7s?OqO3G=;m(*4;t+K*b24-9By;D^f8pof^AB|@ z9mjDVw*C*pVwIHzs}IU$EF`D<#|dF=Y@9-cbU>puwgLBqIBW6Dgx!&_f7&eiY+lox z&p6s8v<_VNS_5I%q)V8J3_FJ=n}c?vzs}cV@?)l6gUeQ}%@`W&w%-xMq1h|KOqZ>x zW5!Zj6S7UmBGzj`qu?DDqr<(f_K01)YKk~}$2)skhvL!?>shbDz;On+2j7WEJ}f%5 zQp@}*A(aKFdMu!BL5sH@@27Hkg~UHOl;>o#vSpl~WAhutNgnx%D&9>K%0kQ}i|}?* z2W4nZYtWQB$xd>7mefNL%QZm7zjzMTF6p5d`HQX?xB*tI_{xIQoLBBo$ZZm-&W6Pu`}EjMsqcz zQO~$s%D7a-Xvk;OYZ(`E8TM0*+AM}mMwmb|OwL6!l8WD?GG`U-12i8MKl+&sCsf79 z-{)bOgJt+d`p6Nx^#F*!6j!AJsKT^lCxd19J6!@sJh$tFa_rv~poabGG(O+$VLgDo zGB9D@f(9G+HYNVGoQGCPvA#{92|vE+fQwkT(E$#OFzck38*O~*Ew9L;B*>8<`AqxS zlifr)rI)K6<&aR1j1GooaXo!TIT0F*PL9N)6Y*F;G_z*s;k&e+CiqG`+!4USB0ieP XH{i`32UuB21=yZe;nt3I< Date: Thu, 24 Oct 2013 16:39:25 -0400 Subject: [PATCH 05/57] Revert some fabric changes to the build worker. --- workers/dockerfilebuild.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/workers/dockerfilebuild.py b/workers/dockerfilebuild.py index 60f145c21..35fdb5720 100644 --- a/workers/dockerfilebuild.py +++ b/workers/dockerfilebuild.py @@ -7,7 +7,6 @@ import digitalocean from apscheduler.scheduler import Scheduler from multiprocessing.pool import ThreadPool -from fabric.api import env from data.queue import dockerfile_build_queue from data import model @@ -61,7 +60,6 @@ def babysit_builder(request): repository_build.phase = 'initializing' repository_build.save() - env.host_string = 'root@%s' % droplet.ip_address # tell it to pull and run the buildserver From b4d916bcc55720bbfae4b0c13d6c83baabc7ce2f Mon Sep 17 00:00:00 2001 From: yackob03 Date: Thu, 24 Oct 2013 17:41:37 -0400 Subject: [PATCH 06/57] Update to Font Awesome 4. --- static/css/quay.css | 21 +++++++++---------- static/directives/repo-circle.html | 4 ++-- static/js/controllers.js | 4 ++-- static/js/graphing.js | 10 ++++----- static/lib/browser-chrome.js | 2 +- static/lib/pagedown/Markdown.Editor.js | 26 ++++++++++++------------ static/partials/header.html | 6 +++--- static/partials/image-view.html | 10 ++++----- static/partials/landing.html | 14 ++++++------- static/partials/repo-admin.html | 22 ++++++++++---------- static/partials/repo-list.html | 2 +- static/partials/signin.html | 4 ++-- static/partials/user-admin.html | 6 +++--- static/partials/view-repo.html | 28 +++++++++++++------------- templates/base.html | 2 +- 15 files changed, 80 insertions(+), 81 deletions(-) diff --git a/static/css/quay.css b/static/css/quay.css index fd8e2f109..ecf162013 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -19,8 +19,7 @@ width: 40px; } -.repo-circle .icon-lock { - font-size: 50%; +.repo-circle .fa-lock { position: absolute; bottom: -6px; right: 0px; @@ -31,11 +30,11 @@ text-align: center; height: 20px; line-height: 21px; - font-size: 12px; + font-size: 16px; } -.repo-circle.no-background .icon-lock { - bottom: -4px; +.repo-circle.no-background .fa-lock { + bottom: 2px; right: -6px; color: #444; } @@ -660,7 +659,7 @@ p.editable:hover i { } .repo-listing i { - font-size: 1.5em; + font-size: 2em; color: #999; display: inline-block; margin-right: 6px; @@ -751,15 +750,15 @@ p.editable:hover i { margin-right: 10px; } -.repo .changes-container i.icon-plus-sign-alt { +.repo .changes-container i.fa-plus-square { color: rgb(73, 209, 73); } -.repo .changes-container i.icon-minus-sign-alt { +.repo .changes-container i.fa-minus-square { color: rgb(209, 73, 73); } -.repo .changes-container i.icon-edit-sign { +.repo .changes-container i.fa-pencil-square { color: rgb(73, 100, 209); } @@ -875,11 +874,11 @@ p.editable:hover i { width: 580px; } -.repo-admin .repo-access-state .state-icon i.icon-lock { +.repo-admin .repo-access-state .state-icon i.fa-lock { background: rgb(253, 191, 191); } -.repo-admin .repo-access-state .state-icon i.icon-unlock-alt { +.repo-admin .repo-access-state .state-icon i.fa-unlock-alt { background: rgb(170, 236, 170); } diff --git a/static/directives/repo-circle.html b/static/directives/repo-circle.html index 62986d4bf..8c03accc8 100644 --- a/static/directives/repo-circle.html +++ b/static/directives/repo-circle.html @@ -1,2 +1,2 @@ - - + + diff --git a/static/js/controllers.js b/static/js/controllers.js index f624c10b7..53edd400d 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -85,7 +85,7 @@ function HeaderCtrl($scope, $location, UserService, Restangular) { }, template: function (datum) { template = '
'; - template += '' + template += '' template += '' + datum.repo.namespace +'/' + datum.repo.name + '' if (datum.repo.description) { template += '' + getFirstTextLine(datum.repo.description) + '' @@ -485,7 +485,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { }, template: function (datum) { template = '
'; - template += '' + template += '' template += '' + datum.username + '' template += '
' return template; diff --git a/static/js/graphing.js b/static/js/graphing.js index 1066c0594..c38ee273e 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -1039,17 +1039,17 @@ ImageFileChangeTree.prototype.update_ = function(source) { node.select('.node-icon') .html(function(d) { if (!d.kind) { - var folder = d._children ? 'icon-folder-close' : 'icon-folder-open'; + var folder = d._children ? 'fa fa-folder' : 'fa fa-folder-open'; return ''; } var icon = { - 'added': 'plus-sign-alt', - 'removed': 'minus-sign-alt', - 'changed': 'edit-sign' + 'added': 'plus-square', + 'removed': 'minus-square', + 'changed': 'pencil-square' }; - return ''; + return ''; }); // Transition exiting nodes to the parent's new position. diff --git a/static/lib/browser-chrome.js b/static/lib/browser-chrome.js index deda3b11f..e0a129227 100644 --- a/static/lib/browser-chrome.js +++ b/static/lib/browser-chrome.js @@ -1,5 +1,5 @@ (function(browserchrome, $) { - var htmlTemplate = '
Tab Title
http://google.com/
' + var htmlTemplate = '
Tab Title
http://google.com/
' browserchrome.update = function() { $('[data-screenshot-url]').each(function(index, element) { diff --git a/static/lib/pagedown/Markdown.Editor.js b/static/lib/pagedown/Markdown.Editor.js index 6bf690ba9..30e1a7b79 100755 --- a/static/lib/pagedown/Markdown.Editor.js +++ b/static/lib/pagedown/Markdown.Editor.js @@ -1359,42 +1359,42 @@ } group1 = makeGroup(1); - buttons.bold = makeButton("wmd-bold-button", "Bold - Ctrl+B", "icon-bold", bindCommand("doBold"), group1); - buttons.italic = makeButton("wmd-italic-button", "Italic - Ctrl+I", "icon-italic", bindCommand("doItalic"), group1); + buttons.bold = makeButton("wmd-bold-button", "Bold - Ctrl+B", "fa fa-bold", bindCommand("doBold"), group1); + buttons.italic = makeButton("wmd-italic-button", "Italic - Ctrl+I", "fa fa-italic", bindCommand("doItalic"), group1); group2 = makeGroup(2); /* - buttons.link = makeButton("wmd-link-button", "Link - Ctrl+L", "icon-link", bindCommand(function (chunk, postProcessing) { + buttons.link = makeButton("wmd-link-button", "Link - Ctrl+L", "fa fa-link", bindCommand(function (chunk, postProcessing) { return this.doLinkOrImage(chunk, postProcessing, false); }), group2); */ - buttons.quote = makeButton("wmd-quote-button", "Blockquote - Ctrl+Q", "icon-quote-left", bindCommand("doBlockquote"), group2); - buttons.code = makeButton("wmd-code-button", "Code Sample - Ctrl+K", "icon-code", bindCommand("doCode"), group2); + buttons.quote = makeButton("wmd-quote-button", "Blockquote - Ctrl+Q", "fa fa-quote-left", bindCommand("doBlockquote"), group2); + buttons.code = makeButton("wmd-code-button", "Code Sample - Ctrl+K", "fa fa-code", bindCommand("doCode"), group2); /* - buttons.image = makeButton("wmd-image-button", "Image - Ctrl+G", "icon-picture", bindCommand(function (chunk, postProcessing) { + buttons.image = makeButton("wmd-image-button", "Image - Ctrl+G", "fa fa-picture", bindCommand(function (chunk, postProcessing) { return this.doLinkOrImage(chunk, postProcessing, true); }), group2); */ group3 = makeGroup(3); - buttons.olist = makeButton("wmd-olist-button", "Numbered List - Ctrl+O", "icon-list", bindCommand(function (chunk, postProcessing) { + buttons.olist = makeButton("wmd-olist-button", "Numbered List - Ctrl+O", "fa fa-list", bindCommand(function (chunk, postProcessing) { this.doList(chunk, postProcessing, true); }), group3); - buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List - Ctrl+U", "icon-list-ul", bindCommand(function (chunk, postProcessing) { + buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List - Ctrl+U", "fa fa-list-ul", bindCommand(function (chunk, postProcessing) { this.doList(chunk, postProcessing, false); }), group3); - buttons.heading = makeButton("wmd-heading-button", "Heading - Ctrl+H", "icon-tasks", bindCommand("doHeading"), group3); - buttons.hr = makeButton("wmd-hr-button", "Horizontal Rule - Ctrl+R", "icon-minus", bindCommand("doHorizontalRule"), group3); + buttons.heading = makeButton("wmd-heading-button", "Heading - Ctrl+H", "fa fa-tasks", bindCommand("doHeading"), group3); + buttons.hr = makeButton("wmd-hr-button", "Horizontal Rule - Ctrl+R", "fa fa-minus", bindCommand("doHorizontalRule"), group3); group4 = makeGroup(4); - buttons.undo = makeButton("wmd-undo-button", "Undo - Ctrl+Z", "icon-undo", null, group4); + buttons.undo = makeButton("wmd-undo-button", "Undo - Ctrl+Z", "fa fa-undo", null, group4); buttons.undo.execute = function (manager) { if (manager) manager.undo(); }; var redoTitle = /win/.test(nav.platform.toLowerCase()) ? "Redo - Ctrl+Y" : "Redo - Ctrl+Shift+Z"; // mac and other non-Windows platforms - buttons.redo = makeButton("wmd-redo-button", redoTitle, "icon-share-alt", null, group4); + buttons.redo = makeButton("wmd-redo-button", redoTitle, "fa fa-share", null, group4); buttons.redo.execute = function (manager) { if (manager) manager.redo(); }; if (helpOptions) { @@ -1402,7 +1402,7 @@ group5.className = group5.className + " pull-right"; var helpButton = document.createElement("button"); var helpButtonImage = document.createElement("i"); - helpButtonImage.className = "icon-question-sign"; + helpButtonImage.className = "fa fa-question-sign"; helpButton.appendChild(helpButtonImage); helpButton.className = "btn"; helpButton.id = "wmd-help-button" + postfix; diff --git a/static/partials/header.html b/static/partials/header.html index 321e08612..2590c0a4b 100644 --- a/static/partials/header.html +++ b/static/partials/header.html @@ -2,9 +2,9 @@
- +
- +

- + {{repo.namespace}} / {{repo.name}} @@ -31,7 +31,7 @@
- +

@@ -75,7 +75,7 @@ No matching changes
- + {{folder}}/{{getFilename(change.file)}} diff --git a/static/partials/landing.html b/static/partials/landing.html index 890566750..f425552e5 100644 --- a/static/partials/landing.html +++ b/static/partials/landing.html @@ -10,7 +10,7 @@
- +

Your Top Repositories

@@ -44,10 +44,10 @@ @@ -68,7 +68,7 @@
- + Secure Store your private docker containers where only you and your team @@ -77,7 +77,7 @@
- + Shareable Have to share a repository? No problem! Share with anyone you choose @@ -85,7 +85,7 @@
- + Cloud Hosted Accessible from anywhere, anytime @@ -97,7 +97,7 @@
-
+
Take a tour of Quay
diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index b7ebbce1e..f8c2a0428 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -1,5 +1,5 @@
- +
@@ -8,7 +8,7 @@
- +

{{repo.namespace}} / {{repo.name}}

@@ -18,7 +18,7 @@
User Access Permissions - +
@@ -33,7 +33,7 @@ - + {{username}} @@ -46,7 +46,7 @@ - + @@ -64,7 +64,7 @@
Access Token Permissions - +
@@ -79,7 +79,7 @@ - + {{ token.friendlyName }} @@ -91,7 +91,7 @@ - + @@ -114,7 +114,7 @@
Repository Settings
-
+
This repository is currently private. Only users on the above access list may view and interact with it. @@ -124,7 +124,7 @@
-
+
This repository is currently public and is visible to all users, and may be pulled by all users. @@ -171,7 +171,7 @@