Skip to main content

devops_models/models/
k8s.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Kubernetes metadata — equivalent of Python K8sMetadata with extra="forbid"
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(deny_unknown_fields)]
7pub struct K8sMetadata {
8    #[serde(default)]
9    pub name: Option<String>,
10    #[serde(default)]
11    pub namespace: Option<String>,
12    #[serde(default)]
13    pub labels: HashMap<String, String>,
14    #[serde(default)]
15    pub annotations: HashMap<String, String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct K8sResourceRequirements {
21    #[serde(default)]
22    pub limits: HashMap<String, String>,
23    #[serde(default)]
24    pub requests: HashMap<String, String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(deny_unknown_fields)]
29pub struct K8sContainerPort {
30    #[serde(rename = "containerPort")]
31    pub container_port: u16,
32    #[serde(default)]
33    pub name: Option<String>,
34    #[serde(default)]
35    pub protocol: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct K8sEnvVar {
41    pub name: String,
42    #[serde(default)]
43    pub value: Option<String>,
44    #[serde(default, rename = "valueFrom")]
45    pub value_from: Option<serde_json::Value>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(deny_unknown_fields)]
50pub struct K8sContainer {
51    pub name: String,
52    pub image: String,
53    #[serde(default)]
54    pub ports: Vec<K8sContainerPort>,
55    #[serde(default)]
56    pub env: Vec<K8sEnvVar>,
57    #[serde(default)]
58    pub resources: Option<K8sResourceRequirements>,
59    #[serde(default)]
60    pub command: Vec<String>,
61    #[serde(default)]
62    pub args: Vec<String>,
63    #[serde(default, rename = "imagePullPolicy")]
64    pub image_pull_policy: Option<String>,
65    #[serde(default, rename = "livenessProbe")]
66    pub liveness_probe: Option<serde_json::Value>,
67    #[serde(default, rename = "readinessProbe")]
68    pub readiness_probe: Option<serde_json::Value>,
69    #[serde(default, rename = "volumeMounts")]
70    pub volume_mounts: Option<serde_json::Value>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(deny_unknown_fields)]
75pub struct K8sPodSpec {
76    pub containers: Vec<K8sContainer>,
77    #[serde(default, rename = "initContainers")]
78    pub init_containers: Vec<K8sContainer>,
79    #[serde(default, rename = "restartPolicy")]
80    pub restart_policy: Option<String>,
81    #[serde(default, rename = "serviceAccountName")]
82    pub service_account_name: Option<String>,
83    #[serde(default)]
84    pub volumes: Option<serde_json::Value>,
85    #[serde(default, rename = "nodeSelector")]
86    pub node_selector: Option<HashMap<String, String>>,
87    #[serde(default)]
88    pub tolerations: Option<serde_json::Value>,
89    #[serde(default)]
90    pub affinity: Option<serde_json::Value>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(deny_unknown_fields)]
95pub struct K8sPodTemplate {
96    pub metadata: K8sMetadata,
97    pub spec: K8sPodSpec,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(deny_unknown_fields)]
102pub struct K8sDeploymentSpec {
103    pub replicas: Option<u32>,
104    pub selector: serde_json::Value,
105    pub template: K8sPodTemplate,
106    #[serde(default)]
107    pub strategy: Option<serde_json::Value>,
108}
109
110/// Kubernetes Deployment — top-level resource
111#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct K8sDeployment {
114    #[serde(rename = "apiVersion")]
115    pub api_version: String,
116    pub kind: String,
117    pub metadata: K8sMetadata,
118    pub spec: K8sDeploymentSpec,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(deny_unknown_fields)]
123pub struct K8sServicePort {
124    pub port: u16,
125    #[serde(default, rename = "targetPort")]
126    pub target_port: Option<serde_json::Value>,
127    #[serde(default)]
128    pub protocol: Option<String>,
129    #[serde(default)]
130    pub name: Option<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(deny_unknown_fields)]
135pub struct K8sServiceSpec {
136    pub selector: HashMap<String, String>,
137    pub ports: Vec<K8sServicePort>,
138    #[serde(default, rename = "type")]
139    pub service_type: Option<String>,
140    #[serde(default, rename = "clusterIP")]
141    pub cluster_ip: Option<String>,
142}
143
144/// Kubernetes Service
145#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct K8sService {
148    #[serde(rename = "apiVersion")]
149    pub api_version: String,
150    pub kind: String,
151    pub metadata: K8sMetadata,
152    pub spec: K8sServiceSpec,
153}
154
155/// Kubernetes ConfigMap
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(deny_unknown_fields)]
158pub struct K8sConfigMap {
159    #[serde(rename = "apiVersion")]
160    pub api_version: String,
161    pub kind: String,
162    pub metadata: K8sMetadata,
163    #[serde(default)]
164    pub data: HashMap<String, String>,
165    #[serde(default, rename = "binaryData")]
166    pub binary_data: Option<HashMap<String, String>>,
167}
168
169/// Kubernetes Secret
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(deny_unknown_fields)]
172pub struct K8sSecret {
173    #[serde(rename = "apiVersion")]
174    pub api_version: String,
175    pub kind: String,
176    pub metadata: K8sMetadata,
177    #[serde(default, rename = "type")]
178    pub secret_type: Option<String>,
179    #[serde(default)]
180    pub data: HashMap<String, String>,
181    #[serde(default, rename = "stringData")]
182    pub string_data: Option<HashMap<String, String>>,
183}
184
185/// Validation result for K8s resources
186impl K8sDeployment {
187    pub fn validate(&self) -> Vec<String> {
188        let mut warnings = Vec::new();
189
190        if self.kind != "Deployment" {
191            warnings.push(format!("Expected kind 'Deployment', got '{}'", self.kind));
192        }
193
194        if let Some(replicas) = self.spec.replicas {
195            if replicas == 0 {
196                warnings.push("Replicas is 0 — no pods will be created".to_string());
197            }
198            if replicas > 100 {
199                warnings.push(format!("Replicas is {} — unusually high", replicas));
200            }
201        }
202
203        for container in &self.spec.template.spec.containers {
204            if container.resources.is_none() {
205                warnings.push(format!(
206                    "Container '{}' has no resource limits/requests",
207                    container.name
208                ));
209            }
210            for port in &container.ports {
211                if port.container_port == 0 {
212                    warnings.push(format!(
213                        "Container '{}': invalid port {}",
214                        container.name, port.container_port
215                    ));
216                }
217            }
218        }
219
220        warnings
221    }
222}