Skip to content
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
6 changes: 3 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ jobs:
# Pinned versions the hub image runs against — tests exercise
# the real oauthenticator + jupyterhub contracts, not a stub.
uv pip install --python .venv-unit/bin/python \
jupyterhub==5.1.0 \
oauthenticator==16.3.0 \
jupyterhub-kubespawner==6.2.0 \
jupyterhub==5.5.0 \
oauthenticator==17.4.0 \
jupyterhub-kubespawner==7.0.0 \
pytest

- name: pytest tests/unit
Expand Down
6 changes: 3 additions & 3 deletions Chart.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ appVersion: "1.0.0"

dependencies:
- name: jupyterhub
version: 4.3.2
version: 4.4.0
repository: https://hub.jupyter.org/helm-chart/
29 changes: 27 additions & 2 deletions config/jupyterhub/00-gateway-auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,14 @@ def configure(
# scope param entirely; KC then issues a token without `openid` and
# /userinfo returns 403 at token_to_user.
c.KeyCloakOAuthenticator.scope = ["openid", "profile", "email", "groups"]
c.KeyCloakOAuthenticator.claim_groups_key = "groups"
c.KeyCloakOAuthenticator.admin_groups = set(admin_groups or ["admin"])
# OAuthenticator 17 requires managed groups for admin_groups and replaces
# GenericOAuthenticator.claim_groups_key with auth_state_groups_key.
# Keep KC's raw full group paths in auth_state for RBAC/shared storage,
# but expose leaf names to JupyterHub groups so admin_groups=["admin"]
# matches a KC claim like "/admin".
c.KeyCloakOAuthenticator.manage_groups = True
c.KeyCloakOAuthenticator.auth_state_groups_key = _jupyterhub_group_names
c.KeyCloakOAuthenticator.admin_groups = set(_leaf_group_names(admin_groups or ["admin"]))
# Persist tokens so refresh_user can use the stored refresh_token.
c.KeyCloakOAuthenticator.enable_auth_state = True
c.KeyCloakOAuthenticator.refresh_pre_spawn = True
Expand Down Expand Up @@ -565,6 +571,25 @@ def _read_secret_file(secret_dir: Path, key: str) -> str:
return (secret_dir / key).read_text().strip()


def _leaf_group_names(raw_groups):
"""Return group leaf names while preserving first-seen order."""
seen = set()
groups = []
for group in raw_groups or []:
if not group:
continue
name = Path(group).name
if name and name not in seen:
seen.add(name)
groups.append(name)
return groups


def _jupyterhub_group_names(auth_state):
"""Return Keycloak group names in the form OAuthenticator admin groups use."""
return _leaf_group_names((auth_state.get("oauth_user") or {}).get("groups"))


def _derive_realm_api_url(issuer_url: str) -> str:
"""Convert a KC realm issuer URL to its admin-API counterpart.

Expand Down
7 changes: 5 additions & 2 deletions config/jupyterhub/01-spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
c.KubeSpawner.storage_pvc_ensure = True
c.KubeSpawner.storage_capacity = get_config("custom.storage-capacity", "20Gi")
c.KubeSpawner.storage_access_modes = ["ReadWriteOnce"]
# KubeSpawner 7 defaults to slug_scheme="safe". Keep the old escaped slug
# behavior until PVC names and affinity labels are intentionally migrated.
c.KubeSpawner.slug_scheme = "escape"
# Without this override, KubeSpawner's default template is
# `claim-{username}--{servername}`, so for jhub-apps named servers it ensures
# a per-server PVC — while the `volumes` block below mounts the per-user
Expand Down Expand Up @@ -1038,13 +1041,13 @@ async def _ensure_workspace_pvc(spawner):
# ---------------------------------------------------------------------------
# Pre-spawn hook orchestrator
# ---------------------------------------------------------------------------
# Chains the independent concerns:
# Chains the independent concerns:
# 1. Nebi auto-auth
# 2. workspace PVC
# 3. Resolve groups
# 5. shared storage mounts
# 4. NSS wrapper setup
#
#
# Each is implemented as its own focused function above. The orchestrator always
# runs so NSS wrapper is active even without Nebi/shared.

Expand Down
2 changes: 1 addition & 1 deletion config/jupyterhub/02-jhub-apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
c.JupyterHub.default_url = "/hub/home"
c.JupyterHub.template_paths = theme_template_paths
# Match JupyterLab default (IBM Plex Sans, PR #75) on hub + JApps pages.
# Requires jhub-apps >= 2026.5.1rc1 (PR #677: font_family / font_url theme vars).
# Requires jhub-apps >= 2026.6.1 for font_family / font_url theme vars.
c.JupyterHub.template_vars = {
**themes.DEFAULT_THEME,
"font_family": "'IBM Plex Sans', sans-serif",
Expand Down
Loading
Loading