From aafce2a7f992c62fb9b0b5c4a78d047458d33c9a Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 2 Apr 2024 19:09:20 -0300 Subject: [PATCH 1/4] include env by groups to condarc --- .../jupyterhub/templates/jupyterhub_config.py | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/roles/jupyterhub/templates/jupyterhub_config.py b/roles/jupyterhub/templates/jupyterhub_config.py index 3cf195f8..c0c8a089 100644 --- a/roles/jupyterhub/templates/jupyterhub_config.py +++ b/roles/jupyterhub/templates/jupyterhub_config.py @@ -148,6 +148,25 @@ async def _get_batch_script(self, **subvars): class QHubHPCSpawner(QHubHPCSpawnerBase): pass +# batchspawner does not support auth_state correctly right now, using Keycloak client to retrieve user-info +@gen.coroutine +def _get_user_groups_state(spawner): + user_name = spawner.user.name + keycloak_admin = keycloak.KeycloakAdmin( + server_url="https://{{ traefik_domain | default(hostvars[groups['hpc_master'][0]].ansible_ssh_host) }}/auth/", + username="{{ keycloak_admin_username }}", + password="{{ keycloak_admin_password }}", + realm_name="{{ keycloak_realm }}", + user_realm_name="master", + verify=False) + username_uid = keycloak_admin.get_user_id(user_name) + _user_groups = keycloak_admin.get_user_groups(username_uid) + spawner.environment.update( + { + "USER_GROUPS": [group['name'] for group in _user_groups] + } + ) + c.JupyterHub.allow_named_servers = True c.JupyterHub.default_url = '/hub/home' @@ -155,6 +174,7 @@ class QHubHPCSpawner(QHubHPCSpawnerBase): c.JupyterHub.extra_handlers = [] c.JupyterHub.spawner_class = 'wrapspawner.ProfilesSpawner' +c.Spawner.pre_spawn_hook = _get_user_groups_state c.SlurmSpawner.start_timeout = {{ jupyterhub_config.spawner.start_timeout }} c.QHubHPCSpawner.default_url = '/lab' @@ -176,11 +196,13 @@ class QHubHPCSpawner(QHubHPCSpawnerBase): export PATH={{ miniforge_home }}/condabin:$PATH ''' -def populate_condarc(username): + +def populate_condarc(username, groups): """Generate condarc configuration string for the given username.""" + # # only run if conda-store is enabled and the jupyterhub service token is available condarc = json.dumps({ "envs_dirs": [ - f"/opt/conda-store/conda-store/{dir_name}/envs" for dir_name in [username, "filesystem"] + f"/opt/conda-store/conda-store/{dir_name}/envs" for dir_name in [username, "filesystem", *groups] ] }) return f"printf '{condarc}' > /home/{username}/.condarc\n" @@ -189,11 +211,7 @@ def populate_condarc(username): def generate_batch_script(spawner): """Generate a batch script for SLURM and JupyterHub based on spawner settings.""" username = spawner.user.name - - auth_state = yield spawner.user.get_auth_state() - if auth_state: - print(f"auth_state: {auth_state}") - print("#######################") + _groups = spawner.environment.get("USER_GROUPS", []) print(f"Generating batch script for {username}") @@ -244,7 +262,7 @@ def generate_batch_script(spawner): return "".join([ sbatch_headers, - populate_condarc(username), + populate_condarc(username, _groups), conda_store_headers, srun_jupyterhub_single_user ]) From ad949b655e0e928b630d11ade1b853a2825980fd Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 2 Apr 2024 19:09:57 -0300 Subject: [PATCH 2/4] rm unoused function --- roles/jupyterhub/templates/jupyterhub_config.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/roles/jupyterhub/templates/jupyterhub_config.py b/roles/jupyterhub/templates/jupyterhub_config.py index c0c8a089..20bf6976 100644 --- a/roles/jupyterhub/templates/jupyterhub_config.py +++ b/roles/jupyterhub/templates/jupyterhub_config.py @@ -17,21 +17,6 @@ from jupyterhub.traitlets import Callable from tornado import gen -# Find all conda environments that have dask jupyterlab, batchspawner, and jupyterhub installed -jupyterlab_packages = ['jupyterlab', 'batchspawner', 'jupyterhub'] -def conda_envs_w_packages(packages, names_only=False): - _environments = [] - output = subprocess.check_output(['conda', 'env', 'list', '--json']) - environments = json.loads(output)['envs'] - for environment in environments: - output = subprocess.check_output(['conda', 'list', '-p', environment, '--json']) - if set(packages) <= {_['name'] for _ in json.loads(output)}: - _environments.append((os.path.basename(environment), environment)) - if names_only: - return [env_name for env_name, path in _environments] - return _environments - - # Allow gathering of jupyterhub prometheus metrics c.JupyterHub.authenticate_prometheus = False From a2d5e039cc601feb98cb77d9532d71b4ecfabf70 Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 2 Apr 2024 19:15:57 -0300 Subject: [PATCH 3/4] fix env var update to comply with string reqs. --- roles/jupyterhub/templates/jupyterhub_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/jupyterhub/templates/jupyterhub_config.py b/roles/jupyterhub/templates/jupyterhub_config.py index 20bf6976..66131f93 100644 --- a/roles/jupyterhub/templates/jupyterhub_config.py +++ b/roles/jupyterhub/templates/jupyterhub_config.py @@ -148,7 +148,7 @@ def _get_user_groups_state(spawner): _user_groups = keycloak_admin.get_user_groups(username_uid) spawner.environment.update( { - "USER_GROUPS": [group['name'] for group in _user_groups] + "USER_GROUPS": json.dumps([group['name'] for group in _user_groups]) } ) @@ -196,7 +196,7 @@ def populate_condarc(username, groups): def generate_batch_script(spawner): """Generate a batch script for SLURM and JupyterHub based on spawner settings.""" username = spawner.user.name - _groups = spawner.environment.get("USER_GROUPS", []) + _groups = json.loads(spawner.environment.get("USER_GROUPS", "[]")) print(f"Generating batch script for {username}") From c6496a194e7ca1631f352b48bd20454422fa8250 Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 9 Apr 2024 17:45:02 -0300 Subject: [PATCH 4/4] include docs on env creation and shared group roles --- docs/user-guide.md | 49 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index 786db77b..7c964a4b 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -4,10 +4,50 @@ ### Adding and updating Conda environments -Managing conda environments is done via -[Conda-Store](https://conda-store.readthedocs.io/en/latest/). You can -visit Conda-Store within your QHub-HPC deployment at -`https:///conda-store/`. +#### What is `conda-store`? + +[`conda-store`][conda-store-docs] is a Python package that serves _identical_ +`conda` environments by controlling the environment lifecycle. +It ensures that the management, building, and serving of environments is as +identical as possible and seamless for the end users. + +All environments in Nebari are served through `conda-store`. + +Using `conda-store`, Nebari admins can track specific files or directories for +changes in environment specifications. They can manage environments using the +web interface, REST API, or the command-line utility (CLI). + +#### Exploring the conda-store Interface + +Access conda-store through your domain ``, log +in to authenticate, and navigate the dashboard to view account details and permissions. + +- Key sections include User, Namespaces, and Permissions, which dictate access + levels and capabilities. + +![conda-store default main page, before authentication, login button highlighted](https://conda.store/assets/images/login-1346a06ed408f74937da23b0a1c6fda3.png) + +#### Creating a New Environment + +Environments are created in conda-store using a YAML file. Post-creation, the +environment can be managed through the conda-store UI, allowing for edits and +build status monitoring. + +More details on creating environments can be found in the [conda-store documentation](https://conda.store/conda-store-ui/tutorials/create-envs). + +Package installation should be done via the conda-store web interface to avoid +inconsistencies and limitations associated with command line installations. + +#### **Note on Shared Namespaces** + +Access to shared namespaces in conda-store depends on user assignment to groups +in Keycloak and the corresponding permissions within those groups. + +By default, NebarSlurm is deployed with the following groups: `admin`, `developer`, +and `analyst` (in roughly descending order of permissions and scope). Note that +such group names will differ on a per-instance basis. Check +[Conda-store authorization model](https://conda-store.readthedocs.io/en/latest/contributing.html#authorization-model) +for more details on conda-store authorization. ## ContainDS Dashboards @@ -43,3 +83,4 @@ Dask support is based on: - [dask distributed](https://distributed.dask.org/en/latest/) - [dask gateway](https://gateway.dask.org/) +[conda-store-docs]: https://conda-store.readthedocs.io/