repo2docker/repo2docker/detectors.py

161 wiersze
5.4 KiB
Python

import os
import sys
import subprocess
from textwrap import dedent
import docker
from docker.utils import kwargs_from_env
from traitlets import Unicode, Dict, Bool
from traitlets.config import LoggingConfigurable
import logging
from pythonjsonlogger import jsonlogger
from .utils import execute_cmd
here = os.path.abspath(os.path.dirname(__file__))
class BuildPack(LoggingConfigurable):
name = Unicode()
capture = Bool(False, help="Capture output for logging")
def detect(self, workdir):
"""
Return True if app in workdir can be built with this buildpack
"""
pass
def build(self, workdir, ref, output_image_spec):
"""
Run a command that will take workdir and produce an image ready to be pushed
"""
pass
class DockerBuildPack(BuildPack):
name = Unicode('Dockerfile')
def detect(self, workdir):
return os.path.exists(os.path.join(workdir, 'Dockerfile'))
def build(self, workdir, ref, output_image_spec):
client = docker.APIClient(version='auto', **kwargs_from_env())
for progress in client.build(
path=workdir,
tag=output_image_spec,
decode=True
):
if 'stream' in progress:
if self.capture:
self.log.info(progress['stream'], extra=dict(phase='building'))
else:
sys.stdout.write(progress['stream'])
class LegacyBinderDockerBuildPack(DockerBuildPack):
name = Unicode('Legacy Binder Dockerfile')
dockerfile_appendix = Unicode(dedent(r"""
USER root
COPY . /home/main/notebooks
RUN chown -R main:main /home/main/notebooks
USER main
WORKDIR /home/main/notebooks
ENV PATH /home/main/anaconda2/envs/python3/bin:$PATH
RUN conda install -n python3 notebook==5.0.0 ipykernel==4.6.0 && \
pip install jupyterhub==0.7.2 && \
conda remove -n python3 nb_conda_kernels && \
conda install -n root ipykernel==4.6.0 && \
/home/main/anaconda2/envs/python3/bin/ipython kernel install --sys-prefix && \
/home/main/anaconda2/bin/ipython kernel install --prefix=/home/main/anaconda2/envs/python3
ENV JUPYTER_PATH /home/main/anaconda2/share/jupyter:$JUPYTER_PATH
CMD jupyter notebook --ip 0.0.0.0
"""), config=True)
def detect(self, workdir):
dockerfile = os.path.join(workdir, 'Dockerfile')
if not os.path.exists(dockerfile):
return False
with open(dockerfile, 'r') as f:
for line in f:
if line.startswith('FROM'):
if 'andrewosh/binder-base' in line.split('#')[0].lower():
self.amend_dockerfile(dockerfile)
return True
else:
return False
# No FROM?!
return False
def amend_dockerfile(self, dockerfile):
with open(dockerfile, 'a') as f:
f.write(self.dockerfile_appendix)
class S2IBuildPack(BuildPack):
# Simple subclasses of S2IBuildPack must set build_image,
# either via config or during `detect()`
build_image = Unicode('')
def s2i_build(self, workdir, ref, output_image_spec, build_image):
# Note: Ideally we'd just copy from workdir here, rather than clone and check out again
# However, setting just --copy and not specifying a ref seems to check out master for
# some reason. Investigate deeper FIXME
cmd = [
's2i',
'build',
'--exclude', '""',
'--ref', ref,
'.',
build_image,
output_image_spec,
]
env = os.environ.copy()
# add bundled s2i to *end* of PATH,
# in case user doesn't have s2i
env['PATH'] = os.pathsep.join([env.get('PATH') or os.defpath, here])
try:
for line in execute_cmd(cmd, cwd=workdir, env=env, capture=self.capture):
self.log.info(line, extra=dict(phase='building', builder=self.name))
except subprocess.CalledProcessError:
self.log.error('Failed to build image!', extra=dict(phase='failed'))
sys.exit(1)
def build(self, workdir, ref, output_image_spec):
return self.s2i_build(workdir, ref, output_image_spec, self.build_image)
class CondaBuildPack(S2IBuildPack):
"""Build Pack for installing from a conda environment.yml using S2I"""
name = Unicode('conda')
build_image = Unicode('jupyterhub/singleuser-builder-conda:v0.1.5', config=True)
def detect(self, workdir):
return os.path.exists(os.path.join(workdir, 'environment.yml'))
class PythonBuildPack(S2IBuildPack):
"""Build Pack for installing from a pip requirements.txt using S2I"""
name = Unicode('python-pip')
runtime_builder_map = Dict({
'python-2.7': 'jupyterhub/singleuser-builder-venv-2.7:v0.1.5',
'python-3.5': 'jupyterhub/singleuser-builder-venv-3.5:v0.1.5',
})
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
self.build_image = self.runtime_builder_map[self.runtime]
return True