diff --git a/.gitignore b/.gitignore index 894a44c..1e5c982 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# OS X stuff +.DS_Store diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 3a42fb2..70bdbf8 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -16,6 +16,7 @@ import os import subprocess import sys import logging +import shutil def get_os_release_variable(key): @@ -31,8 +32,10 @@ def get_os_release_variable(key): "source /etc/os-release && echo ${{{key}}}".format(key=key) ]).decode().strip() -def main(): - +def validate_host(): + """ + Make sure TLJH is installable in current host + """ # Support only Ubuntu 18.04+ distro = get_os_release_variable('ID') version = float(get_os_release_variable('VERSION_ID')) @@ -43,6 +46,21 @@ def main(): print('The Littlest JupyterHub requires Ubuntu 18.04 or higher') sys.exit(1) + if sys.version_info < (3, 5): + print("bootstrap.py must be run with at least Python 3.5") + sys.exit(1) + + if not (shutil.which('systemd') and shutil.which('systemctl')): + print("Systemd is required to run TLJH") + # Only fail running inside docker if systemd isn't present + if os.path.exists('/.dockerenv'): + print("Running inside a docker container without systemd isn't supported") + print("We recommend against running a production TLJH instance inside a docker container") + print("For local development, see http://tljh.jupyter.org/en/latest/contributing/dev-setup.html") + sys.exit(1) + +def main(): + validate_host() install_prefix = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh') hub_prefix = os.path.join(install_prefix, 'hub') diff --git a/docs/.DS_Store b/docs/.DS_Store deleted file mode 100644 index 1286e27..0000000 Binary files a/docs/.DS_Store and /dev/null differ diff --git a/docs/contributing/dev-setup.rst b/docs/contributing/dev-setup.rst index e69ca68..af0a3b8 100644 --- a/docs/contributing/dev-setup.rst +++ b/docs/contributing/dev-setup.rst @@ -1,4 +1,4 @@ -.. _contributing_dev_setup: +.. _contributing/dev-setup: ================================== Setting up Development Environment diff --git a/docs/images/.DS_Store b/docs/images/.DS_Store deleted file mode 100644 index 1c30720..0000000 Binary files a/docs/images/.DS_Store and /dev/null differ diff --git a/docs/images/providers/.DS_Store b/docs/images/providers/.DS_Store deleted file mode 100644 index 6c20ed2..0000000 Binary files a/docs/images/providers/.DS_Store and /dev/null differ diff --git a/docs/install/custom-server.rst b/docs/install/custom-server.rst index 651abce..e31c492 100644 --- a/docs/install/custom-server.rst +++ b/docs/install/custom-server.rst @@ -4,11 +4,21 @@ Installing on your own server ============================= + +Follow this guide if your cloud provider doesn't have a direct tutorial, or +you are setting this up on a bare metal server. + +.. warning:: + + Do **not** install TLJH directly on your laptop or personal computer! + It will most likely open up exploitable security holes when run directly + on your personal computer. + .. note:: - You should use this if your cloud provider does not already have a direct tutorial, - or if you have experience setting up servers. - + Running TLJH *inside* a docker container is not supported, since we depend + on systemd. If you want to run TLJH locally for development, see + :ref:`contributing/dev-setup`. Goal ==== diff --git a/docs/troubleshooting/.DS_Store b/docs/troubleshooting/.DS_Store deleted file mode 100644 index 4f10a9c..0000000 Binary files a/docs/troubleshooting/.DS_Store and /dev/null differ diff --git a/integration-tests/test_bootstrap.py b/integration-tests/test_bootstrap.py index 5bf143f..fa02b68 100644 --- a/integration-tests/test_bootstrap.py +++ b/integration-tests/test_bootstrap.py @@ -2,14 +2,9 @@ Test running bootstrap script in different circumstances """ import subprocess +from textwrap import dedent - -def test_ubuntu_too_old(): - """ - Error with a useful message when running in older Ubuntu - """ - container_name = 'old-distro-test' - +def run_bootstrap(container_name, image): # stop container if it is already running subprocess.run([ 'docker', 'rm', '-f', container_name @@ -17,7 +12,7 @@ def test_ubuntu_too_old(): # Start a detached Ubuntu 16.04 container subprocess.check_call([ - 'docker', 'run', '--detach', '--name', container_name, 'ubuntu:16.04', + 'docker', 'run', '--detach', '--name', container_name, image, '/bin/bash', '-c', 'sleep 1000s' ]) # Install python3 inside the ubuntu container @@ -35,10 +30,27 @@ def test_ubuntu_too_old(): 'bootstrap/', f'{container_name}:/srv' ]) - # Run bootstrap script, validate that it fails appropriately - output = subprocess.run([ + # Run bootstrap script, return the output + return subprocess.run([ 'docker', 'exec', '-i', container_name, 'python3', '/srv/bootstrap/bootstrap.py' ], check=False, stdout=subprocess.PIPE, encoding='utf-8') + +def test_ubuntu_too_old(): + """ + Error with a useful message when running in older Ubuntu + """ + output = run_bootstrap('old-distro-test', 'ubuntu:16.04') assert output.stdout == 'The Littlest JupyterHub requires Ubuntu 18.04 or higher\n' - assert output.returncode == 1 \ No newline at end of file + assert output.returncode == 1 + + +def test_inside_no_systemd_docker(): + output = run_bootstrap('plain-docker-test', 'ubuntu:18.04') + assert output.stdout.strip() == dedent(""" + Systemd is required to run TLJH + Running inside a docker container without systemd isn't supported + We recommend against running a production TLJH instance inside a docker container + For local development, see http://tljh.jupyter.org/en/latest/contributing/dev-setup.html + """).strip() + assert output.returncode == 1 diff --git a/setup.py b/setup.py index 61f8ead..4597a95 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ setup( 'jinja2', 'pluggy>0.7<1.0', 'passlib', + 'backoff', 'jupyterhub-traefik-proxy==0.1.*' ], entry_points={ diff --git a/tests/test_configurer.py b/tests/test_configurer.py index 98dd106..641e407 100644 --- a/tests/test_configurer.py +++ b/tests/test_configurer.py @@ -66,7 +66,7 @@ def test_default_memory_limit(): Test default per user memory limit """ c = apply_mock_config({}) - assert c.SystemdSpawner.mem_limit is None + assert c.Spawner.mem_limit is None def test_set_memory_limit(): @@ -74,7 +74,7 @@ def test_set_memory_limit(): Test setting per user memory limit """ c = apply_mock_config({'limits': {'memory': '42G'}}) - assert c.SystemdSpawner.mem_limit == '42G' + assert c.Spawner.mem_limit == '42G' def test_app_default(): diff --git a/tljh/configurer.py b/tljh/configurer.py index 491a5f8..e909ac7 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -162,8 +162,8 @@ def update_limits(c, config): """ limits = config['limits'] - c.SystemdSpawner.mem_limit = limits['memory'] - c.SystemdSpawner.cpu_limit = limits['cpu'] + c.Spawner.mem_limit = limits['memory'] + c.Spawner.cpu_limit = limits['cpu'] def update_user_environment(c, config): diff --git a/tljh/traefik.py b/tljh/traefik.py index 4581953..4e05b7a 100644 --- a/tljh/traefik.py +++ b/tljh/traefik.py @@ -1,10 +1,11 @@ """Traefik installation and setup""" import hashlib import os -from urllib.request import urlretrieve +from urllib.request import urlretrieve, ContentTooShortError from jinja2 import Template from passlib.apache import HtpasswdFile +import backoff from tljh.configurer import load_config @@ -26,7 +27,12 @@ def checksum_file(path): hasher.update(chunk) return hasher.hexdigest() - +@backoff.on_exception( + backoff.expo, + # Retry when connection is reset or we think we didn't download entire file + (ConnectionResetError, ContentTooShortError), + max_tries=2 +) def ensure_traefik_binary(prefix): """Download and install the traefik binary""" traefik_bin = os.path.join(prefix, "bin", "traefik")