use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sMetadata {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub labels: HashMap<String, String>,
#[serde(default)]
pub annotations: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sResourceRequirements {
#[serde(default)]
pub limits: HashMap<String, String>,
#[serde(default)]
pub requests: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sContainerPort {
#[serde(rename = "containerPort")]
pub container_port: u16,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub protocol: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sEnvVar {
pub name: String,
#[serde(default)]
pub value: Option<String>,
#[serde(default, rename = "valueFrom")]
pub value_from: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sContainer {
pub name: String,
pub image: String,
#[serde(default)]
pub ports: Vec<K8sContainerPort>,
#[serde(default)]
pub env: Vec<K8sEnvVar>,
#[serde(default)]
pub resources: Option<K8sResourceRequirements>,
#[serde(default)]
pub command: Vec<String>,
#[serde(default)]
pub args: Vec<String>,
#[serde(default, rename = "imagePullPolicy")]
pub image_pull_policy: Option<String>,
#[serde(default, rename = "livenessProbe")]
pub liveness_probe: Option<serde_json::Value>,
#[serde(default, rename = "readinessProbe")]
pub readiness_probe: Option<serde_json::Value>,
#[serde(default, rename = "volumeMounts")]
pub volume_mounts: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sPodSpec {
pub containers: Vec<K8sContainer>,
#[serde(default, rename = "initContainers")]
pub init_containers: Vec<K8sContainer>,
#[serde(default, rename = "restartPolicy")]
pub restart_policy: Option<String>,
#[serde(default, rename = "serviceAccountName")]
pub service_account_name: Option<String>,
#[serde(default)]
pub volumes: Option<serde_json::Value>,
#[serde(default, rename = "nodeSelector")]
pub node_selector: Option<HashMap<String, String>>,
#[serde(default)]
pub tolerations: Option<serde_json::Value>,
#[serde(default)]
pub affinity: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sPodTemplate {
pub metadata: K8sMetadata,
pub spec: K8sPodSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sDeploymentSpec {
pub replicas: Option<u32>,
pub selector: serde_json::Value,
pub template: K8sPodTemplate,
#[serde(default)]
pub strategy: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sDeployment {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: K8sMetadata,
pub spec: K8sDeploymentSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sServicePort {
pub port: u16,
#[serde(default, rename = "targetPort")]
pub target_port: Option<serde_json::Value>,
#[serde(default)]
pub protocol: Option<String>,
#[serde(default)]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sServiceSpec {
pub selector: HashMap<String, String>,
pub ports: Vec<K8sServicePort>,
#[serde(default, rename = "type")]
pub service_type: Option<String>,
#[serde(default, rename = "clusterIP")]
pub cluster_ip: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sService {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: K8sMetadata,
pub spec: K8sServiceSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sConfigMap {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: K8sMetadata,
#[serde(default)]
pub data: HashMap<String, String>,
#[serde(default, rename = "binaryData")]
pub binary_data: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct K8sSecret {
#[serde(rename = "apiVersion")]
pub api_version: String,
pub kind: String,
pub metadata: K8sMetadata,
#[serde(default, rename = "type")]
pub secret_type: Option<String>,
#[serde(default)]
pub data: HashMap<String, String>,
#[serde(default, rename = "stringData")]
pub string_data: Option<HashMap<String, String>>,
}
impl K8sDeployment {
pub fn validate(&self) -> Vec<String> {
let mut warnings = Vec::new();
if self.kind != "Deployment" {
warnings.push(format!("Expected kind 'Deployment', got '{}'", self.kind));
}
if let Some(replicas) = self.spec.replicas {
if replicas == 0 {
warnings.push("Replicas is 0 — no pods will be created".to_string());
}
if replicas > 100 {
warnings.push(format!("Replicas is {} — unusually high", replicas));
}
}
for container in &self.spec.template.spec.containers {
if container.resources.is_none() {
warnings.push(format!(
"Container '{}' has no resource limits/requests",
container.name
));
}
for port in &container.ports {
if port.container_port == 0 {
warnings.push(format!(
"Container '{}': invalid port {}",
container.name, port.container_port
));
}
}
}
warnings
}
}