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