From aa7eb895c1ec62c696cc9fe21256dc95687b256a Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 9 May 2017 01:37:19 -0700 Subject: [PATCH] Add basic builder code - Takes in config via traitlets - Pushes image after it is built - Can run different detectors easily as well - Single straight up application that runs from start to end, no concurrency (woot!) --- builder/app.py | 89 ++++++++++++++++++++++++++++++++++++++++++++ builder/detectors.py | 51 +++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 builder/app.py create mode 100644 builder/detectors.py diff --git a/builder/app.py b/builder/app.py new file mode 100644 index 00000000..2ef0b9da --- /dev/null +++ b/builder/app.py @@ -0,0 +1,89 @@ +import json +import os + + +from traitlets.config import Application, LoggingConfigurable, Unicode, Dict, List +from traitlets import Type +import docker + +import subprocess + + +from .detectors import BuildPack, PythonBuildPack + +class Builder(Application): + config_file = Unicode( + 'builder_config.py', + config=True + ) + + build_name = Unicode( + None, + allow_none=True, + config=True + ) + + source_url = Unicode( + None, + allow_none=True, + config=True + ) + + output_image_spec = Unicode( + None, + allow_none=True, + config=True + ) + + git_workdir = Unicode( + "/tmp/git", + config=True + ) + + buildpacks = List( + None, + [PythonBuildPack], + config=True + ) + + aliases = Dict({ + 'source': 'Builder.source_url', + 'output': 'Builder.output_image_spec', + 'f': 'Builder.config_file', + 'n': 'Builder.build_name' + }) + + + def fetch(self, url, ref, output_path): + subprocess.check_call([ + "git", "clone", "--depth", "1", + url, output_path + ]) + + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + + self.load_config_file(self.config_file) + + def run(self): + output_path = os.path.join(self.git_workdir, self.build_name) + self.fetch( + self.source_url, + 'master', + output_path + ) + for bp_class in self.buildpacks: + bp = bp_class() + if bp.detect(output_path): + bp.build(output_path, self.output_image_spec) + + client = docker.from_env(version='1.24') + for line in client.images.push(self.output_image_spec, stream=True): + progress = json.loads(line.decode('utf-8')) + print(progress['status']) + +if __name__ == '__main__': + f = Builder() + f.initialize() + f.run() diff --git a/builder/detectors.py b/builder/detectors.py new file mode 100644 index 00000000..f994017d --- /dev/null +++ b/builder/detectors.py @@ -0,0 +1,51 @@ +import os +import subprocess + +from traitlets import Unicode, Dict +from traitlets.config import LoggingConfigurable + + +class BuildPack(LoggingConfigurable): + def detect(self, workdir): + """ + Return True if app in workdir can be built with this buildpack + """ + pass + + def build(self, workdir, output_image_spec): + """ + Run a command that will take workdir and produce an image ready to be pushed + """ + pass + + + +class PythonBuildPack(BuildPack): + runtime_builder_map = Dict({ + 'python-2.7': 'jupyterhub/singleuser-builder-venv-2.7:v0.1.2', + 'python-3.5': 'jupyterhub/singleuser-builder-venv-3.5:v0.1.2', + }) + + runtime = Unicode( + 'python-3.5', + config=True + ) + + def detect(self, workdir): + if os.path.exists(os.path.join(workdir, 'requirements.txt')): + try: + with open(os.path.join(workdir, 'runtime.txt')) as f: + self.runtime = f.read().strip() + except FileNotFoundError: + pass + return True + + def build(self, workdir, output_image_spec): + cmd = [ + 's2i', + 'build', + workdir, + self.runtime_builder_map[self.runtime], + output_image_spec + ] + subprocess.check_call(cmd)