---
proxy:
  service:
    type: NodePort

ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: "nginx"
    kubernetes.io/tls-acme: "true"
  hosts:
    - "{{ notebooks_hostname }}"
  tls:
    - hosts:
        - "{{ notebooks_hostname }}"
      secretName: acme-tls-fullhub

singleuser:
  # keep resource limits in sync with:
  # - profileList
  storage:
    type: none
    extraVolumes:
      - name: cvmfs-host
        hostPath:
          path: /cvmfs
          type: Directory
      - name: owncloud-home
        empty_dir:
      # - name: scratch
      #   ephemeral:
      #     volumeClaimTemplate:
      #       spec:
      #         accessModes: [ "ReadWriteOnce" ]
      #         storageClassName: local-path
      #         resources:
      #           requests:
      #             storage: "10Gi"
    extraVolumeMounts:
      - name: cvmfs-host
        mountPath: "/cvmfs:shared"
      - name: owncloud-home
        mountPath: '/home/jovyan:shared'
      # - name: scratch
      #   mountPath: '/scratch'
  memory:
    limit: 4G
    guarantee: 128M
  cpu:
    limit: 2
    guarantee: .02
  defaultUrl: "/lab"
  image:
    name: eginotebooks/single-user-eosc
    tag: "sha-dea4fa2"
  profileList:
    - display_name: Small Environment - 2 vCPU / 4 GB RAM (non-collaboratice)
      description: >
        The notebook environment includes Python, R, Julia and Octave kernels. Non-collaborative.
      default: true
      kubespawner_override:
        args:
          - "--CondaKernelSpecManager.env_filter='/opt/conda$'"
        extra_annotations:
          "egi.eu/flavor": "small-environment-2-vcpu-4-gb-ram"
    - display_name: Small Environment - 2 vCPU / 4 GB RAM (collaboratice)
      description: >
        The notebook environment includes Python, R, Julia and Octave kernels. Collaborative.
      kubespawner_override:
        args:
          - "--CondaKernelSpecManager.env_filter='/opt/conda$'"
        image: "valtri/single-user:jupyter-4e-collab"
        extra_annotations:
          "egi.eu/flavor": "small-environment-2-vcpu-4-gb-ram"
    - display_name: Small Environment - 2 vCPU / 4 GB RAM
      description: >
        The notebook environment includes Python, R, Julia and Octave kernels.
      default: true
      kubespawner_override:
        args:
          - "--CondaKernelSpecManager.env_filter='/opt/conda$'"
        extra_annotations:
          "egi.eu/flavor": "small-environment-2-vcpu-4-gb-ram"
      vo_claims:
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:2-vcpu-4-gb-ram:act:ppa
    - display_name: Medium Environment - 4 vCPU / 8 GB RAM
      description: >
        The notebook environment includes Python, R, Julia and Octave kernels.
      kubespawner_override:
        args:
          - "--CondaKernelSpecManager.env_filter='/opt/conda$'"
        extra_annotations:
          "egi.eu/flavor": "medium-environment-4-vcpu-8-gb-ram"
        cpu_guarantee: 0.4
        cpu_limit: 4
        mem_guarantee: 1G
        mem_limit: 8G
      vo_claims:
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:4-vcpu-8-gb-ram:act:ppa
    - display_name: Large Environment - 8 vCPU / 16 GB RAM / GPU
      description: >
        The notebook environment includes Python, R, Julia and Octave kernels with GPU.
      kubespawner_override:
        args:
          - "--CondaKernelSpecManager.env_filter='/opt/conda$'"
        cpu_guarantee: 0.8
        cpu_limit: 8
        mem_guarantee: 2G
        mem_limit: 16G
        extra_annotations:
          "egi.eu/flavor": "large-environment-8-vcpu-16-gb-ram-gpu"
        extra_resource_guarantees:
          nvidia.com/gpu: 1
        extra_resource_limits:
          nvidia.com/gpu: 1
      vo_claims:
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:8-vcpu-16-gb-ram-gpu:act:ppa
  cmd: jupyterhub-singleuser-webdav-wrapper
  extraFiles:
    wait-remote-home.sh:
      mode: 0755
      mountPath: /usr/local/bin/jupyterhub-wait-remote-home
      stringData: |-
        #! /bin/sh
        i=0
        while ! grep '^webdav-fs: /home/jovyan ' /proc/mounts && test $i -lt 30; do
          echo 'Waiting for ownClound mount...'
          sleep 0.5
          i=$((i+1))
        done
    singleuser-webdav-wrapper.sh:
      mode: 0755
      mountPath: /usr/local/bin/jupyterhub-singleuser-webdav-wrapper
      #NotebookNotary.db_file=':memory:' is used due to issues
      #notebook notary file was causing in ~/.jupyter in ownCloud mount
      #
      #LabApp.custom_css=True allows to use custom CSS for EOSC style
      #
      #ResourceUseDisplay.mem_warning_threshold=0.25 sets for resource-usage
      #extension to warn about used memory when only 25% of memory is available
      #which is also used by EGI notebooks-resource-warning extension
      stringData: |-
        #! /bin/sh
        #
        # Dirty hack to make remote mount on home directory working properly:
        #
        # 1) wait for webdav sidecar image to kick in
        # 2) change directory to the mounted version of itself
        # 3) launch notebook server
        #
        /usr/local/bin/jupyterhub-wait-remote-home

        # Disables RTC extension. To enable it set this env variable in kubespawner_override
        # to JUPYTERHUB_ALLOW_TOKEN_IN_URL="1"
        if [ -z JUPYTERHUB_ALLOW_TOKEN_IN_URL ]; then
          jupyter-labextension  disable @jupyter/collaboration-extension
        fi

        cd .
        exec jupyterhub-singleuser \
          --FileCheckpoints.checkpoint_dir='/home/jovyan/.notebookCheckpoints' \
          --NotebookNotary.db_file=':memory:' \
          --LabApp.custom_css=True \
          --ResourceUseDisplay.mem_warning_threshold=0.25 \
          "$@"

