Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Auth aware profile list #700

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ repos:

# Autoformat: Python code
- repo: https://github.com/pycqa/isort
rev: 5.11.4
rev: 5.12.0
hooks:
- id: isort

# Autoformat: Python code
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 23.1.0
hooks:
- id: black

Expand Down
64 changes: 57 additions & 7 deletions kubespawner/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import string
import sys
import warnings
from functools import partial, wraps
from functools import lru_cache, partial, wraps
from urllib.parse import urlparse

import escapism
Expand Down Expand Up @@ -1616,6 +1616,8 @@ def _validate_image_pull_secrets(self, proposal):
and value can be either the final value or a callable that returns the final
value when called with the spawner instance as the only parameter. The callable
may be async.
- `oauthenticator_override` in the profile will allow certain profiles to be set
based on specific OAuthenticator instance. Top level override
- `default`: (optional Bool) True if this is the default selected option

kubespawner setting overrides work in the following manner, with items further in the
Expand All @@ -1626,6 +1628,8 @@ def _validate_image_pull_secrets(self, proposal):
3. `kubespawner_override` in the specific choices the user has made within the
profile, applied linearly based on the ordering of the option in the profile
definition configuration
4. `oauthenticator_override` in the profile will allow certain profiles to be set
based on specific OAuthenticator instance.

Example::

Expand Down Expand Up @@ -1698,6 +1702,9 @@ def _validate_image_pull_secrets(self, proposal):
'cpu_limit': 48,
'mem_limit': '96G',
'extra_resource_guarantees': {"nvidia.com/gpu": "2"},
},
'oauthenticator_override': {
'allowed_groups': ['gpu_user', 'ml_engineers']
}
}
]
Expand Down Expand Up @@ -2876,7 +2883,6 @@ async def _make_delete_pvc_request(self, pvc_name, request_timeout):

@_await_pod_reflector
async def stop(self, now=False):

delete_options = client.V1DeleteOptions()

if now:
Expand Down Expand Up @@ -2927,10 +2933,57 @@ def _render_options_form(self, profile_list):
return profile_form_template.render(profile_list=self._profile_list)

async def _render_options_form_dynamically(self, current_spawner):
profile_list = await maybe_future(self.profile_list(current_spawner))
if callable(self.profile_list):
profile_list = await maybe_future(self.profile_list(current_spawner))
else:
profile_list = self.profile_list
profile_list = self._init_profile_list(profile_list)
# protect non oauthenticator instances
if all(
(
hasattr(self.authenticator, 'enable_auth_state'),
hasattr(self.authenticator, 'user_is_authorized'),
self.authenticator.enable_auth_state,
)
):
profile_list = await self._filter_profile_options_form(profile_list)
return self._render_options_form(profile_list)

async def _filter_profile_options_form(self, profile_list):
@lru_cache
async def check_auth_overrides(auth_state, oauthenticator_overrides=None):
# only check if overrides are present
if oauthenticator_overrides:
return await self.authenticator.user_is_authorized(
auth_state, **oauthenticator_overrides
)
return True

auth_profile_list = []

auth_state = await self.user.get_auth_state()
for profile in profile_list:
# top level oauthenticator_override should take precendent
if not await check_auth_overrides(
auth_state, profile.pop("oauthenticator_override", None)
):
continue

profile_options = {}
for pk, pv in profile.pop("profile_options", {}).items():
# filter out choices on profile_options
profile_options[pk]["choices"] = {
ck: cv
for ck, cv in pv.pop("choices", {}).items()
if await check_auth_overrides(
auth_state, cv.pop("oauthenticator_override", None)
)
}

profile["profile_options"] = profile_options
auth_profile_list.append(profile)
return auth_profile_list

@default('options_form')
def _options_form_default(self):
"""
Expand All @@ -2942,10 +2995,7 @@ def _options_form_default(self):
"""
if not self.profile_list:
return ''
if callable(self.profile_list):
return self._render_options_form_dynamically
else:
return self._render_options_form(self.profile_list)
return self._render_options_form_dynamically

@default('options_from_form')
def _options_from_form_default(self):
Expand Down
1 change: 0 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ async def watch_kubernetes(kube_client, kube_ns):
func=kube_client.list_namespaced_event,
namespace=kube_ns,
):

resource = event['object']
obj = resource.involved_object
print(
Expand Down