From 1494a5d5d2d1dcaf9c51c284953330ddf8177475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= <valtri@civ.zcu.cz> Date: Tue, 2 Jul 2024 16:26:08 +0000 Subject: [PATCH] Testing: deploy changes in ownCloud integration --- cesnet-central/deployments/fullhub.yaml | 4 +- testing/deployments/hub.yaml | 138 +++++++++++++++++------- 2 files changed, 102 insertions(+), 40 deletions(-) diff --git a/cesnet-central/deployments/fullhub.yaml b/cesnet-central/deployments/fullhub.yaml index 13aa549..6d159a3 100644 --- a/cesnet-central/deployments/fullhub.yaml +++ b/cesnet-central/deployments/fullhub.yaml @@ -52,9 +52,9 @@ singleuser: - name: owncloud-home mountPath: '/home/jovyan:shared' - name: owncloud-shared - mountPath: '/owncloud/shared:shared' + mountPath: '/owncloud/Shared:shared' - name: owncloud-spaces - mountPath: '/owncloud/spaces:shared' + mountPath: '/owncloud/Spaces:shared' # - name: scratch # mountPath: '/scratch' memory: diff --git a/testing/deployments/hub.yaml b/testing/deployments/hub.yaml index 7a5ef24..b41cf89 100644 --- a/testing/deployments/hub.yaml +++ b/testing/deployments/hub.yaml @@ -30,9 +30,11 @@ singleuser: # sizeLimit problematic in this environment, # not needed for remote mounts empty_dir: - - name: owncloud - # sizeLimit problematic in this environment, - # not needed for remote mounts + - name: owncloud-home + empty_dir: + - name: owncloud-shared + empty_dir: + - name: owncloud-spaces empty_dir: # - name: scratch # ephemeral: @@ -48,8 +50,12 @@ singleuser: mountPath: "/cvmfs:shared" - name: b2drop mountPath: '/home/jovyan/b2drop:shared' - - name: owncloud + - name: owncloud-home mountPath: '/home/jovyan:shared' + - name: owncloud-shared + mountPath: '/owncloud/Shared:shared' + - name: owncloud-spaces + mountPath: '/owncloud/Spaces:shared' # - name: scratch # mountPath: '/scratch' memory: @@ -96,9 +102,9 @@ singleuser: nvidia.com/gpu: 1 cmd: jupyterhub-singleuser-webdav-wrapper extraFiles: - wait-owncloud.sh: + wait-remote-home.sh: mode: 0755 - mountPath: /usr/local/bin/jupyterhub-singleuser-webdav-wrapper + mountPath: /usr/local/bin/jupyterhub-wait-remote-home stringData: |- #! /bin/sh @@ -115,6 +121,12 @@ singleuser: sleep 0.5 i=$((i+1)) done + singleuser-webdav-wrapper.sh: + mode: 0755 + mountPath: /usr/local/bin/jupyterhub-singleuser-webdav-wrapper + stringData: |- + #! /bin/sh + /usr/local/bin/jupyterhub-wait-remote-home cd . @@ -163,10 +175,13 @@ hub: egi-notebooks-b2drop: |- {% raw %} import base64 + import json from jinja2 import BaseLoader from jinja2 import Environment from egi_notebooks_hub.onedata import OnedataSpawner from kubernetes_asyncio.client.rest import ApiException + from tornado.httpclient import AsyncHTTPClient, HTTPClientError, HTTPRequest + class B2DropSpawner(OnedataSpawner): async def auth_state_hook(self, spawner, auth_state): @@ -243,45 +258,92 @@ hub: 'b2drop-pwd': formdata.get('b2drop-pwd', [None])[0]}) return data + class WebDavOIDCSpawner(B2DropSpawner): + # ownCloud Infinite Scale parameters + # (https://owncloud.dev/apis/http/graph/spaces/#list-my-spaces-get-medrives) + OCIS_URL = "https://ocis.aaitest.owncloud.works" + # personal space + OCIS_PERSONAL_SPACE = "/graph/v1.0/me/drives?%24filter=driveType+eq+personal" + # shared space + OCIS_SHARED_WITH_ME = "/graph/v1.0/me/drives?%24filter=driveType+eq+virtual" + # otter spaces + OCIS_SPACES = "/graph/v1.0/me/drives?%24filter=driveType+eq+project" + + async def append_owncloud_sidecar(self, spawner, type, query, fallback_url=None, headers={}): + owncloud_url = fallback_url + http_client = AsyncHTTPClient() + req = HTTPRequest( + self.OCIS_URL + query, + headers=headers, + method="GET", + ) + try: + resp = await http_client.fetch(req) + body = json.loads(resp.body.decode("utf8", "replace")) + self.log.debug("OCIS response: %s", body) + if "value" in body: + ocis_infos = body["value"] + if len(ocis_infos) >= 1 and "root" in ocis_infos[0]: + owncloud_url = ocis_infos[0]["root"].get("webDavUrl", None) + except HTTPClientError as e: + self.log.error("can't query ownCloud: %s", e) + self.log.info("ownCloud %s URL: %s", type, owncloud_url) + + if owncloud_url is None: + return + + volume_mounts = [ + {"mountPath": "/owncloud:shared", "name": "owncloud-" + type}, + {"mountPath": self.token_mount_path, "name": self.token_secret_volume_name, "readOnly": True}, + ] + spawner.extra_containers.append( + { + "name": "owncloud-" + type, + "image": "eginotebooks/webdav-rclone-sidecar:sha-95b4f95", + "args": ["bearer_token_command=cat " + self.token_path], + "env": [ + {"name": "WEBDAV_URL", "value": owncloud_url}, + {"name": "WEBDAV_VENDOR", "value": "owncloud"}, + # XXX: strict permissions needed for .local/share/jupyter/runtime/jupyter_cookie_secret + # quicker directory cache and polling + {"name": "MOUNT_OPTS", "value": "--file-perms=0600 --dir-perms=0770 --dir-cache-time=1m0s --poll-interval=0m20s"}, + {"name": "MOUNT_PATH", "value": "/owncloud"}, + # default mode is "full" + {"name": "VFS_CACHE_MODE", "value": "full"}, + ], + "resources": self.sidecar_resources, + "securityContext": { + "runAsUser": 1000, + "fsUser": 1000, + "fsGroup": 100, + "privileged": True, + "capabilities": {"add": ["SYS_ADMIN"]}, + }, + "volumeMounts": volume_mounts, + } + ) + async def pre_spawn_hook(self, spawner): await super(WebDavOIDCSpawner, self).pre_spawn_hook(spawner) auth_state = await self.user.get_auth_state() # volume name as in EGI spawner - token_secret_volume_name = self._expand_user_properties( + self.token_secret_volume_name = self._expand_user_properties( self.token_secret_volume_name_template ) - token_path = os.path.join(self.token_mount_path, "access_token") - volume_mounts = [ - {"mountPath": "/owncloud:shared", "name": "owncloud"}, - {"mountPath": self.token_mount_path, "name": token_secret_volume_name, "readOnly": True}, - ] - spawner.extra_containers.append( - { - "name": "owncloud", - "image": "eginotebooks/webdav-rclone-sidecar:sha-95b4f95", - "args": ["bearer_token_command=cat " + token_path], - "env": [ - {"name": "WEBDAV_URL", "value": "https://ocis.aaitest.owncloud.works/remote.php/webdav/"}, - {"name": "WEBDAV_VENDOR", "value": "owncloud"}, - # XXX: strict permissions needed for .local/share/jupyter/runtime/jupyter_cookie_secret - # quicker directory cache and polling - {"name": "MOUNT_OPTS", "value": "--file-perms=0600 --dir-perms=0770 --dir-cache-time=1m0s --poll-interval=0m20s"}, - {"name": "MOUNT_PATH", "value": "/owncloud"}, - # default mode is "full" - {"name": "VFS_CACHE_MODE", "value": "full"}, - ], - "resources": self.sidecar_resources, - "securityContext": { - "runAsUser": 1000, - "fsUser": 1000, - "fsGroup": 100, - "privileged": True, - "capabilities": {"add": ["SYS_ADMIN"]}, - }, - "volumeMounts": volume_mounts, - } - ) + self.token_path = os.path.join(self.token_mount_path, "access_token") + + access_token = auth_state.get("access_token", None) + headers = { + "Accept": "application/json", + "User-Agent": "JupyterHub", + "Authorization": "Bearer %s" % access_token, + } + + # ownCloud user home + await self.append_owncloud_sidecar(spawner, "home", self.OCIS_PERSONAL_SPACE, headers=headers) + await self.append_owncloud_sidecar(spawner, "shared", self.OCIS_SHARED_WITH_ME, headers=headers) + await self.append_owncloud_sidecar(spawner, "spaces", self.OCIS_SPACES, headers=headers) c.JupyterHub.spawner_class = WebDavOIDCSpawner -- GitLab