diff --git a/components/renku_data_services/crc/db.py b/components/renku_data_services/crc/db.py index 8248345b3..ac198e90f 100644 --- a/components/renku_data_services/crc/db.py +++ b/components/renku_data_services/crc/db.py @@ -559,6 +559,8 @@ async def update_resource_pool( rp.hibernation_warning_period = update.hibernation_warning_period if update.platform is not None: rp.platform = update.platform + if update.skip_session_iframe is not None: + rp.skip_session_iframe = update.skip_session_iframe match (update.cluster_id, rp.cluster_id): case ResetType.Reset, x if x is not None: diff --git a/components/renku_data_services/crc/models.py b/components/renku_data_services/crc/models.py index b4040d492..5566d90e3 100644 --- a/components/renku_data_services/crc/models.py +++ b/components/renku_data_services/crc/models.py @@ -328,6 +328,7 @@ class UnsavedResourcePool: remote: RemoteConfigurationFirecrest | RemoteConfigurationRunai | None = None cluster_id: ClusterId | None = None platform: RuntimePlatform + skip_session_iframe: bool = False @dataclass(frozen=True, eq=True, kw_only=True) @@ -347,6 +348,7 @@ class ResourcePool: cluster: SavedClusterSettings | None = None platform: RuntimePlatform credits_used: int | None = None + skip_session_iframe: bool = False def get_resource_class(self, resource_class_id: int) -> ResourceClass | None: """Find a specific resource class in the resource pool by the resource class id.""" @@ -384,6 +386,7 @@ class ResourcePoolPatch: remote: RemoteConfigurationPatch | None = None cluster_id: ClusterId | ResetType | None = None platform: RuntimePlatform | None = None + skip_session_iframe: bool | None = None class RemoteConfigurationKind(StrEnum): diff --git a/components/renku_data_services/crc/orm.py b/components/renku_data_services/crc/orm.py index 67ccbd5dd..713bb59b7 100644 --- a/components/renku_data_services/crc/orm.py +++ b/components/renku_data_services/crc/orm.py @@ -17,6 +17,9 @@ Table, literal, ) +from sqlalchemy import ( + false as sql_false, +) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column, relationship from sqlalchemy.schema import ForeignKey @@ -294,6 +297,9 @@ class ResourcePoolORM(BaseORM): Enum(models.RuntimePlatform, name="build_platform"), default=None, server_default=literal("linux_amd64") ) + skip_session_iframe: Mapped[bool] = mapped_column(default=False, server_default=sql_false()) + """If set to true it will cause the UI to not open the session in an iframe.""" + @classmethod def from_unsaved_model( cls, @@ -325,6 +331,7 @@ def from_unsaved_model( remote_json=remote_json, cluster_id=cluster.id if cluster else None, platform=new_resource_pool.platform, + skip_session_iframe=new_resource_pool.skip_session_iframe, ) def dump( @@ -362,6 +369,7 @@ def dump( cluster=cluster, platform=self.platform, credits_used=credits_used.value if credits_used else None, + skip_session_iframe=self.skip_session_iframe, ) def _dump_remote(self) -> models.RemoteConfigurationFirecrest | models.RemoteConfigurationRunai | None: diff --git a/components/renku_data_services/notebooks/api.spec.yaml b/components/renku_data_services/notebooks/api.spec.yaml index 6c0344e26..2401359f7 100644 --- a/components/renku_data_services/notebooks/api.spec.yaml +++ b/components/renku_data_services/notebooks/api.spec.yaml @@ -268,6 +268,10 @@ components: $ref: "#/components/schemas/Ulid" resource_class_id: type: integer + skip_iframe: + type: boolean + default: false + description: If set to true the UI will not open the session in an iframe once it is ready required: - image - name diff --git a/components/renku_data_services/notebooks/apispec.py b/components/renku_data_services/notebooks/apispec.py index fed2dc7ac..0594c4628 100644 --- a/components/renku_data_services/notebooks/apispec.py +++ b/components/renku_data_services/notebooks/apispec.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: api.spec.yaml -# timestamp: 2026-05-05T12:21:39+00:00 +# timestamp: 2026-05-20T15:08:05+00:00 from __future__ import annotations @@ -192,6 +192,10 @@ class SessionResponse(BaseAPISpec): pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$", ) resource_class_id: int + skip_iframe: bool = Field( + False, + description="If set to true the UI will not open the session in an iframe once it is ready", + ) class SessionListResponse(RootModel[List[SessionResponse]]): diff --git a/components/renku_data_services/notebooks/constants.py b/components/renku_data_services/notebooks/constants.py index d66304178..daec653f6 100644 --- a/components/renku_data_services/notebooks/constants.py +++ b/components/renku_data_services/notebooks/constants.py @@ -6,3 +6,5 @@ AMALTHEA_SESSION_GVK: Final[GVK] = GVK(group="amalthea.dev", version="v1alpha1", kind="AmaltheaSession") JUPYTER_SESSION_GVK: Final[GVK] = GVK(group="amalthea.dev", version="v1alpha1", kind="JupyterServer") + +SKIP_SESSION_IFRAME_ANNOTATION = Final[str] = "renku.io/skip_session_iframe" diff --git a/components/renku_data_services/notebooks/core_sessions.py b/components/renku_data_services/notebooks/core_sessions.py index 297589ab7..e08ee6cf3 100644 --- a/components/renku_data_services/notebooks/core_sessions.py +++ b/components/renku_data_services/notebooks/core_sessions.py @@ -14,6 +14,7 @@ import httpx from kubernetes.client import V1ObjectMeta, V1Secret +from renku_data_services.notebooks.constants import SKIP_SESSION_IFRAME_ANNOTATION from sanic import Request from toml import dumps from ulid import ULID @@ -962,6 +963,7 @@ async def start_session( "renku.io/launcher_id": str(launcher_id), "renku.io/resource_class_id": str(resource_class.id), "renku.io/resource_pool_id": str(resource_pool.id), + SKIP_SESSION_IFRAME_ANNOTATION: str(resource_pool.skip_session_iframe), } # Authentication @@ -1240,6 +1242,7 @@ async def patch_session( annotations.update(session.spec.template.metadata.annotations) annotations["renku.io/resource_class_id"] = str(rc.id) annotations["renku.io/resource_pool_id"] = str(rp.id) + annotations[SKIP_SESSION_IFRAME_ANNOTATION] = rp.skip_session_iframe patch.spec.template = TemplatePatch(metadata=TemplateMetadataPatch(annotations=annotations)) if not patch.spec.session: patch.spec.session = AmaltheaSessionV1Alpha1SpecSessionPatch() diff --git a/components/renku_data_services/notebooks/crs.py b/components/renku_data_services/notebooks/crs.py index 34e4a7096..ff5cb0679 100644 --- a/components/renku_data_services/notebooks/crs.py +++ b/components/renku_data_services/notebooks/crs.py @@ -17,7 +17,11 @@ from renku_data_services.base_models.core import RESET, ResetType from renku_data_services.errors import errors from renku_data_services.notebooks import apispec -from renku_data_services.notebooks.constants import AMALTHEA_SESSION_GVK, JUPYTER_SESSION_GVK +from renku_data_services.notebooks.constants import ( + AMALTHEA_SESSION_GVK, + JUPYTER_SESSION_GVK, + SKIP_SESSION_IFRAME_ANNOTATION, +) from renku_data_services.notebooks.cr_amalthea_session import Affinity as _Affinity from renku_data_services.notebooks.cr_amalthea_session import ( Authentication, @@ -364,6 +368,7 @@ def as_apispec(self) -> apispec.SessionResponse: project_id=str(self.project_id), launcher_id=str(self.launcher_id), resource_class_id=self.resource_class_id(), + skip_iframe=self.metadata.annotations.get(SKIP_SESSION_IFRAME_ANNOTATION, False), ) def base_url(self) -> str | None: