syncable_cli/analyzer/kubelint/templates/
pdb.rs1use crate::analyzer::kubelint::context::K8sObject;
4use crate::analyzer::kubelint::context::Object;
5use crate::analyzer::kubelint::templates::{CheckFunc, ParameterDesc, Template, TemplateError};
6use crate::analyzer::kubelint::types::{Diagnostic, ObjectKindsDesc};
7
8pub struct PdbMaxUnavailableTemplate;
10
11impl Template for PdbMaxUnavailableTemplate {
12 fn key(&self) -> &str {
13 "pdb-max-unavailable"
14 }
15
16 fn human_name(&self) -> &str {
17 "PDB Max Unavailable"
18 }
19
20 fn description(&self) -> &str {
21 "Checks PodDisruptionBudget maxUnavailable settings"
22 }
23
24 fn supported_object_kinds(&self) -> ObjectKindsDesc {
25 ObjectKindsDesc::new(&["PodDisruptionBudget"])
26 }
27
28 fn parameters(&self) -> Vec<ParameterDesc> {
29 Vec::new()
30 }
31
32 fn instantiate(
33 &self,
34 _params: &serde_yaml::Value,
35 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
36 Ok(Box::new(PdbMaxUnavailableCheck))
37 }
38}
39
40struct PdbMaxUnavailableCheck;
41
42impl CheckFunc for PdbMaxUnavailableCheck {
43 fn check(&self, object: &Object) -> Vec<Diagnostic> {
44 let mut diagnostics = Vec::new();
45
46 if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object
47 && let Some(max_unavailable) = &pdb.max_unavailable
48 {
49 if max_unavailable == "0" || max_unavailable == "0%" {
51 diagnostics.push(Diagnostic {
52 message:
53 "PDB maxUnavailable is set to 0, which blocks all voluntary disruptions"
54 .to_string(),
55 remediation: Some(
56 "Set maxUnavailable to at least 1 or a non-zero percentage to allow \
57 voluntary disruptions during cluster maintenance."
58 .to_string(),
59 ),
60 });
61 }
62 }
63
64 diagnostics
65 }
66}
67
68pub struct PdbMinAvailableTemplate;
70
71impl Template for PdbMinAvailableTemplate {
72 fn key(&self) -> &str {
73 "pdb-min-available"
74 }
75
76 fn human_name(&self) -> &str {
77 "PDB Min Available"
78 }
79
80 fn description(&self) -> &str {
81 "Checks PodDisruptionBudget minAvailable settings"
82 }
83
84 fn supported_object_kinds(&self) -> ObjectKindsDesc {
85 ObjectKindsDesc::new(&["PodDisruptionBudget"])
86 }
87
88 fn parameters(&self) -> Vec<ParameterDesc> {
89 Vec::new()
90 }
91
92 fn instantiate(
93 &self,
94 _params: &serde_yaml::Value,
95 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
96 Ok(Box::new(PdbMinAvailableCheck))
97 }
98}
99
100struct PdbMinAvailableCheck;
101
102impl CheckFunc for PdbMinAvailableCheck {
103 fn check(&self, object: &Object) -> Vec<Diagnostic> {
104 let mut diagnostics = Vec::new();
105
106 if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object
107 && let Some(min_available) = &pdb.min_available
108 {
109 if min_available == "100%" {
111 diagnostics.push(Diagnostic {
112 message:
113 "PDB minAvailable is set to 100%, which blocks all voluntary disruptions"
114 .to_string(),
115 remediation: Some(
116 "Set minAvailable to less than 100% to allow voluntary disruptions \
117 during cluster maintenance."
118 .to_string(),
119 ),
120 });
121 }
122 }
123
124 diagnostics
125 }
126}
127
128pub struct PdbUnhealthyPodEvictionPolicyTemplate;
130
131impl Template for PdbUnhealthyPodEvictionPolicyTemplate {
132 fn key(&self) -> &str {
133 "pdb-unhealthy-pod-eviction-policy"
134 }
135
136 fn human_name(&self) -> &str {
137 "PDB Unhealthy Pod Eviction Policy"
138 }
139
140 fn description(&self) -> &str {
141 "Checks PodDisruptionBudget unhealthyPodEvictionPolicy settings"
142 }
143
144 fn supported_object_kinds(&self) -> ObjectKindsDesc {
145 ObjectKindsDesc::new(&["PodDisruptionBudget"])
146 }
147
148 fn parameters(&self) -> Vec<ParameterDesc> {
149 Vec::new()
150 }
151
152 fn instantiate(
153 &self,
154 _params: &serde_yaml::Value,
155 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
156 Ok(Box::new(PdbUnhealthyPodEvictionPolicyCheck))
157 }
158}
159
160struct PdbUnhealthyPodEvictionPolicyCheck;
161
162impl CheckFunc for PdbUnhealthyPodEvictionPolicyCheck {
163 fn check(&self, object: &Object) -> Vec<Diagnostic> {
164 let mut diagnostics = Vec::new();
165
166 if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object {
167 if pdb.unhealthy_pod_eviction_policy.is_none() {
169 diagnostics.push(Diagnostic {
170 message: "PDB does not specify unhealthyPodEvictionPolicy".to_string(),
171 remediation: Some(
172 "Consider setting unhealthyPodEvictionPolicy to 'AlwaysAllow' to allow \
173 eviction of unhealthy pods even when budget is violated."
174 .to_string(),
175 ),
176 });
177 }
178 }
179
180 diagnostics
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::analyzer::kubelint::parser::yaml::parse_yaml;
188
189 #[test]
190 fn test_pdb_max_unavailable_zero() {
191 let yaml = r#"
192apiVersion: policy/v1
193kind: PodDisruptionBudget
194metadata:
195 name: strict-pdb
196spec:
197 maxUnavailable: 0
198 selector:
199 matchLabels:
200 app: test
201"#;
202 let objects = parse_yaml(yaml).unwrap();
203 let check = PdbMaxUnavailableCheck;
204 let diagnostics = check.check(&objects[0]);
205 assert_eq!(diagnostics.len(), 1);
206 assert!(diagnostics[0].message.contains("maxUnavailable"));
207 }
208
209 #[test]
210 fn test_pdb_min_available_100_percent() {
211 let yaml = r#"
212apiVersion: policy/v1
213kind: PodDisruptionBudget
214metadata:
215 name: strict-pdb
216spec:
217 minAvailable: "100%"
218 selector:
219 matchLabels:
220 app: test
221"#;
222 let objects = parse_yaml(yaml).unwrap();
223 let check = PdbMinAvailableCheck;
224 let diagnostics = check.check(&objects[0]);
225 assert_eq!(diagnostics.len(), 1);
226 assert!(diagnostics[0].message.contains("minAvailable"));
227 }
228}