From 449eeef7eb8a23cc3270e4496ae6433da07a5e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= <valtri@civ.zcu.cz> Date: Thu, 20 Jun 2024 18:21:46 +0000 Subject: [PATCH] Implement changes in ownCloud integration --- cesnet-central/deployments/fullhub.yaml | 125 +++++++++++++++++------- 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/cesnet-central/deployments/fullhub.yaml b/cesnet-central/deployments/fullhub.yaml index 8d58387..7dcbd32 100644 --- a/cesnet-central/deployments/fullhub.yaml +++ b/cesnet-central/deployments/fullhub.yaml @@ -29,9 +29,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: @@ -47,8 +49,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: '/home/jovyan/Shared:shared' + - name: owncloud-spaces + mountPath: '/home/jovyan/Spaces:shared' # - name: scratch # mountPath: '/scratch' memory: @@ -154,6 +160,8 @@ hub: 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): @@ -230,45 +238,90 @@ 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) + ocis_infos = json.loads(resp.body.decode("utf8", "replace")) + self.log.info("OCIS response: %s", ocis_infos) + if len(ocis_infos) >= 1 and "root" in ocis_infos[0]: + owncloud_url = get(ocis_infos[0]["root"], "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, fallback_url=self.OCIS_URL + "/remote.php/webdav/", 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