From 32e0f99c3c1934ebf2e7ea5a21c2fd7a3b35a136 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Thu, 1 Apr 2021 14:26:54 +0300 Subject: [PATCH 1/6] Add the jupyterhub-configurator service --- integration-tests/plugins/simplest/setup.py | 2 -- setup.py | 8 ++++-- tljh/configurer.py | 28 +++++++++++++++++++-- tljh/schemas/__init__.py | 0 tljh/schemas/tljh_configurator.py | 27 ++++++++++++++++++++ tljh/user_creating_spawner.py | 3 ++- 6 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tljh/schemas/__init__.py create mode 100644 tljh/schemas/tljh_configurator.py diff --git a/integration-tests/plugins/simplest/setup.py b/integration-tests/plugins/simplest/setup.py index 9a34d26..a5b0857 100644 --- a/integration-tests/plugins/simplest/setup.py +++ b/integration-tests/plugins/simplest/setup.py @@ -5,5 +5,3 @@ setup( entry_points={"tljh": ["simplest = tljh_simplest"]}, py_modules=["tljh_simplest"], ) - - diff --git a/setup.py b/setup.py index 5352f2a..e1bb699 100644 --- a/setup.py +++ b/setup.py @@ -18,11 +18,15 @@ setup( 'backoff', 'requests', 'bcrypt', - 'jupyterhub-traefik-proxy==0.2.*' + 'jupyterhub-traefik-proxy==0.2.*', + 'jupyterhub-configurator @ git+https://github.com/yuvipanda/jupyterhub-configurator@ecca97e016e9a939dd48c6c0e66c40e4e2951fa7', ], entry_points={ + 'jupyterhub_configurator': [ + 'schema = tljh.schemas.tljh_configurator', + ], 'console_scripts': [ 'tljh-config = tljh.config:main', - ], + ] }, ) diff --git a/tljh/configurer.py b/tljh/configurer.py index 018c7d2..0281325 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -66,6 +66,9 @@ default = { 'concurrency': 5, 'users': False, 'max_age': 0 + }, + 'configurator': { + 'enabled': True } } } @@ -175,8 +178,8 @@ def update_userlists(c, config): """ users = config['users'] - c.Authenticator.whitelist = set(users['allowed']) - c.Authenticator.blacklist = set(users['banned']) + c.Authenticator.allowed_users = set(users['allowed']) + c.Authenticator.blocked_users = set(users['banned']) c.Authenticator.admin_users = set(users['admin']) @@ -249,10 +252,31 @@ def set_cull_idle_service(config): return cull_service +def set_configurator(config): + """ + Set the JupyterHub Configurator service + """ + HERE = os.path.abspath(os.path.dirname(__file__)) + configurator_cmd = [ + sys.executable, "-m", "jupyterhub_configurator.app", + f"--Configurator.config_file={HERE}/jupyterhub_configurator_config.py" + ] + configurator_service = { + 'name': 'configurator', + 'url': 'http://127.0.0.1:10101', + 'command': configurator_cmd, + } + + return configurator_service + + def update_services(c, config): c.JupyterHub.services = [] + if config['services']['cull']['enabled']: c.JupyterHub.services.append(set_cull_idle_service(config)) + if config['services']['configurator']['enabled']: + c.JupyterHub.services.append(set_configurator(config)) def _merge_dictionaries(a, b, path=None, update=True): diff --git a/tljh/schemas/__init__.py b/tljh/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tljh/schemas/tljh_configurator.py b/tljh/schemas/tljh_configurator.py new file mode 100644 index 0000000..abd0335 --- /dev/null +++ b/tljh/schemas/tljh_configurator.py @@ -0,0 +1,27 @@ +from jupyterhub_configurator.hooks import hookimpl + +@hookimpl +def jupyterhub_configurator_fields(): + return { + "schema.default_interface": { + "type": "string", + "traitlet": "Spawner.default_url", + "title": "Default User Interface", + "enum": ["/tree", "/lab", "/nteract"], + "default": "/tree", + "enumMetadata": { + "/tree": { + "title": "Classic Notebook", + "description": "The original single-document interface for creating Jupyter Notebooks.", + }, + "/lab": { + "title": "JupyterLab", + "description": "A Powerful next generation notebook interface", + }, + "/nteract": { + "title": "Nteract", + "description": "Nteract notebook interface", + }, + }, + } + } diff --git a/tljh/user_creating_spawner.py b/tljh/user_creating_spawner.py index 35bd0b6..bf75675 100755 --- a/tljh/user_creating_spawner.py +++ b/tljh/user_creating_spawner.py @@ -2,8 +2,9 @@ from tljh.normalize import generate_system_username from tljh import user from systemdspawner import SystemdSpawner from traitlets import Dict, Unicode, List +from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin -class UserCreatingSpawner(SystemdSpawner): +class UserCreatingSpawner(ConfiguratorSpawnerMixin, SystemdSpawner): """ SystemdSpawner with user creation on spawn. From 3ff80378c971bc10126ea743a71666fc41474251 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 5 Apr 2021 21:42:01 +0300 Subject: [PATCH 2/6] Make configurator disabled by default --- tljh/configurer.py | 2 +- tljh/user_creating_spawner.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tljh/configurer.py b/tljh/configurer.py index 0281325..62ed375 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -68,7 +68,7 @@ default = { 'max_age': 0 }, 'configurator': { - 'enabled': True + 'enabled': False } } } diff --git a/tljh/user_creating_spawner.py b/tljh/user_creating_spawner.py index bf75675..f05cf0e 100755 --- a/tljh/user_creating_spawner.py +++ b/tljh/user_creating_spawner.py @@ -1,10 +1,10 @@ from tljh.normalize import generate_system_username from tljh import user +from tljh import configurer from systemdspawner import SystemdSpawner from traitlets import Dict, Unicode, List -from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin -class UserCreatingSpawner(ConfiguratorSpawnerMixin, SystemdSpawner): +class CustomSpawner(SystemdSpawner): """ SystemdSpawner with user creation on spawn. @@ -33,3 +33,8 @@ class UserCreatingSpawner(ConfiguratorSpawnerMixin, SystemdSpawner): user.ensure_user_group(system_username, group) return super().start() +cfg = configurer.load_config() +if cfg['services']['configurator']['enabled']: + UserCreatingSpawner = type('UserCreatingSpawner', (ConfiguratorSpawnerMixin, CustomSpawner), {}) +else: + UserCreatingSpawner = type('UserCreatingSpawner', (CustomSpawner,), {}) From c8f86833b718b0bfafb664a39c658489ef158460 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 5 Apr 2021 21:52:37 +0300 Subject: [PATCH 3/6] Install git too --- .github/workflows/unit-test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index a3f15cb..ce047fd 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -12,9 +12,9 @@ jobs: - uses: actions/setup-python@v2 with: python-version: 3.6 - - name: Install and setup venv + - name: Install venv, git and setup venv run: | - apt-get update --yes && apt-get install --yes python3-venv + apt-get update --yes && apt-get install --yes python3-venv git python3 -m venv /srv/venv echo '/srv/venv/bin' >> $GITHUB_PATH - name: Cache pip deps From 0e7c30296d863e73678e2c1c89c17a73510e9102 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Tue, 6 Apr 2021 13:44:29 +0300 Subject: [PATCH 4/6] Move schema to configurator --- setup.py | 4 ---- tljh/installer.py | 1 + tljh/schemas/__init__.py | 0 tljh/schemas/tljh_configurator.py | 27 --------------------------- tljh/user_creating_spawner.py | 1 + 5 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 tljh/schemas/__init__.py delete mode 100644 tljh/schemas/tljh_configurator.py diff --git a/setup.py b/setup.py index e1bb699..f73290c 100644 --- a/setup.py +++ b/setup.py @@ -19,12 +19,8 @@ setup( 'requests', 'bcrypt', 'jupyterhub-traefik-proxy==0.2.*', - 'jupyterhub-configurator @ git+https://github.com/yuvipanda/jupyterhub-configurator@ecca97e016e9a939dd48c6c0e66c40e4e2951fa7', ], entry_points={ - 'jupyterhub_configurator': [ - 'schema = tljh.schemas.tljh_configurator', - ], 'console_scripts': [ 'tljh-config = tljh.config:main', ] diff --git a/tljh/installer.py b/tljh/installer.py index 8c89ccf..cfbbeda 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -141,6 +141,7 @@ def ensure_jupyterhub_package(prefix): "oauthenticator==0.10.0", "jupyterhub-idle-culler==1.0", "chardet==3.0.4", + "git+https://github.com/yuvipanda/jupyterhub-configurator@317759e17c8e48de1b1352b836dac2a230536dba" ], ) traefik.ensure_traefik_binary(prefix) diff --git a/tljh/schemas/__init__.py b/tljh/schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tljh/schemas/tljh_configurator.py b/tljh/schemas/tljh_configurator.py deleted file mode 100644 index abd0335..0000000 --- a/tljh/schemas/tljh_configurator.py +++ /dev/null @@ -1,27 +0,0 @@ -from jupyterhub_configurator.hooks import hookimpl - -@hookimpl -def jupyterhub_configurator_fields(): - return { - "schema.default_interface": { - "type": "string", - "traitlet": "Spawner.default_url", - "title": "Default User Interface", - "enum": ["/tree", "/lab", "/nteract"], - "default": "/tree", - "enumMetadata": { - "/tree": { - "title": "Classic Notebook", - "description": "The original single-document interface for creating Jupyter Notebooks.", - }, - "/lab": { - "title": "JupyterLab", - "description": "A Powerful next generation notebook interface", - }, - "/nteract": { - "title": "Nteract", - "description": "Nteract notebook interface", - }, - }, - } - } diff --git a/tljh/user_creating_spawner.py b/tljh/user_creating_spawner.py index f05cf0e..14307f0 100755 --- a/tljh/user_creating_spawner.py +++ b/tljh/user_creating_spawner.py @@ -3,6 +3,7 @@ from tljh import user from tljh import configurer from systemdspawner import SystemdSpawner from traitlets import Dict, Unicode, List +from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin class CustomSpawner(SystemdSpawner): """ From 4fc26bc5ef1ffcbcf0aa31e408962b0f620d8ee0 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Tue, 6 Apr 2021 14:00:28 +0300 Subject: [PATCH 5/6] Explain how we contruct the spawner --- tljh/user_creating_spawner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tljh/user_creating_spawner.py b/tljh/user_creating_spawner.py index 14307f0..6490596 100755 --- a/tljh/user_creating_spawner.py +++ b/tljh/user_creating_spawner.py @@ -35,7 +35,11 @@ class CustomSpawner(SystemdSpawner): return super().start() cfg = configurer.load_config() +# Use the jupyterhub-configurator mixin only if configurator is enabled +# otherwise, any bugs in the configurator backend will stop new user spawns! if cfg['services']['configurator']['enabled']: + # Dynamically create the Spawner class using `type`(https://docs.python.org/3/library/functions.html?#type), + # based on whether or not it should inherit from ConfiguratorSpawnerMixin UserCreatingSpawner = type('UserCreatingSpawner', (ConfiguratorSpawnerMixin, CustomSpawner), {}) else: UserCreatingSpawner = type('UserCreatingSpawner', (CustomSpawner,), {}) From 5786dce3675c392b8791e297116699bbce244d1c Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Tue, 6 Apr 2021 18:04:43 +0300 Subject: [PATCH 6/6] Document enabling the configurator --- docs/topic/index.rst | 1 + docs/topic/jupyterhub-configurator.rst | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/topic/jupyterhub-configurator.rst diff --git a/docs/topic/index.rst b/docs/topic/index.rst index 6e5744f..d4366a7 100644 --- a/docs/topic/index.rst +++ b/docs/topic/index.rst @@ -17,3 +17,4 @@ Topic guides provide in-depth explanations of specific topics. authenticator-configuration escape-hatch idle-culler + jupyterhub-configurator diff --git a/docs/topic/jupyterhub-configurator.rst b/docs/topic/jupyterhub-configurator.rst new file mode 100644 index 0000000..642a996 --- /dev/null +++ b/docs/topic/jupyterhub-configurator.rst @@ -0,0 +1,28 @@ +.. _topic/jupyterhub-configurator: + +======================= +JupyterHub Configurator +======================= + +The `JupyterHub configurator `_ allows admins to change a subset of hub settings via a GUI. + +.. image:: ../images/jupyterhub-configurator.png + :alt: Changing the default JupyterHub interface + +Enabling the configurator +========================= + +Because the configurator is under continue development and it might change over time, it is disabled by default in TLJH. +If you want to experiment with it, it can be enabled using ``tljh-config``: + +.. code-block:: bash + + sudo tljh-config set services.configurator.enabled True + sudo tljh-config reload + +Accessing the Configurator +========================== + +After enabling the configurator using ``tljh-config``, the service will only be available to hub admins, from within the control panel. +The configurator can be accessed from under ``Services`` in the top navigation bar. It will ask to authenticate, so it knows the user is an admin. +Once done, the configurator interface will be available. \ No newline at end of file