11"""K8s client wrapper for Renku apps."""
22
33from collections .abc import AsyncGenerator
4+ from datetime import datetime
45from typing import Any
56
67from renku_data_services .crc .db import ClusterRepository
910from renku_data_services .k8s .constants import DEFAULT_K8S_CLUSTER , DUMMY_RENKU_APP_USER_ID , ClusterId
1011from renku_data_services .k8s .models import GVK , K8sObjectMeta
1112from renku_data_services .project .models import Project
13+ from renku_data_services .renku_apps .cr_knative_service import Condition
1214from renku_data_services .renku_apps .crs import KnativeService
15+ from renku_data_services .renku_apps .models import AppRuntimeState
1316from renku_data_services .session .models import SessionLauncher
1417
1518KNATIVE_SERVICE_GVK = GVK (kind = "Service" , group = "serving.knative.dev" , version = "v1" )
@@ -36,8 +39,8 @@ def __init__(self, client: K8sClusterClientsPool, cluster_repo: ClusterRepositor
3639
3740 async def create_app_deployment (
3841 self , session_launcher : SessionLauncher , resource_class : ResourceClass | None , project : Project
39- ) -> KnativeService :
40- """Create a deployment for the given app and return the created Knative Service ."""
42+ ) -> AppRuntimeState :
43+ """Create a deployment for the given app and return its observed runtime state ."""
4144 cluster_id : ClusterId = DEFAULT_K8S_CLUSTER
4245 cluster = await self .__client .cluster_by_id (cluster_id )
4346 app_name = _generate_app_name (project )
@@ -52,10 +55,10 @@ async def create_app_deployment(
5255 created = await self .__client .create (
5356 meta .with_manifest (manifest .model_dump (exclude_none = True , mode = "json" )), refresh = True
5457 )
55- return KnativeService .model_validate (created .manifest )
58+ return _extract_runtime_state ( KnativeService .model_validate (created .manifest ) )
5659
57- async def get_app_deployment (self , app_name : str ) -> KnativeService | None :
58- """Get the deployment for the given app name, or None if it does not exist."""
60+ async def get_app_deployment (self , app_name : str ) -> AppRuntimeState | None :
61+ """Get the runtime state for the given app name, or None if it does not exist."""
5962 cluster_id : ClusterId = DEFAULT_K8S_CLUSTER
6063 cluster = await self .__client .cluster_by_id (cluster_id )
6164 meta = K8sObjectMeta (
@@ -68,21 +71,21 @@ async def get_app_deployment(self, app_name: str) -> KnativeService | None:
6871 obj = await self .__client .get (meta )
6972 if obj is None :
7073 return None
71- return KnativeService .model_validate (obj .manifest )
74+ return _extract_runtime_state ( KnativeService .model_validate (obj .manifest ) )
7275
73- async def get_app_deployment_for_project (self , project : Project ) -> KnativeService | None :
74- """Get the app deployment for the given project, or None if it does not exist."""
76+ async def get_app_deployment_for_project (self , project : Project ) -> AppRuntimeState | None :
77+ """Get the runtime state for the given project's app , or None if it does not exist."""
7578 return await self .get_app_deployment (_generate_app_name (project ))
7679
7780 async def delete_app_deployment (self , app_name : str ) -> None :
7881 """Delete the deployment for the given app name. NOT IMPLEMENTED."""
7982 raise NotImplementedError ("Deleting app deployment is not implemented yet" )
8083
81- async def list_app_deployments (self ) -> AsyncGenerator [KnativeService , None ]:
84+ async def list_app_deployments (self ) -> AsyncGenerator [AppRuntimeState , None ]:
8285 """List all app deployments. NOT IMPLEMENTED."""
8386 raise NotImplementedError ("Listing app deployments is not implemented yet" )
8487
85- async def update_app_deployment (self , app_name : str , session_launcher : SessionLauncher ) -> KnativeService :
88+ async def update_app_deployment (self , app_name : str , session_launcher : SessionLauncher ) -> AppRuntimeState :
8689 """Update the deployment for the given app name. NOT IMPLEMENTED."""
8790 raise NotImplementedError ("Updating app deployment is not implemented yet" )
8891
@@ -142,3 +145,38 @@ def _build_app_deployment_manifest(
142145 },
143146 }
144147 )
148+
149+
150+ def _url (knative_service : KnativeService ) -> str | None :
151+ """Get the public URL Knative assigned to the service, or None if it is not yet routed."""
152+ if knative_service .status is None :
153+ return None
154+ return knative_service .status .url
155+
156+
157+ def _ready_condition (knative_service : KnativeService ) -> Condition | None :
158+ """Get the Ready condition from a Knative service, or None if it doesn't exist."""
159+ if knative_service .status is None or not knative_service .status .conditions :
160+ return None
161+ return next ((c for c in knative_service .status .conditions if c .type == "Ready" ), None )
162+
163+
164+ def _started_at (knative_service : KnativeService ) -> datetime | None :
165+ """Get the time the Knative service became Ready, or None if not yet ready."""
166+ ready = _ready_condition (knative_service )
167+ if ready is None or ready .status != "True" or ready .lastTransitionTime is None :
168+ return None
169+ return datetime .fromisoformat (ready .lastTransitionTime )
170+
171+
172+ def _extract_runtime_state (knative_service : KnativeService ) -> AppRuntimeState :
173+ """Read app runtime state primitives off a Knative Service."""
174+ ready = _ready_condition (knative_service )
175+ return AppRuntimeState (
176+ name = knative_service .metadata .name ,
177+ launcher_id = knative_service .launcher_id ,
178+ project_id = knative_service .project_id ,
179+ ready_status = ready .status if ready is not None else None ,
180+ url = _url (knative_service ),
181+ started_at = _started_at (knative_service ),
182+ )
0 commit comments