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

[LEAP Hub] Allow for profile list options based on GitHub team membership #1146

Closed
3 tasks
choldgraf opened this issue Mar 24, 2022 · 8 comments · Fixed by #1239
Closed
3 tasks

[LEAP Hub] Allow for profile list options based on GitHub team membership #1146

choldgraf opened this issue Mar 24, 2022 · 8 comments · Fixed by #1239
Assignees
Labels
Enhancement An improvement to something or creating something new.

Comments

@choldgraf
Copy link
Member

choldgraf commented Mar 24, 2022

Background and proposal

The LEAP Hub has different sub-communities within its user community. Some of them require access to more complex infrastructure than others, and they'd like to offer different environments to some users over others.

Specifically, they'd like this breakdown of github team membership and machine types (ref: #1050 (comment)):

tier privileges
Public tier leap-stc/leap-pangeo-users Access to "Small" and "Medium" machine types
Research tier leap-stc/leap-pangeo-research Access to all machine types plus GPU option

So from a user's perspective, the end-result they would like is something like:

  • A user logs in to the hub, authenticating with their GitHub handle
  • At the profile list page, they are presented with a different list of options based on their GitHub team membership
  • They can then select an option like normal and proceed.

Implementation guide and constraints

@consideRatio provided a nice description of the solution in this Discourse comment.

Here's the major parts of this approach:

Problem 1 - Adjust presented server options based on user

What if you want to present different server options to different users, based on some property of the user? Just setting c.KubeSpawner.profile_list / singleuser.profileList won't do because then all users would get the same options.

The solution to this is to update c.KubeSpawner.options_form with a custom function that first updates the the_users_spawner_instance.profile_list based on logic that involve the user information.

Common section for examples below

The examples below assume we can request a "scope" called "gpu_access", which is supposed to return a "claim" with the same name as a true or false value.

# This snipped assumes a OAuthenticator based Authenticator, and the scopes will
# vary and this is just an example.
#
# - openid is common for OpenID Connect based authentication
# - profile, email are both commonly available scopes to request
# - gpu_access is entirely custom
#
c.OAuthenticator.scopes = ["openid", "profile", "email", "gpu_access"]

Solution to problem 1

To present JupyterHub server spawn options based on the user

# To adjust the spawn options presented to the user, we must create a custom
# options_form function, and this example demonstrates how!
#
#
#
# profile_list (KubeSpawner class) can be configured as a convenience to
# generate set HTML for the options_form configuration (Spawner class).
#
# If options_form is set (or indirectly set through profile_list), it is the
# HTML that users are presented with when users have signed in and want to start
# a server.
#
# While options_form is allowed to be a HTML string, it can also be a callable
# function, that when called generates HTML. If a callable function return a
# falsy value, no form will be rendered.
#
# In this custom options_form function, we will make a decision based on user
# information, update profile_list, and rely on the profile_list logic to render
# the HTML for us.
#
async def custom_options_form(spawner):
    # See the pre_spawn_hook example for more ways to get information about the
    # user
    auth_state = await spawner.user.get_auth_state()
    user_details = auth_state["oauth_user"]
    gpu_access = user_details.get("gpu_access", False)

    # Declare the common profile list for all users
    spawner.profile_list = [
        {
            'display_name': 'CPU server',
            'slug': 'cpu',
            'default': True,
        },
    ]

    # Dynamically update profile_list based on user
    if gpu_access:
        spawner.log.info(f"GPU access options added for {username}.")
        spawner.profile_list.extend(
            [
                {
                    'display_name': 'GPU server',
                    'slug': 'gpu',
                    'kubespawner_override': {
                        'image': 'training/datascience:my_tag',
                    },
                },
            ]
        )

    # Let KubeSpawner inspect profile_list and decide what to return, it
    # will return a falsy blank string if there is no profile_list,
    # which makes no options form be presented.
    #
    # ref: https://github.com/jupyterhub/kubespawner/blob/37a80abb0a6c826e5c118a068fa1cf2725738038/kubespawner/spawner.py#L1885-L1935
    return spawner._options_form_default()

# Don't forget to ask KubeSpawner to make use of this custom hook
c.KubeSpawner.options_form = custom_options_form

Opportunity to generalize

Note that while this is a request for a specific hub, the general pattern of "allow some users access to a subset of environments" will likely be very common. As we do this work, we should keep an eye on how to document and/or abstract this process to be repeatable with minimal extra toil.

Updates and ongoing work

@choldgraf choldgraf added the Enhancement An improvement to something or creating something new. label Mar 24, 2022
@choldgraf choldgraf mentioned this issue Mar 24, 2022
10 tasks
@consideRatio
Copy link
Contributor

