From 5374f5eaf5f9d52b8df2fd30369410c8d4d38629 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 30 Oct 2018 20:08:47 -0700 Subject: [PATCH 1/7] Provide better error message when running on unsupported distro --- .circleci/config.yml | 8 ++++++- bootstrap/bootstrap.py | 13 +++++++++-- docs/index.rst | 4 +++- integration-tests/test_distro.py | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 integration-tests/test_distro.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 572bcd2..c57ad61 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,7 +57,7 @@ jobs: - run: name: setup python3 command: | - apk add --no-cache python3 + apk add --no-cache python3 pytest - checkout - setup_remote_docker @@ -79,6 +79,12 @@ jobs: --installer-args "--plugin /srv/src/integration-tests/plugins/simplest" \ plugins test_simplest_plugin.py + - run: + name: Run distro check test + command: | + py.test integration-tests/test_distro.py + + documentation: docker: diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 324dae9..78fe7fe 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -8,15 +8,24 @@ This script is run as: curl | sudo python3 - Constraints: - - Be compatible with Python 3.4 (since we support Ubuntu 16.04) + - Entire script should be compatible with Python 3.6 (We run on Ubuntu 18.04+) + - Script should parse in Python 3.4 (since we exit with useful error message on Ubuntu 14.04+) - Use stdlib modules only """ import os import subprocess import sys import logging +import platform - +# Support only Ubuntu 18.04+ +distro, version, _ = platform.linux_distribution() +if distro != 'Ubuntu': + print('The Littlest JupyterHub currently supports Ubuntu Linux only') + sys.exit(1) +elif float(version) < 18.04: + print('The Littlest JupyterHub requires Ubuntu 18.04 or higher') + sys.exit(1) def main(): install_prefix = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh') diff --git a/docs/index.rst b/docs/index.rst index ea10e01..3bc5597 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,7 @@ A simple `JupyterHub `_ distribution f a small (0-100) number of users on a single server. We recommend reading :ref:`topic/whentouse` to determine if this is the right tool for you. + Development Status ================== @@ -18,7 +19,8 @@ Installation ============ The Littlest JupyterHub (TLJH) can run on any server that is running at least -Ubuntu 18.04. We have a bunch of tutorials to get you started. +**Ubuntu 18.04**. Earlier versions of Ubuntu are not supported. +We have a bunch of tutorials to get you started. - Tutorials to create a new server from scratch on a cloud provider & run TLJH on it. These are **recommended** if you do not have much experience setting up diff --git a/integration-tests/test_distro.py b/integration-tests/test_distro.py new file mode 100644 index 0000000..cf237f1 --- /dev/null +++ b/integration-tests/test_distro.py @@ -0,0 +1,40 @@ +""" +Test running on non-supported distros +""" +import subprocess + +def test_ubuntu_too_old(): + container_name = 'old-distro-test' + + # stop container if it is already running + subprocess.run([ + 'docker', 'rm', '-f', container_name + ]) + + # Start a detached Ubuntu 16.04 container + subprocess.check_call([ + 'docker', 'run', '--detach', '--name', container_name, 'ubuntu:16.04', + '/bin/bash', '-c', 'sleep 1000s' + ]) + # Install python3 inside the ubuntu container + # There is no trusted Ubuntu+Python3 container we can use + subprocess.check_output([ + 'docker', 'exec', container_name, 'apt-get', 'update' + ]) + subprocess.check_output([ + 'docker', 'exec', container_name, 'apt-get', 'install', '--yes', 'python3' + ]) + # Copy only the bootstrap script to container, so this is faster + subprocess.check_call([ + 'docker', + 'cp', + 'bootstrap/', f'{container_name}:/srv' + ]) + + # Run bootstrap script, validate that it fails appropriately + output = subprocess.run([ + 'docker', 'exec', '-i', container_name, + 'python3', '/srv/bootstrap/bootstrap.py' + ], check=False, stdout=subprocess.PIPE, encoding='utf-8') + assert output.stdout == 'The Littlest JupyterHub requires Ubuntu 18.04 or higher\n' + assert output.returncode == 1 \ No newline at end of file From b644599af838212c2ca02242bf1ef23ca10d5263 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Wed, 31 Oct 2018 10:25:29 -0700 Subject: [PATCH 2/7] Import TLJH only when needed in conftest.py --- integration-tests/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py index ee0ebbd..82dd70e 100644 --- a/integration-tests/conftest.py +++ b/integration-tests/conftest.py @@ -4,12 +4,14 @@ import os from pytest import fixture -from tljh.config import CONFIG_FILE, reload_component @fixture def preserve_config(request): """Fixture to save and restore config around tests""" + # Import TLJH only when needed. This lets us run tests in places + # where TLJH is not installed - particularly, the 'distro check' test. + from tljh.config import CONFIG_FILE, reload_component if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE) as f: save_config = f.read() From 99e3106b29c07f50628d9bd89fafdbbf1540e37d Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Wed, 31 Oct 2018 11:21:09 -0700 Subject: [PATCH 3/7] Make bootstrap test name / description more generic --- .circleci/config.yml | 4 ++-- integration-tests/{test_distro.py => test_bootstrap.py} | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) rename integration-tests/{test_distro.py => test_bootstrap.py} (90%) diff --git a/.circleci/config.yml b/.circleci/config.yml index c57ad61..64db5e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,9 +80,9 @@ jobs: plugins test_simplest_plugin.py - run: - name: Run distro check test + name: Run bootstrap checks command: | - py.test integration-tests/test_distro.py + py.test integration-tests/test_bootstrap.py diff --git a/integration-tests/test_distro.py b/integration-tests/test_bootstrap.py similarity index 90% rename from integration-tests/test_distro.py rename to integration-tests/test_bootstrap.py index cf237f1..5bf143f 100644 --- a/integration-tests/test_distro.py +++ b/integration-tests/test_bootstrap.py @@ -1,9 +1,13 @@ """ -Test running on non-supported distros +Test running bootstrap script in different circumstances """ import subprocess + def test_ubuntu_too_old(): + """ + Error with a useful message when running in older Ubuntu + """ container_name = 'old-distro-test' # stop container if it is already running From e9462bb7ae95162b83292738a0216c8a5e3b0b9d Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 13 Nov 2018 12:47:05 -0800 Subject: [PATCH 4/7] Do not use deprecated platform module Since bootstrap.py needs to restrict itself to stdlib python, we read from /etc/os-release than trying to install the distro package. --- bootstrap/bootstrap.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 78fe7fe..69ddcf7 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -16,18 +16,33 @@ import os import subprocess import sys import logging -import platform -# Support only Ubuntu 18.04+ -distro, version, _ = platform.linux_distribution() -if distro != 'Ubuntu': - print('The Littlest JupyterHub currently supports Ubuntu Linux only') - sys.exit(1) -elif float(version) < 18.04: - print('The Littlest JupyterHub requires Ubuntu 18.04 or higher') - sys.exit(1) + +def get_os_release_variable(key): + """ + Return value for key from /etc/os-release + + /etc/os-release is a bash file, so should use bash to parse it. + + Returns empty string if key is not found. + """ + return subprocess.check_output([ + '/bin/bash', '-c', + "source /etc/os-release && echo $\{{key}\}".format(key=key) + ]).split() def main(): + + # Support only Ubuntu 18.04+ + distro = get_os_release_variable('ID') + version = float(get_os_release_variable('VERSION_ID')) + if distro != 'ubuntu': + print('The Littlest JupyterHub currently supports Ubuntu Linux only') + sys.exit(1) + elif float(version) < 18.04: + print('The Littlest JupyterHub requires Ubuntu 18.04 or higher') + sys.exit(1) + install_prefix = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh') hub_prefix = os.path.join(install_prefix, 'hub') From 43d9f02203f944e05b8a11d753d178dc3058c043 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 13 Nov 2018 14:24:21 -0800 Subject: [PATCH 5/7] Fix putting literal {}s in python format string --- bootstrap/bootstrap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 69ddcf7..58b815a 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -28,8 +28,8 @@ def get_os_release_variable(key): """ return subprocess.check_output([ '/bin/bash', '-c', - "source /etc/os-release && echo $\{{key}\}".format(key=key) - ]).split() + "source /etc/os-release && echo ${{{key}}}".format(key=key) + ]).trim() def main(): From ca38dcd41337780a6d1408ba589c3a2e9b7bb41b Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 13 Nov 2018 14:40:24 -0800 Subject: [PATCH 6/7] Decode output from subprocess before performing string operations --- bootstrap/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 58b815a..255bdb3 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -29,7 +29,7 @@ def get_os_release_variable(key): return subprocess.check_output([ '/bin/bash', '-c', "source /etc/os-release && echo ${{{key}}}".format(key=key) - ]).trim() + ]).decode().trim() def main(): From 599c9d37dd28eefcc782e66ff76baa455358f1c1 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 13 Nov 2018 14:46:16 -0800 Subject: [PATCH 7/7] Get '.strip()' right --- bootstrap/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 255bdb3..2424ae6 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -29,7 +29,7 @@ def get_os_release_variable(key): return subprocess.check_output([ '/bin/bash', '-c', "source /etc/os-release && echo ${{{key}}}".format(key=key) - ]).decode().trim() + ]).decode().strip() def main():