diff --git a/repo2docker/app.py b/repo2docker/app.py index 1d3238d0..915e3295 100644 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -26,7 +26,7 @@ import subprocess from .detectors import ( BuildPack, PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack, - CondaBuildPack, DefaultBuildPack + CondaBuildPack, DefaultBuildPack, JuliaBuildPack ) from .utils import execute_cmd from . import __version__ @@ -93,7 +93,7 @@ class Repo2Docker(Application): buildpacks = List( Type(BuildPack), - [LegacyBinderDockerBuildPack, DockerBuildPack, CondaBuildPack, PythonBuildPack, DefaultBuildPack], + [LegacyBinderDockerBuildPack, DockerBuildPack, CondaBuildPack, PythonBuildPack, JuliaBuildPack, DefaultBuildPack], config=True, help=""" Ordered list of BuildPacks to try to use to build a git repository. diff --git a/repo2docker/detectors.py b/repo2docker/detectors.py index 0b239b00..d757dad1 100644 --- a/repo2docker/detectors.py +++ b/repo2docker/detectors.py @@ -136,6 +136,14 @@ class CondaBuildPack(S2IBuildPack): return os.path.exists(os.path.join(workdir, 'environment.yml')) +class JuliaBuildPack(S2IBuildPack): + name = Unicode('julia') + build_image = Unicode('jupyterhub/singleuser-builder-julia:v0.2.1', config=True) + + def detect(self, workdir): + return os.path.exists(os.path.join(workdir, 'REQUIRE')) + + class PythonBuildPack(S2IBuildPack): """Build Pack for installing from a pip requirements.txt using S2I""" name = Unicode('python-pip') diff --git a/s2i-builders/julia/Dockerfile b/s2i-builders/julia/Dockerfile new file mode 100644 index 00000000..463bcab2 --- /dev/null +++ b/s2i-builders/julia/Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:17.04 + +MAINTAINER Yuvi Panda + +LABEL io.openshift.s2i.scripts-url=image:///usr/libexec/s2i + +ENV APP_DIR /srv/app +ENV JULIA_PATH /usr/local/julia +ENV PATH ${APP_DIR}/venv/bin:$PATH + +RUN apt-get update && \ + apt-get install --yes \ + python3 \ + python3-venv \ + python3-dev \ + build-essential \ + pkg-config \ + libfreetype6-dev \ + libpng-dev \ + tar \ + git \ + curl \ + locales && \ + apt-get purge && apt-get clean + +RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ + locale-gen + +ENV LC_ALL en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US.UTF-8 + +RUN adduser --disabled-password --gecos "Default Jupyter user" jovyan + +RUN mkdir -p ${APP_DIR} && chown -R jovyan:jovyan ${APP_DIR} + +# Install Julia +ENV JULIA_PATH /usr/local/julia +ENV JULIA_VERSION 0.5.2 + +RUN mkdir $JULIA_PATH && \ + curl -sSL "https://julialang-s3.julialang.org/bin/linux/x64/${JULIA_VERSION%[.-]*}/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" | tar -xz -C ${JULIA_PATH} --strip-components 1 + +WORKDIR /home/jovyan + +USER jovyan +RUN python3 -m venv ${APP_DIR}/venv + +RUN pip install --no-cache-dir notebook==5.0.0 jupyterhub==0.7.2 ipywidgets==5.2.3 jupyterlab==0.22.1 && \ + jupyter nbextension enable --py widgetsnbextension --sys-prefix && \ + jupyter serverextension enable --py jupyterlab --sys-prefix + +ENV JUPYTER ${APP_DIR}/venv/bin/jupyter +ENV PATH ${APP_DIR}/venv/bin:$PATH:${JULIA_PATH}/bin +ENV JULIA_PKGDIR ${APP_DIR}/.julia + +RUN julia -e 'Pkg.init(); Pkg.add("IJulia")' && \ + mv ${HOME}/.local/share/jupyter/kernels/julia-0.5/ ${APP_DIR}/venv/share/jupyter/kernels/julia-0.5 + + +COPY ./s2i/bin/ /usr/libexec/s2i + +EXPOSE 8888 diff --git a/s2i-builders/julia/Makefile b/s2i-builders/julia/Makefile new file mode 100644 index 00000000..0dff9303 --- /dev/null +++ b/s2i-builders/julia/Makefile @@ -0,0 +1,9 @@ +IMAGE_PREFIX = jupyterhub/singleuser-builder-julia +VERSION = $(shell cat version) + +.PHONY: build +build: + docker build -t $(IMAGE_PREFIX):$(VERSION) . -f Dockerfile + +push: + docker push "$(IMAGE_PREFIX):$(VERSION)" diff --git a/s2i-builders/julia/README.md b/s2i-builders/julia/README.md new file mode 100755 index 00000000..4c2bdc7a --- /dev/null +++ b/s2i-builders/julia/README.md @@ -0,0 +1,12 @@ + +# JupyterHub singleuser builder + +This is a builder image for use with [s2i](https://github.com/openshift/source-to-image). It +builds a source repository (such as a github repository) into a docker image that is suitable +for use with [JupyterHub](http://github.com/jupyterhub/jupyterhub). + +It is based off Ubuntu 17.04, and uses [virtualenv](https://pypi.python.org/pypi/virtualenv) to +provide a custom python3.5 environment. + +It looks for a `requirements.txt` file in the source repository, and installs it into the virtualenv. +It also installs a number of default notebook related modules in there diff --git a/s2i-builders/julia/test/run b/s2i-builders/julia/test/run new file mode 100755 index 00000000..57c952a4 --- /dev/null +++ b/s2i-builders/julia/test/run @@ -0,0 +1,160 @@ +#!/bin/bash +# +# The 'run' performs a simple test that verifies the S2I image. +# The main focus here is to exercise the S2I scripts. +# +# For more information see the documentation: +# https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md +# +# IMAGE_NAME specifies a name of the candidate image used for testing. +# The image has to be available before this script is executed. +# +IMAGE_NAME=${IMAGE_NAME-ubuntu1610-python35-venv-candidate} + +# Determining system utility executables (darwin compatibility check) +READLINK_EXEC="readlink" +MKTEMP_EXEC="mktemp" +if [[ "$OSTYPE" =~ 'darwin' ]]; then + ! type -a "greadlink" &>"/dev/null" || READLINK_EXEC="greadlink" + ! type -a "gmktemp" &>"/dev/null" || MKTEMP_EXEC="gmktemp" +fi + +test_dir="$($READLINK_EXEC -zf $(dirname "${BASH_SOURCE[0]}"))" +image_dir=$($READLINK_EXEC -zf ${test_dir}/..) +scripts_url="file://${image_dir}/.s2i/bin" +cid_file=$($MKTEMP_EXEC -u --suffix=.cid) + +# Since we built the candidate image locally, we don't want S2I to attempt to pull +# it from Docker hub +s2i_args="--pull-policy=never --loglevel=2" + +# Port the image exposes service to be tested +test_port=8080 + +image_exists() { + docker inspect $1 &>/dev/null +} + +container_exists() { + image_exists $(cat $cid_file) +} + +container_ip() { + if [ ! -z "$DOCKER_HOST" ] && [[ "$OSTYPE" =~ 'darwin' ]]; then + docker-machine ip + else + docker inspect --format="{{ .NetworkSettings.IPAddress }}" $(cat $cid_file) + fi +} + +container_port() { + if [ ! -z "$DOCKER_HOST" ] && [[ "$OSTYPE" =~ 'darwin' ]]; then + docker inspect --format="{{(index .NetworkSettings.Ports \"$test_port/tcp\" 0).HostPort}}" "$(cat "${cid_file}")" + else + echo $test_port + fi +} + +run_s2i_build() { + s2i build --incremental=true ${s2i_args} file://${test_dir}/test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp +} + +prepare() { + if ! image_exists ${IMAGE_NAME}; then + echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed." + exit 1 + fi + # s2i build requires the application is a valid 'Git' repository + pushd ${test_dir}/test-app >/dev/null + git init + git config user.email "build@localhost" && git config user.name "builder" + git add -A && git commit -m "Sample commit" + popd >/dev/null + run_s2i_build +} + +run_test_application() { + docker run --rm --cidfile=${cid_file} -p ${test_port} ${IMAGE_NAME}-testapp +} + +cleanup() { + if [ -f $cid_file ]; then + if container_exists; then + docker stop $(cat $cid_file) + fi + fi + if image_exists ${IMAGE_NAME}-testapp; then + docker rmi ${IMAGE_NAME}-testapp + fi +} + +check_result() { + local result="$1" + if [[ "$result" != "0" ]]; then + echo "S2I image '${IMAGE_NAME}' test FAILED (exit code: ${result})" + cleanup + exit $result + fi +} + +wait_for_cid() { + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + [ -f $cid_file ] && break + echo "Waiting for container to start..." + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done +} + +test_usage() { + echo "Testing 's2i usage'..." + s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null +} + +test_connection() { + echo "Testing HTTP connection (http://$(container_ip):$(container_port))" + local max_attempts=10 + local sleep_time=1 + local attempt=1 + local result=1 + while [ $attempt -le $max_attempts ]; do + echo "Sending GET request to http://$(container_ip):$(container_port)/" + response_code=$(curl -s -w %{http_code} -o /dev/null http://$(container_ip):$(container_port)/) + status=$? + if [ $status -eq 0 ]; then + if [ $response_code -eq 200 ]; then + result=0 + fi + break + fi + attempt=$(( $attempt + 1 )) + sleep $sleep_time + done + return $result +} + +# Build the application image twice to ensure the 'save-artifacts' and +# 'restore-artifacts' scripts are working properly +prepare +run_s2i_build +check_result $? + +# Verify the 'usage' script is working properly +test_usage +check_result $? + +# Verify that the HTTP connection can be established to test application container +run_test_application & + +# Wait for the container to write its CID file +wait_for_cid + +test_connection +check_result $? + +cleanup + diff --git a/s2i-builders/julia/test/test-app/index.html b/s2i-builders/julia/test/test-app/index.html new file mode 100755 index 00000000..1d35c559 --- /dev/null +++ b/s2i-builders/julia/test/test-app/index.html @@ -0,0 +1,11 @@ + + + + + Hello World! + + +

Hello World!

+ + + \ No newline at end of file diff --git a/s2i-builders/julia/version b/s2i-builders/julia/version new file mode 100644 index 00000000..22c08f72 --- /dev/null +++ b/s2i-builders/julia/version @@ -0,0 +1 @@ +v0.2.1