1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[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#[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#[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#[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#[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
185impl 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}