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 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 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..f73290c 100644 --- a/setup.py +++ b/setup.py @@ -18,11 +18,11 @@ setup( 'backoff', 'requests', 'bcrypt', - 'jupyterhub-traefik-proxy==0.2.*' + 'jupyterhub-traefik-proxy==0.2.*', ], entry_points={ 'console_scripts': [ 'tljh-config = tljh.config:main', - ], + ] }, ) diff --git a/tljh/configurer.py b/tljh/configurer.py index 018c7d2..62ed375 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -66,6 +66,9 @@ default = { 'concurrency': 5, 'users': False, 'max_age': 0 + }, + 'configurator': { + 'enabled': False } } } @@ -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/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/user_creating_spawner.py b/tljh/user_creating_spawner.py index 35bd0b6..6490596 100755 --- a/tljh/user_creating_spawner.py +++ b/tljh/user_creating_spawner.py @@ -1,9 +1,11 @@ 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(SystemdSpawner): +class CustomSpawner(SystemdSpawner): """ SystemdSpawner with user creation on spawn. @@ -32,3 +34,12 @@ class UserCreatingSpawner(SystemdSpawner): user.ensure_user_group(system_username, group) 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,), {})