11from __future__ import annotations
22
33from dataclasses import dataclass , field
4+ from datetime import timedelta
45
56import yaml
7+ from google .protobuf import duration_pb2 , field_mask_pb2 , json_format
68from grpc .aio import Channel
7- from jumpstarter_protocol import client_pb2 , client_pb2_grpc
8- from pydantic import BaseModel
9+ from jumpstarter_protocol import client_pb2 , client_pb2_grpc , kubernetes_pb2
10+ from pydantic import BaseModel , ConfigDict , Field , field_serializer
911
1012
11- def parse_exporter_identifier (identifier : str ) -> (str , str ):
13+ def parse_identifier (identifier : str , kind : str ) -> (str , str ):
1214 segments = identifier .split ("/" )
1315 if len (segments ) != 4 :
1416 raise ValueError ("incorrect number of segments in identifier, expecting 4, got {}" .format (len (segments )))
1517 if segments [0 ] != "namespaces" :
1618 raise ValueError ("incorrect first segment in identifier, expecting namespaces, got {}" .format (segments [0 ]))
17- if segments [2 ] != "exporters" :
18- raise ValueError ("incorrect third segment in identifier, expecting exporters , got {}" .format (segments [2 ]))
19+ if segments [2 ] != kind :
20+ raise ValueError ("incorrect third segment in identifier, expecting {} , got {}" .format (kind , segments [2 ]))
1921 return segments [1 ], segments [3 ]
2022
2123
24+ def parse_client_identifier (identifier : str ) -> (str , str ):
25+ return parse_identifier (identifier , "clients" )
26+
27+
28+ def parse_exporter_identifier (identifier : str ) -> (str , str ):
29+ return parse_identifier (identifier , "exporters" )
30+
31+
32+ def parse_lease_identifier (identifier : str ) -> (str , str ):
33+ return parse_identifier (identifier , "leases" )
34+
35+
2236class Exporter (BaseModel ):
2337 namespace : str
2438 name : str
@@ -30,9 +44,54 @@ def from_protobuf(cls, data: client_pb2.Exporter) -> Exporter:
3044 return cls (namespace = namespace , name = name , labels = data .labels )
3145
3246
47+ class Lease (BaseModel ):
48+ namespace : str
49+ name : str
50+ selector : str
51+ duration : timedelta
52+ client : str
53+ exporter : str
54+ conditions : list [kubernetes_pb2 .Condition ]
55+
56+ model_config = ConfigDict (
57+ arbitrary_types_allowed = True ,
58+ ser_json_timedelta = "float" ,
59+ )
60+
61+ @field_serializer ("conditions" )
62+ def serialize_conditions (self , conditions : list [kubernetes_pb2 .Condition ], _info ):
63+ return [json_format .MessageToDict (condition ) for condition in conditions ]
64+
65+ @classmethod
66+ def from_protobuf (cls , data : client_pb2 .Lease ) -> Lease :
67+ namespace , name = parse_lease_identifier (data .name )
68+
69+ _ , client = parse_client_identifier (data .client )
70+ if data .exporter != "" :
71+ _ , exporter = parse_exporter_identifier (data .exporter )
72+ else :
73+ exporter = ""
74+
75+ return cls (
76+ namespace = namespace ,
77+ name = name ,
78+ selector = data .selector ,
79+ duration = data .duration .ToTimedelta (),
80+ client = client ,
81+ exporter = exporter ,
82+ conditions = data .conditions ,
83+ )
84+
85+ def dump_json (self ):
86+ return self .model_dump_json (indent = 4 , by_alias = True )
87+
88+ def dump_yaml (self ):
89+ return yaml .safe_dump (self .model_dump (mode = "json" , by_alias = True ), indent = 2 )
90+
91+
3392class ExporterList (BaseModel ):
3493 exporters : list [Exporter ]
35- next_page_token : str | None
94+ next_page_token : str | None = Field ( exclude = True )
3695
3796 @classmethod
3897 def from_protobuf (cls , data : client_pb2 .ListExportersResponse ) -> ExporterList :
@@ -48,6 +107,24 @@ def dump_yaml(self):
48107 return yaml .safe_dump (self .model_dump (mode = "json" , by_alias = True ), indent = 2 )
49108
50109
110+ class LeaseList (BaseModel ):
111+ leases : list [Lease ]
112+ next_page_token : str | None = Field (exclude = True )
113+
114+ @classmethod
115+ def from_protobuf (cls , data : client_pb2 .ListLeasesResponse ) -> LeaseList :
116+ return cls (
117+ leases = list (map (Lease .from_protobuf , data .leases )),
118+ next_page_token = data .next_page_token ,
119+ )
120+
121+ def dump_json (self ):
122+ return self .model_dump_json (indent = 4 , by_alias = True )
123+
124+ def dump_yaml (self ):
125+ return yaml .safe_dump (self .model_dump (mode = "json" , by_alias = True ), indent = 2 )
126+
127+
51128@dataclass (kw_only = True , slots = True )
52129class ClientService :
53130 channel : Channel
@@ -81,3 +158,81 @@ async def ListExporters(
81158 )
82159 )
83160 return ExporterList .from_protobuf (exporters )
161+
162+ async def GetLease (self , * , namespace : str , name : str ):
163+ lease = await self .stub .GetLease (
164+ client_pb2 .GetLeaseRequest (
165+ name = "namespaces/{}/leases/{}" .format (namespace , name ),
166+ )
167+ )
168+ return Lease .from_protobuf (lease )
169+
170+ async def ListLeases (
171+ self ,
172+ * ,
173+ namespace : str ,
174+ page_size : int | None = None ,
175+ page_token : str | None = None ,
176+ filter : str | None = None ,
177+ ):
178+ leases = await self .stub .ListLeases (
179+ client_pb2 .ListLeasesRequest (
180+ parent = "namespaces/{}" .format (namespace ),
181+ page_size = page_size ,
182+ page_token = page_token ,
183+ filter = filter ,
184+ )
185+ )
186+ return LeaseList .from_protobuf (leases )
187+
188+ async def CreateLease (
189+ self ,
190+ * ,
191+ namespace : str ,
192+ selector : str ,
193+ duration : timedelta ,
194+ ):
195+ duration_pb = duration_pb2 .Duration ()
196+ duration_pb .FromTimedelta (duration )
197+
198+ lease = await self .stub .CreateLease (
199+ client_pb2 .CreateLeaseRequest (
200+ parent = "namespaces/{}" .format (namespace ),
201+ lease = client_pb2 .Lease (
202+ duration = duration_pb ,
203+ selector = selector ,
204+ ),
205+ )
206+ )
207+ return Lease .from_protobuf (lease )
208+
209+ async def UpdateLease (
210+ self ,
211+ * ,
212+ namespace : str ,
213+ name : str ,
214+ duration : timedelta ,
215+ ):
216+ duration_pb = duration_pb2 .Duration ()
217+ duration_pb .FromTimedelta (duration )
218+
219+ update_mask = field_mask_pb2 .FieldMask ()
220+ update_mask .FromJsonString ("duration" )
221+
222+ lease = await self .stub .UpdateLease (
223+ client_pb2 .UpdateLeaseRequest (
224+ lease = client_pb2 .Lease (
225+ name = "namespaces/{}/leases/{}" .format (namespace , name ),
226+ duration = duration_pb ,
227+ ),
228+ update_mask = update_mask ,
229+ )
230+ )
231+ return Lease .from_protobuf (lease )
232+
233+ async def DeleteLease (self , * , namespace : str , name : str ):
234+ await self .stub .DeleteLease (
235+ client_pb2 .DeleteLeaseRequest (
236+ name = "namespaces/{}/leases/{}" .format (namespace , name ),
237+ )
238+ )
0 commit comments