diff --git a/CHANGELOG.md b/CHANGELOG.md index c816e1f..dec20fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - First working version of `dockernel install` and `dockernel start`. [unreleased]: https://github.com/mrmino/dockernel/v1.0.2...HEAD -[1.0.1]: https://github.com/mrmino/dockernel/releases/tag/v1.0.2 +[1.0.2]: https://github.com/mrmino/dockernel/releases/tag/v1.0.2 [1.0.1]: https://github.com/mrmino/dockernel/releases/tag/v1.0.1 [1.0.0]: https://github.com/mrmino/dockernel/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 0cf8692..5e42067 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,52 @@ different one. ## Usage +*Note for Linux users: + +If you run into permission errors with `docker` or `dockernel` - either use +`sudo`, or follow the steps outlined in [Manage Docker as a non-root +user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) +guide.* + +### Creating a Dockernel image + First, create a docker image that will host your kernel. This will require a -proper dockerfile. An example can be seen +proper dockerfile. A full example for IPython kernel can be seen [here](https://github.com/MrMino/dockernel/blob/master/example_dockerfile). +Most kernels take a path to a "connection file" (also called "control file" by +some kernels) as a CLI argument. This file contains all of the information +necessary to start up a kernel, including TCP ports to use, IP address, etc. + +When running your container, Dockernel will supply this file and put it into a +predefined path in the container. This path will be given via an environment +variable visible in the container as `$DOCKERNEL_CONNECTION_FILE`. + +Therefore, in order for the kernel to know the connection settings it should +use, you need to pass the contents of this variable in `CMD` of the container. +For example, for IPython kernel: + +``` +CMD python -m ipykernel_launcher -f $DOCKERNEL_CONNECTION_FILE +``` + +Or for the Rust kernel (Evcxr, see the +[example Rust +dockerfile](https://github.com/MrMino/dockernel/blob/master/example_rust_dockerfile)): + +``` +CMD evcxr_jupyter --control_file $DOCKERNEL_CONNECTION_FILE +``` + To build your image, use `docker build`. E.g. to build the example mentioned above: ``` -sudo docker build --tag my_kernel - < example_dockerfile +docker build --tag my_kernel - < example_dockerfile ``` +### Installing your image as a Jupyter Kernel + After that, use Dockernel to install the docker image as a Jupyter kernel: ``` diff --git a/dockernel/cli/install.py b/dockernel/cli/install.py index 488a18e..36f6d6c 100644 --- a/dockernel/cli/install.py +++ b/dockernel/cli/install.py @@ -49,7 +49,7 @@ def python_argv(system_type: str) -> List[str]: def generate_kernelspec_argv(image_name: str, system_type: str) -> List[str]: dockernel_argv = ['dockernel', 'start', - image_name, JUPYTER_CONNECTION_FILE_TEMPLATE] + image_name, JUPYTER_CONNECTION_FILE_TEMPLATE] return python_argv(system_type) + dockernel_argv diff --git a/dockernel/kernelspec.py b/dockernel/kernelspec.py index 4c184b4..7016263 100644 --- a/dockernel/kernelspec.py +++ b/dockernel/kernelspec.py @@ -12,9 +12,12 @@ from enum import Enum from pathlib import Path +from .version import version as dockernel_version + KERNELSPEC_FILENAME = 'kernel.json' KERNELSPEC_STORE_DIRNAME = 'kernels' +DOCKERNEL_VERSIONFILE_FILENAME = 'DOCKERNEL' class InterruptMode(str, Enum): @@ -165,3 +168,18 @@ def install_kernelspec(kernelspec_dir: Path, kernelspec: Kernelspec) -> None: kernelspec_dir.mkdir() kernelspec_file = kernelspec_dir/KERNELSPEC_FILENAME kernelspec_file.write_text(kernelspec.json()) + add_dockernel_versionfile(kernelspec_dir) + + +def add_dockernel_versionfile(kernelspec_dir: Path) -> None: + """Generate DOCKERNEL file under the specified path. + + Creates a text file with the current version of dockernel package + + Parameters + ---------- + kernelspec_dir + Path object to the store where the kernel should be installed. + """ + versionfile_path = kernelspec_dir/DOCKERNEL_VERSIONFILE_FILENAME + versionfile_path.write_text(dockernel_version + '\n') diff --git a/dockernel/version.py b/dockernel/version.py new file mode 100644 index 0000000..71ee811 --- /dev/null +++ b/dockernel/version.py @@ -0,0 +1,4 @@ +# This file is written to by setuptools_scm. +# The value supplied here is for development purposes only. + +__version__ = '0.0.0.dev' diff --git a/example_dockerfile b/example_dockerfile index 7dfb2cd..fc034da 100644 --- a/example_dockerfile +++ b/example_dockerfile @@ -1,4 +1,4 @@ FROM python:3.7-slim-buster RUN pip install --upgrade pip ipython ipykernel -CMD cat $DOCKERNEL_CONNECTION_FILE; python -m ipykernel_launcher -f $DOCKERNEL_CONNECTION_FILE || true +CMD python -m ipykernel_launcher -f $DOCKERNEL_CONNECTION_FILE diff --git a/example_rust_dockerfile b/example_rust_dockerfile new file mode 100644 index 0000000..26c2b68 --- /dev/null +++ b/example_rust_dockerfile @@ -0,0 +1,5 @@ +FROM rust:1.50.0-buster + +RUN apt-get update && apt-get install build-essential cmake -y +RUN cargo install evcxr_jupyter +CMD evcxr_jupyter --control_file $DOCKERNEL_CONNECTION_FILE diff --git a/pyproject.toml b/pyproject.toml index 498d416..6c44890 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,3 +2,4 @@ requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] [tool.setuptools_scm] +write_to = "dockernel/version.py" diff --git a/setup.cfg b/setup.cfg index 7a3001d..34987dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifiers = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Programming Language :: Python :: Implementation :: CPython [options] diff --git a/setup.py b/setup.py index e02756e..6267b7a 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,3 @@ import setuptools -setuptools.setup(use_scm_version=True) +setuptools.setup(use_scm_version={'write_to': 'dockernel/version.py'}) diff --git a/tests/test_install.py b/tests/test_install.py index f5f31f8..424d5cd 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -4,12 +4,13 @@ from dockernel.cli import main_arguments -class TestKernelspecArgvGeneration: +# TODO Add tests for Darwin and Windows +class TestLinuxKernelspecArgvGeneration: IMAGE_NAME = 'example/image-name' @pytest.fixture def argv(self): - return generate_kernelspec_argv(self.IMAGE_NAME) + return generate_kernelspec_argv(self.IMAGE_NAME, 'Linux') def test_argv_starts_with_usr_bin_env_python_m(self, argv): assert argv[:3] == ['/usr/bin/env', 'python', '-m'] diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index 32cba82..354b329 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -1,3 +1,4 @@ +import os import pytest import json from dockernel.kernelspec import (Kernelspec, InterruptMode, @@ -7,6 +8,16 @@ from pathlib import Path +@pytest.yield_fixture +def environment_variables(): + starting_state = os.environ.copy() + + yield os.environ + + os.environ.clear() + os.environ.update(starting_state) + + @pytest.fixture def tmpdir(tmpdir): """Workaround for tmpdir being py.path.LocalPath instead of Path""" @@ -81,7 +92,10 @@ def test_raises_ValueError_when_directory_name_is_not_store(self, tmpdir): class TestKernelspecStoreDir: @pytest.mark.parametrize('system_type', ('Linux', 'Windows', 'Darwin')) - def test_works_for_supported_platforms(self, system_type): + def test_works_for_supported_platforms(self, system_type, + environment_variables): + if system_type == 'Windows': + environment_variables['APPDATA'] = '' user_kernelspec_store(system_type) def test_raises_TypeError_on_unknown_system(self):