From fef5f0acf68c49432126bbec04d5fe480da44980 Mon Sep 17 00:00:00 2001 From: vinicius douglas cerutti Date: Wed, 10 Dec 2025 11:15:16 -0300 Subject: [PATCH 1/2] add option to pass jhubapps additional services --- .../stages/kubernetes_services/__init__.py | 31 ++++++++- .../jupyterhub/files/jupyterhub/02-spawner.py | 64 +++++++++++++------ .../kubernetes/services/jupyterhub/main.tf | 1 + 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/_nebari/stages/kubernetes_services/__init__.py b/src/_nebari/stages/kubernetes_services/__init__.py index 24f185fd8..9e372e157 100644 --- a/src/_nebari/stages/kubernetes_services/__init__.py +++ b/src/_nebari/stages/kubernetes_services/__init__.py @@ -274,9 +274,33 @@ class ArgoWorkflows(schema.Base): nebari_workflow_controller: NebariWorkflowController = NebariWorkflowController() +class JhubAppsAdditionalService(schema.Base): + """ + List of additional external services to display in JupyterHub's services menu. + Services with `pinned=True` will also appear in the quick access section + for easy access. + + - `name` (required): Display name of the service + - `url` (required): URL path for the service + - `description` (optional): Description of the service shown in the UI + - `pinned` (optional): Whether the service should appear in the quick access + section (default: `False`) + - `thumbnail` (optional): URL or base64-encoded data URL for the service icon + (e.g., `"https://..."` or `"data:image/png;base64,..."`) + + """ + + name: str + url: str + description: Optional[str] = None + pinned: Optional[bool] = None + thumbnail: Optional[str] = None + + class JHubApps(schema.Base): enabled: bool = False - overrides: Dict = {} + overrides: Dict[str, Any] = Field(default_factory=dict) + additional_services: List[JhubAppsAdditionalService] = Field(default_factory=list) class MonitoringOverrides(schema.Base): @@ -558,6 +582,10 @@ class JupyterhubInputVars(schema.Base): argo_workflows_enabled: bool = Field(alias="argo-workflows-enabled") jhub_apps_enabled: bool = Field(alias="jhub-apps-enabled") jhub_apps_overrides: str = Field(alias="jhub-apps-overrides") + jhub_apps_additional_services: List[JhubAppsAdditionalService] = Field( + alias="jhub-apps-additional-services", + default_factory=list, + ) cloud_provider: str = Field(alias="cloud-provider") jupyterlab_preferred_dir: Optional[str] = Field(alias="jupyterlab-preferred-dir") shared_fs_type: SharedFsEnum @@ -753,6 +781,7 @@ def _node_taint_tolerations(node_group_name: str) -> List[Toleration]: argo_workflows_enabled=self.config.argo_workflows.enabled, jhub_apps_enabled=self.config.jhub_apps.enabled, jhub_apps_overrides=json.dumps(self.config.jhub_apps.overrides), + jhub_apps_additional_services=self.config.jhub_apps.additional_services, initial_repositories=str(self.config.jupyterlab.initial_repositories), jupyterlab_default_settings=self.config.jupyterlab.default_settings, jupyterlab_gallery_settings=self.config.jupyterlab.gallery_settings, diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/files/jupyterhub/02-spawner.py b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/files/jupyterhub/02-spawner.py index ac652a12e..a27767c54 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/files/jupyterhub/02-spawner.py +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/files/jupyterhub/02-spawner.py @@ -241,30 +241,54 @@ def get_conda_store_environments(user_info: dict): c.JAppsConfig.hub_host = "hub" c.JAppsConfig.service_workers = 4 + # ------------------------------------------------------------------ + # Base additional services shown in the JupyterHub Apps UI + # ------------------------------------------------------------------ + base_additional_services = [ + { + "name": "Monitoring", + "url": "/monitoring", + "description": "System monitoring dashboard", + "pinned": True, + }, + { + "name": "Argo", + "url": "/argo", + "description": "Argo Workflows UI", + }, + { + "name": "Users", + "url": "/auth/admin/nebari/console/", + "description": "Keycloak user and group management", + }, + { + "name": "Environments", + "url": "/conda-store", + "description": "Conda environment manager", + "pinned": True, + }, + ] + + # Extra services injected via Terraform as JSON (custom.jhub-apps-additional-services) + extra_services_raw = z2jh.get_config("custom.jhub-apps-additional-services") + extra_additional_services = [] + if extra_services_raw: + try: + extra_additional_services = json.loads(extra_services_raw) + except Exception: + # If misconfigured, fall back to just the base list + extra_additional_services = [] + + # Set the final list for JHub Apps + c.JAppsConfig.additional_services = ( + base_additional_services + extra_additional_services + ) + + # Apply generic overrides (can override additional_services entirely if desired) jhub_apps_overrides = json.loads(z2jh.get_config("custom.jhub-apps-overrides")) for config_key, config_value in jhub_apps_overrides.items(): setattr(c.JAppsConfig, config_key, config_value) - def service_for_jhub_apps(name, url): - return { - "name": name, - "display": True, - "info": { - "name": name, - "url": url, - "external": True, - }, - } - - c.JupyterHub.services.extend( - [ - service_for_jhub_apps(name="Argo", url="/argo"), - service_for_jhub_apps(name="Users", url="/auth/admin/nebari/console/"), - service_for_jhub_apps(name="Environments", url="/conda-store"), - service_for_jhub_apps(name="Monitoring", url="/monitoring"), - ] - ) - c.JupyterHub.template_paths = theme_template_paths kwargs = {} diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/main.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/main.tf index b0a8da8ee..3bba4e910 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/main.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/main.tf @@ -78,6 +78,7 @@ resource "helm_release" "jupyterhub" { conda-store-jhub-apps-token = var.conda-store-jhub-apps-token jhub-apps-enabled = var.jhub-apps-enabled jhub-apps-overrides = var.jhub-apps-overrides + jhub-apps-additional-services = jsonencode(var.jhub-apps-additional-services) initial-repositories = var.initial-repositories node-taint-tolerations = var.node-taint-tolerations skel-mount = { From 965ff7396cd33372bb4ec914fef2eca7bd1a3e02 Mon Sep 17 00:00:00 2001 From: vinicius douglas cerutti Date: Wed, 10 Dec 2025 16:55:26 -0300 Subject: [PATCH 2/2] add missing tf jupyterhub additional services variable definition --- .../template/jupyterhub.tf | 1 + .../services/jupyterhub/variables.tf | 24 +++++++++++++++++++ .../kubernetes_services/template/variables.tf | 6 +++++ 3 files changed, 31 insertions(+) diff --git a/src/_nebari/stages/kubernetes_services/template/jupyterhub.tf b/src/_nebari/stages/kubernetes_services/template/jupyterhub.tf index 8759f13f4..e66943390 100644 --- a/src/_nebari/stages/kubernetes_services/template/jupyterhub.tf +++ b/src/_nebari/stages/kubernetes_services/template/jupyterhub.tf @@ -192,6 +192,7 @@ module "jupyterhub" { jhub-apps-enabled = var.jhub-apps-enabled node-taint-tolerations = var.node-taint-tolerations jhub-apps-overrides = var.jhub-apps-overrides + jhub-apps-additional-services = var.jhub-apps-additional-services extra-mounts = { "/etc/dask" = { diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/variables.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/variables.tf index cc0c93587..66079b348 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/variables.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/jupyterhub/variables.tf @@ -229,3 +229,27 @@ variable "node-taint-tolerations" { effect = string })) } + +variable "jhub-apps-additional-services" { + description = <<-EOT + Extra services to display in the JupyterHub App Launcher (jhub-apps). + These are *appended* to the built-in base services defined in 02-spawner.py. + + Each element has the same shape as JAppsConfig.additional_services: + - name (string, required) + - url (string, required) + - description (string, optional) + - pinned (bool, optional) + - thumbnail (string, optional; URL or data:image/... base64) + EOT + + type = list(object({ + name = string + url = string + description = optional(string) + pinned = optional(bool) + thumbnail = optional(string) + })) + + default = [] +} diff --git a/src/_nebari/stages/kubernetes_services/template/variables.tf b/src/_nebari/stages/kubernetes_services/template/variables.tf index 5d4e6d4a7..3f6e7a287 100644 --- a/src/_nebari/stages/kubernetes_services/template/variables.tf +++ b/src/_nebari/stages/kubernetes_services/template/variables.tf @@ -65,6 +65,12 @@ variable "jhub-apps-overrides" { default = "{}" } +variable "jhub-apps-additional-services" { + description = "" + type = list(map(any)) + default = [{}] +} + variable "cloud-provider" { description = "Name of cloud provider." type = string