hub:
  services:
    status:
      url: "http://status-web/"
      admin: true
    jwt:
      url: "http://jwt/"
      display: false
  # recommended to keep in sync with common/playbooks/files/jupyterhub-jwt.yaml
  image:
    name: eginotebooks/hub
    tag: "sha-ea893ce"
  config:
    Authenticator:
      enable_auth_state: true
      admin_users:
        # valtri@civ.zcu.cz
        - c36b18fe-e03a-4a22-ab14-5965e0171410@eosc-federation.eu
      allowed_groups:
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:2-vcpu-4-gb-ram:act:ppa
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:4-vcpu-8-gb-ram:act:ppa
        - urn:geant:eosc-federation.eu:res:notebooks.open-science-cloud.ec.europa.eu:8-vcpu-16-gb-ram-gpu:act:ppa
        - urn:geant:eosc-federation.eu:testing:group:eosc
      admin_groups:
        - urn:geant:eosc-federation.eu:group:asg:notebooks.open-science-cloud.ec.europa.eu:role=admin
      claim_groups_key: "entitlements"
    EGICheckinAuthenticator:
      checkin_host: "{{ secret['checkin_host'] }}"
      authorize_url: "https://{{ secret['checkin_host'] }}/OIDC/authorization"
      token_url: "https://{{ secret['checkin_host'] }}/OIDC/token"
      userdata_url: "https://{{ secret['checkin_host'] }}/OIDC/userinfo"
      introspect_url: "https://{{ secret['checkin_host'] }}/OIDC/introspect"
      client_id: "{{ secret['client_id'] }}"
      client_secret: "{{ secret['client_secret'] }}"
      oauth_callback_url: "https://{{ notebooks_hostname }}/hub/oauth_callback"
      openid_configuration_url: "https://{{ secret['checkin_host'] }}/.well-known/openid-configuration"
      scope: ["openid", "profile", "email", "offline_access", "entitlements"]
      username_claim: "sub"
      extra_authorize_params:
        prompt: consent
    JupyterHub:
      admin_access: true
      authenticate_prometheus: false
      authenticator_class: egi_notebooks_hub.egiauthenticator.EOSCNodeAuthenticator
      # spawner_class: (in egi-notebooks-b2drop)
    LabApp:
      check_for_updates_class: jupyterlab.NeverCheckForUpdate
  extraConfig:
    egi-notebooks-welcome: |-
      from egi_notebooks_hub.welcome import WelcomeHandler
      c.JupyterHub.default_url = "/welcome"
      c.JupyterHub.extra_handlers = [(r'/welcome', WelcomeHandler)]
    egi-notebooks-b2drop: |-
{%- raw %}
      import json
      from egi_notebooks_hub.onedata import OnedataSpawner
      from tornado.httpclient import AsyncHTTPClient, HTTPClientError, HTTPRequest

      class WebDavOIDCSpawner(OnedataSpawner):
          # 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

              if type == "home":
                  #Jupyter side
                  subpath = ""
                  #ownCloud backend side
                  remote_path = "/notebooks_service"
              else:
                  #Jupyter side
                  subpath = "/" + type.capitalize()
                  #ownCloud backend side
                  remote_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" + subpath},
                  # default mode is "full"
                  {"name": "VFS_CACHE_MODE", "value": "full"},
                  # remote path to mount on ownCloud backend
                  {"name": "REMOTE_PATH", "value": remote_path}
              ]
              if type != "home":
                  env.append({"name": "MOUNT_WAIT_POINT", "value": "webdav-fs: /owncloud fuse.rclone"})
              volume_mounts = [
                  {"mountPath": "/owncloud:shared", "name": "owncloud-home"},
                  {"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-0f42313",
                      "args": ["bearer_token_command=cat " + self.token_path],
                      "env": env,
                      "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
              self.token_secret_volume_name = self._expand_user_properties(
                self.token_secret_volume_name_template
              )
              self.token_path = os.path.join(self.token_mount_path, "access_token")

              if auth_state:
                  access_token = auth_state.get("access_token", None)
                  headers = {
                      "Accept": "application/json",
                      "User-Agent": "JupyterHub",
                      "Authorization": "Bearer %s" % access_token,
                  }

                  await self.append_owncloud_sidecar(spawner, "home", self.OCIS_PERSONAL_SPACE, headers=headers)
                  await self.append_owncloud_sidecar(spawner, "shares", self.OCIS_SHARED_WITH_ME, headers=headers)
                  await self.append_owncloud_sidecar(spawner, "spaces", self.OCIS_SPACES, headers=headers)
              else:
                self.log.info("No auth state, skipping ownCloud")

      c.JupyterHub.spawner_class = WebDavOIDCSpawner
      c.WebDavOIDCSpawner.token_mount_path = "/var/run/secrets/oidc/"
      c.WebDavOIDCSpawner.http_timeout = 90
{% endraw %}
  templatePaths:
    - /egi-notebooks-hub/ec-templates
  extraFiles:
    welcome.html:
      mountPath: /usr/local/share/jupyterhub/templates/welcome.html
      stringData: |-
{%- raw %}
        {% extends "login.html" %}
{% endraw %}
    403.html:
      mountPath: /usr/local/share/jupyterhub/templates/403.html
      stringData: |-
{%- raw %}
        {% extends "error.html" %}
        {% block main %}
        <div class="error">
          <h1>Unauthorized</h1>
          <p>You don't have the correct entitlements to access this service.</p>
          <p>If you think you should be granted access, please open an issue!</p>
        </div>
        {% endblock %}
{% endraw %}

debug:
  enabled: true
