22
33logger = logging .getLogger (__name__ )
44from enum import StrEnum , auto
5- from typing import Any , Dict , List , Optional
5+ from typing import Annotated , Any , Dict , List , Optional , Union
66
77from kapitan .inputs .kadet import BaseObj , load_from_search_paths
8- from pydantic import DirectoryPath , Field , FilePath
8+ from pydantic import ConfigDict , DirectoryPath , Field , FilePath , model_validator
9+ from pydantic .alias_generators import to_camel
910
1011kgenlib = load_from_search_paths ("kgenlib" )
1112
@@ -68,6 +69,8 @@ def body(self):
6869 self .root .kind = self .kind
6970
7071 self .root .metadata .name = self .rendered_name or self .name
72+ self .root .metadata .labels
73+ self .root .metadata .annotations
7174 self .add_label ("name" , self .name )
7275 if self .config :
7376 self .add_labels (self .config .labels )
@@ -141,13 +144,17 @@ def body(self):
141144 def set_namespace (self , namespace : str ):
142145 self .root .metadata .namespace = namespace
143146
147+ def remove_namespace (self ):
148+ self .root .metadata .pop ("namespace" )
149+
144150
145151class WorkloadTypes (StrEnum ):
146152 DEPLOYMENT = auto ()
147153 STATEFULSET = auto ()
148154 DAEMONSET = auto ()
149155 JOB = auto ()
150156 CRONJOB = auto ()
157+ CLOUD_RUN_SERVICE = auto ()
151158
152159
153160class RestartPolicy (StrEnum ):
@@ -156,6 +163,12 @@ class RestartPolicy(StrEnum):
156163 NEVER = "Never"
157164
158165
166+ class ConcurrentPolicy (StrEnum ):
167+ ALLOW = "Allow"
168+ FORBID = "Forbid"
169+ REPLACE = "Replace"
170+
171+
159172class ImagePullPolicy (StrEnum ):
160173 ALWAYS = "Always"
161174 IF_NOT_PRESENT = "IfNotPresent"
@@ -286,7 +299,9 @@ class ServiceAccountConfigSpec(KubernetesResourceSpec):
286299 namespace : Optional [str ] = None
287300 rendered_name : Optional [str ] = None
288301 name : Optional [str ] = None
289- roles : Optional [List [Dict [str , Any ]]] = None
302+ roles : Optional [
303+ list [dict [str , list [str ]]] | dict [str , dict [str , list [str ]] | list [str ]]
304+ ] = None
290305
291306
292307class ContainerSpec (kgenlib .BaseModel ):
@@ -298,7 +313,7 @@ class ContainerSpec(kgenlib.BaseModel):
298313 healthcheck : Optional [HealthCheckConfigSpec ] = None
299314 image : str = None
300315 image_pull_policy : Optional [ImagePullPolicy ] = ImagePullPolicy .IF_NOT_PRESENT
301- lifecycle : dict = {}
316+ lifecycle : Optional [ dict ] = None
302317 pod_annotations : dict = {}
303318 pod_labels : dict = {}
304319 ports : Dict [str , ContainerPortSpec ] = {}
@@ -308,6 +323,10 @@ class ContainerSpec(kgenlib.BaseModel):
308323 volume_mounts : dict = {}
309324
310325
326+ class InitContainerSpec (ContainerSpec ):
327+ sidecar : Optional [bool ] = None
328+
329+
311330class ServiceTypes (StrEnum ):
312331 EXTERNAL_NAME = "ExternalName"
313332 CLUSTER_IP = "ClusterIP"
@@ -322,7 +341,7 @@ class SessionAffinity(StrEnum):
322341
323342class RoleBindingConfigSpec (KubernetesResourceSpec ):
324343 roleRef : Optional [Dict [str , Any ]] = None
325- subject : Optional [List [Dict [str , Any ]]] = None
344+ subjects : Optional [List [Dict [str , Any ]]] = None
326345
327346
328347class ServiceConfigSpec (KubernetesResourceSpec ):
@@ -355,8 +374,9 @@ class NetworkPolicySpec(KubernetesResourceSpec):
355374
356375class WorkloadConfigSpec (KubernetesResourceSpec , ContainerSpec ):
357376 type : Optional [WorkloadTypes ] = WorkloadTypes .DEPLOYMENT
377+ namespace : str | None = None
358378 schedule : Optional [str ] = None
359- additional_containers : Optional [Dict [str , ContainerSpec ]] = {}
379+ additional_containers : Optional [Dict [str , Union [ ContainerSpec , None ] ]] = {}
360380 additional_services : Optional [Dict [str , ServiceConfigSpec ]] = {}
361381 annotations : dict = {}
362382 application : Optional [str ] = None
@@ -371,7 +391,7 @@ class WorkloadConfigSpec(KubernetesResourceSpec, ContainerSpec):
371391 host_pid : Optional [bool ] = None
372392 hpa : dict = {}
373393 image_pull_secrets : list = []
374- init_containers : Optional [Dict [str , ContainerSpec ]] = {}
394+ init_containers : Optional [Dict [str , Union [ InitContainerSpec , None ] ]] = {}
375395 istio_policy : dict = {}
376396 keda_scaled_object : dict = {}
377397 labels : Dict [str , str ] = {}
@@ -399,16 +419,25 @@ class WorkloadConfigSpec(KubernetesResourceSpec, ContainerSpec):
399419 workload_security_context : dict = {}
400420
401421
422+ class CloudRunServiceConfigSpec (WorkloadConfigSpec ):
423+ type : Optional [WorkloadTypes ] = WorkloadTypes .CLOUD_RUN_SERVICE
424+
425+
402426class DeploymentConfigSpec (WorkloadConfigSpec ):
403427 type : Optional [WorkloadTypes ] = WorkloadTypes .DEPLOYMENT
404428 update_strategy : Optional [dict ] = {}
405429 strategy : Optional [dict ] = {}
406430
407431
432+ class PodManagementPolicy (StrEnum ):
433+ ORDERED_READY = "OrderedReady"
434+ PARALLEL = "Parallel"
435+
436+
408437class StatefulSetConfigSpec (WorkloadConfigSpec ):
409438 type : WorkloadTypes = WorkloadTypes .STATEFULSET
439+ pod_management_policy : str = PodManagementPolicy .ORDERED_READY
410440 update_strategy : dict = {}
411- strategy : dict = {}
412441
413442
414443class DaemonSetConfigSpec (WorkloadConfigSpec ):
@@ -426,3 +455,94 @@ class JobConfigSpec(WorkloadConfigSpec):
426455class CronJobConfigSpec (JobConfigSpec ):
427456 type : WorkloadTypes = WorkloadTypes .CRONJOB
428457 schedule : str
458+ concurrency_policy : Optional [ConcurrentPolicy ] = ConcurrentPolicy .ALLOW
459+
460+
461+ ContainerProbeSpecTypes = Annotated [
462+ Union [ContainerEXECProbeSpec , ContainerHTTPProbeSpec , ContainerTCPProbeSpec ],
463+ Field (discriminator = "type" ),
464+ ]
465+
466+
467+ class ContainerProbes (kgenlib .BaseModel ):
468+ model_config = ConfigDict (
469+ alias_generator = to_camel ,
470+ populate_by_name = True ,
471+ from_attributes = True ,
472+ extra = "ignore" ,
473+ )
474+ type : ProbeTypes = Field (exclude = True )
475+ initial_delay_seconds : int = 0
476+ period_seconds : int = 10
477+ timeout_seconds : int = 1
478+ success_threshold : int = 1
479+ failure_threshold : int = 3
480+
481+ # Define a class method for creating probes from data
482+ @classmethod
483+ def from_spec (cls , spec : ContainerProbeSpecTypes ) -> Union ["ContainerProbes" , None ]:
484+ probe = None
485+ if not spec or not spec .enabled :
486+ return probe
487+ probe_type = spec .type
488+ if probe_type == ProbeTypes .TCP :
489+ probe = TCPProbe .model_validate (spec )
490+ elif probe_type == ProbeTypes .HTTP :
491+ probe = HTTPProbe .model_validate (spec )
492+ elif probe_type == ProbeTypes .EXEC :
493+ probe = ExecProbe .model_validate (spec )
494+ else :
495+ raise ValueError (f"Invalid probe type: { probe_type } " )
496+ return probe .model_dump (by_alias = True )
497+
498+
499+ class HttpGet (kgenlib .BaseModel ):
500+ model_config = ConfigDict (
501+ alias_generator = to_camel ,
502+ populate_by_name = True ,
503+ from_attributes = True ,
504+ )
505+ path : str = "/"
506+ port : str | int = 80
507+ httpHeaders : Optional [List [dict ]] = None
508+ scheme : Optional [ProbeSchemeSpec ] = ProbeSchemeSpec .HTTP
509+
510+
511+ class HTTPProbe (ContainerProbes ):
512+ httpGet : Optional [HttpGet ] = None
513+ port : str | int = Field (exclude = True )
514+ path : str = Field (exclude = True )
515+ scheme : Optional [ProbeSchemeSpec ] = Field (exclude = True )
516+ httpHeaders : Optional [List [dict ]] = Field (exclude = True )
517+
518+ # Use a validator to handle the 'port' mapping
519+ @model_validator (mode = "after" )
520+ def map_port_to_http_config (self ):
521+ self .httpGet = HttpGet .model_validate (self )
522+ return self
523+
524+
525+ class TCPProbe (ContainerProbes ):
526+ port : str | int = Field (exclude = True )
527+ tcpSocket : Optional [dict ] = None
528+
529+ # Use a validator to handle the 'port' mapping
530+ @model_validator (mode = "after" )
531+ def map_port_to_tcp_socket (self ):
532+ self .tcpSocket = {"port" : self .port }
533+ return self
534+
535+
536+ class ExecProbe (ContainerProbes ):
537+ command : List = Field (exclude = True )
538+ exec : Optional [dict ] = None
539+
540+ @model_validator (mode = "after" )
541+ def map_command_to_exec_socket (self ):
542+ self .exec = {"command" : self .command }
543+ return self
544+
545+
546+ ContainerProbeTypes = Annotated [
547+ Union [HTTPProbe , TCPProbe , ExecProbe ], Field (discriminator = "type" )
548+ ]
0 commit comments