Note how we in this example have this line:

    auth_state = await spawner.user.get_auth_state()

If we want to make decisions based on a GitHubOAuthenticator which is informed about what teams etc a user is part of, we should probably work to address the feature request: jupyterhub/oauthenticator#492

@choldgraf
Copy link
Member Author

Ah-ha good catch @consideRatio , thanks for the reminder. Anything else that you think is needed in order to implement this within our hubs?

@consideRatio
Copy link
Contributor

@choldgraf no I think that is all it takes!

@choldgraf
Copy link
Member Author

Note - there was some other conversation about this in the original thread here:

yuvipanda added a commit to yuvipanda/oauthenticator that referenced this issue Apr 8, 2022
Hoping to get 2i2c-org/infrastructure#1146
done. Longer term, we might use some of the new group management
features that JupyterHub 2.x uses - but I think this is still
pretty helpful, and we're still on 1.x :D

We automatically add read:org permission if the fetch_teams
flag is set, otherwise you just get thrown a weird error.

Fixes jupyterhub#492
@yuvipanda yuvipanda self-assigned this Apr 8, 2022
@yuvipanda
Copy link
Member

@rabernat I just opened jupyterhub/oauthenticator#498 which should get us most of the way here.

yuvipanda added a commit to yuvipanda/oauthenticator that referenced this issue Apr 25, 2022
Hoping to get 2i2c-org/infrastructure#1146
done. Longer term, we might use some of the new group management
features that JupyterHub 2.x uses - but I think this is still
pretty helpful, and we're still on 1.x :D

We automatically add read:org permission if the fetch_teams
flag is set, otherwise you just get thrown a weird error.

Fixes jupyterhub#492
yuvipanda added a commit to yuvipanda/pilot-hubs that referenced this issue Apr 25, 2022
profile_list is now dynamically generated, based on the GH teams
user is a part of. This list of teams is refreshed only during login -
so user needs to log out and log back in to see new teams! This also
means that users removed from teams on GH will still have access to
the profiles until they are logged out from the admin panel too (to
be fixed)

This approach is taken over customizing options_form to protect
against users just bypassing the options form and using the API
directly to spawn servers.

Deployed to the leap hub, except 'huge' is only available to
leap-stc:leap-pangeo-users members, not 2i2c-org:tech-team members.
This is temporary.

Fixes 2i2c-org#1146
yuvipanda added a commit to yuvipanda/pilot-hubs that referenced this issue Apr 25, 2022
profile_list is now dynamically generated, based on the GH teams
user is a part of. This list of teams is refreshed only during login -
so user needs to log out and log back in to see new teams! This also
means that users removed from teams on GH will still have access to
the profiles until they are logged out from the admin panel too (to
be fixed)

This approach is taken over customizing options_form to protect
against users just bypassing the options form and using the API
directly to spawn servers.

Deployed to the leap hub, except 'large' & 'huge' is only available to
leap-stc:leap-pangeo-research members, not to leap-stc:leap-pangeo-users
members - based on 2i2c-org#1050 (comment)

Fixes 2i2c-org#1146
yuvipanda added a commit to yuvipanda/pilot-hubs that referenced this issue Apr 25, 2022
profile_list is now dynamically generated, based on the GH teams
user is a part of. This list of teams is refreshed only during login -
so user needs to log out and log back in to see new teams! This also
means that users removed from teams on GH will still have access to
the profiles until they are logged out from the admin panel too (to
be fixed)

This approach is taken over customizing options_form to protect
against users just bypassing the options form and using the API
directly to spawn servers.

Deployed to the leap hub, except 'large' & 'huge' is only available to
leap-stc:leap-pangeo-research members, not to leap-stc:leap-pangeo-users
members - based on 2i2c-org#1050 (comment)

Fixes 2i2c-org#1146
@yuvipanda
Copy link
Member

ok, #1239 sets this up! I've 'small' and 'medium' available to leap-stc:leap-pangeo-users while 'huge' and 'large' are available only to leap-stc:leap-pangeo-research. Everything is available to 2i2c-org:tech-team though, so I'll need someone to actually test this. I've deployed this on https://staging.leap.2i2c.cloud/ - can you test, @rabernat?

Repository owner moved this from In progress to Complete in DEPRECATED Engineering and Product Backlog Apr 26, 2022
@damianavila
Copy link
Contributor

damianavila commented Apr 27, 2022

@rabernat, can you confirm if you tested it? Thanks!

@rabernat
Copy link
Contributor

Yes I tested it and it works as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement An improvement to something or creating something new.
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

5 participants