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!)
ADD
builder/app.py
+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()

ADD
builder/detectors.py
+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)

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!)