Skip to content

Commit

Permalink
GKE improvements for D4science (#117)
Browse files Browse the repository at this point in the history
* Avoid error 500 when context is not available

Instead show a 403 error, that looks better

* Add namespaces as option to spawner

* Add image override option to spawner

* Remove tornado setting from the spawner

Instead of using complicated options in the tornado settings that are
harder to express with the latest Jupyter versions, use the ingress
settings to set the headers for every requests. This will also ensure
that we don't forget some random page or sub-service from having the
correct headers

* Add label as authentication option

These are passed to the spawner so they can be reflected in the
Kubernetes objects accordingly

* Linting

* Fix method

* Disable pylint just for one line

* Fix method name
  • Loading branch information
enolfc authored Jun 20, 2024
1 parent 9b41726 commit f7ff823
Showing 1 changed file with 54 additions and 9 deletions.
63 changes: 54 additions & 9 deletions egi_notebooks_hub/d4science.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@
class D4ScienceContextHandler(OAuthLoginHandler):
def get(self):
context = self.get_argument("context", None)
namespace = self.get_argument("namespace", None)
label = self.get_argument("label", None)
self.authenticator.d4science_context = context
self.authenticator.d4science_namespace = namespace
self.authenticator.d4science_label = label
return super().get()


class D4ScienceOauthenticator(GenericOAuthenticator):
login_handler = D4ScienceContextHandler
# some options that will come from the context handler
d4science_context = None
d4science_namespace = None
d4science_label = None

d4science_oidc_url = Unicode(
D4SCIENCE_OIDC_URL,
config=True,
Expand All @@ -63,6 +72,13 @@ class D4ScienceOauthenticator(GenericOAuthenticator):
help="""The URL for getting DataMiner resources from the
Information System of D4science""",
)
d4science_label_name = Unicode(
"d4science-namespace",
config=True,
help="""The name of the label to use when setting extra labels
coming from the authentication (i.e. label="blue-cloud"
as param)""",
)

_pubkeys = None

Expand Down Expand Up @@ -185,10 +201,16 @@ async def get_resources(self, access_token):
# Assume that this will fly
return xmltodict.parse(resp.body)

def _get_d4science_attr(self, attr_name):
v = getattr(self, attr_name, None)
if v:
return quote_plus(v)
return None

async def authenticate(self, handler, data=None):
# first get authorized upstream
user_data = await super().authenticate(handler, data)
context = quote_plus(getattr(self, "d4science_context", None))
context = self._get_d4science_attr("d4science_context")
self.log.debug("Context is %s", context)
if not context:
self.log.error("Unable to get the user context")
Expand Down Expand Up @@ -220,6 +242,8 @@ async def authenticate(self, handler, data=None):
"context_token": ws_token,
"permissions": permissions,
"context": context,
"namespace": self._get_d4science_attr("d4science_namespace"),
"label": self._get_d4science_attr("d4science_label"),
"resources": resources,
"roles": roles,
}
Expand All @@ -234,6 +258,12 @@ async def pre_spawn_start(self, user, spawner):
if not auth_state:
# auth_state not enabled
return
namespace = auth_state.get("namespace", None)
if namespace:
spawner.namespace = namespace
label = auth_state.get("label", None)
if label:
spawner.extra_labels[self.d4science_label_name] = label
# GCUBE_TOKEN should be removed in the future
spawner.environment["GCUBE_TOKEN"] = auth_state["context_token"]
spawner.environment["D4SCIENCE_TOKEN"] = auth_state["context_token"]
Expand Down Expand Up @@ -333,25 +363,36 @@ class D4ScienceSpawner(KubeSpawner):
config=True,
help="""Name of the data manager role in D4Science""",
)
context_namespaces = Bool(
False,
config=True,
help="""Whether context-specific namespaces will be used or not""",
)
image_repo_override = Unicode(
"",
config=True,
help="""If provided, override image repository with this value""",
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.allowed_profiles = []
self.server_options = []
self._orig_volumes = self.volumes
self._orig_volume_mounts = self.volume_mounts
if self.image_repo_override:
# pylint: disable-next=access-member-before-definition
image = self.image.rsplit("/", 1)[-1]
self.image = f"{self.image_repo_override}/{image}"

async def _ensure_namespace(self):
if not self.context_namespaces:
super()._ensure_namespace()

def get_args(self):
args = super().get_args()
tornado_settings = {
"headers": {
"Content-Security-Policy": "frame-ancestors %s" % self.frame_ancestors
},
"cookie_options": {"samesite": "None", "secure": True},
}
# TODO: check if this keeps making sense
return [
"--ServerApp.tornado_settings=%s" % tornado_settings,
"--FileCheckpoints.checkpoint_dir='/home/jovyan/.notebookCheckpoints'",
"--FileContentsManager.use_atomic_writing=False",
"--ResourceUseDisplay.track_cpu_percent=True",
Expand Down Expand Up @@ -451,7 +492,11 @@ def profile_list(self, spawner):
)
continue
if "ImageId" in p:
override["image"] = p.get("ImageId", None)
image = p.get("ImageId", "")
if self.image_repo_override:
image = image.rsplit("/", 1)[-1]
image = f"{self.image_repo_override}/{image}"
override["image"] = image
if "Cut" in p:
cut_info = []
if "Cores" in p["Cut"]:
Expand Down

0 comments on commit f7ff823

Please sign in to comment.