Skip to main content

devops_models/models/
k8s_storage.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use crate::models::k8s::K8sMetadata;
4use crate::models::validation::{ConfigValidator, Diagnostic, Severity, YamlType};
5
6// ═══════════════════════════════════════════════════════════════════════════
7// PersistentVolumeClaim
8// ═══════════════════════════════════════════════════════════════════════════
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct PVCResources {
13    #[serde(default)]
14    pub requests: HashMap<String, String>,
15    #[serde(default)]
16    pub limits: HashMap<String, String>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct PVCSpec {
22    #[serde(rename = "accessModes")]
23    pub access_modes: Vec<String>,
24    #[serde(default)]
25    pub resources: Option<PVCResources>,
26    #[serde(default, rename = "storageClassName")]
27    pub storage_class_name: Option<String>,
28    #[serde(default, rename = "volumeMode")]
29    pub volume_mode: Option<String>,
30    #[serde(default, rename = "volumeName")]
31    pub volume_name: Option<String>,
32    #[serde(default)]
33    pub selector: Option<serde_json::Value>,
34    #[serde(default, rename = "dataSource")]
35    pub data_source: Option<serde_json::Value>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct K8sPVC {
41    #[serde(rename = "apiVersion")]
42    pub api_version: String,
43    pub kind: String,
44    pub metadata: K8sMetadata,
45    pub spec: PVCSpec,
46}
47
48const VALID_ACCESS_MODES: &[&str] = &[
49    "ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod",
50];
51
52impl ConfigValidator for K8sPVC {
53    fn yaml_type(&self) -> YamlType { YamlType::K8sPVC }
54
55    fn validate_structure(&self) -> Vec<Diagnostic> {
56        let mut diags = Vec::new();
57        if self.spec.access_modes.is_empty() {
58            diags.push(Diagnostic {
59                severity: Severity::Error,
60                message: "accessModes is required and cannot be empty".into(),
61                path: Some("spec > accessModes".into()),
62            });
63        }
64        for mode in &self.spec.access_modes {
65            if !VALID_ACCESS_MODES.contains(&mode.as_str()) {
66                diags.push(Diagnostic {
67                    severity: Severity::Error,
68                    message: format!("Invalid accessMode '{}' — expected one of: {}", mode, VALID_ACCESS_MODES.join(", ")),
69                    path: Some("spec > accessModes".into()),
70                });
71            }
72        }
73        diags
74    }
75
76    fn validate_semantics(&self) -> Vec<Diagnostic> {
77        let mut diags = Vec::new();
78        if self.spec.resources.is_none() {
79            diags.push(Diagnostic {
80                severity: Severity::Warning,
81                message: "No resources.requests.storage — PVC size is undefined".into(),
82                path: Some("spec > resources".into()),
83            });
84        } else if let Some(res) = &self.spec.resources
85            && !res.requests.contains_key("storage") {
86                diags.push(Diagnostic {
87                    severity: Severity::Warning,
88                    message: "No resources.requests.storage — PVC size is undefined".into(),
89                    path: Some("spec > resources > requests".into()),
90                });
91            }
92        if self.spec.storage_class_name.is_none() {
93            diags.push(Diagnostic {
94                severity: Severity::Info,
95                message: "No storageClassName — cluster default StorageClass will be used".into(),
96                path: Some("spec > storageClassName".into()),
97            });
98        }
99        diags
100    }
101}