syncable_cli/analyzer/kubelint/templates/
replicas.rs1use crate::analyzer::kubelint::context::{K8sObject, Object};
4use crate::analyzer::kubelint::templates::{CheckFunc, ParameterDesc, Template, TemplateError};
5use crate::analyzer::kubelint::types::{Diagnostic, ObjectKindsDesc};
6
7pub struct ReplicasTemplate;
9
10impl Template for ReplicasTemplate {
11 fn key(&self) -> &str {
12 "replicas"
13 }
14
15 fn human_name(&self) -> &str {
16 "Minimum Replicas"
17 }
18
19 fn description(&self) -> &str {
20 "Checks that deployments have at least a minimum number of replicas"
21 }
22
23 fn supported_object_kinds(&self) -> ObjectKindsDesc {
24 ObjectKindsDesc::new(&["Deployment", "StatefulSet"])
25 }
26
27 fn parameters(&self) -> Vec<ParameterDesc> {
28 vec![ParameterDesc {
29 name: "minReplicas".to_string(),
30 description: "Minimum required replicas".to_string(),
31 param_type: "integer".to_string(),
32 required: false,
33 default: Some(serde_yaml::Value::Number(2.into())),
34 }]
35 }
36
37 fn instantiate(&self, params: &serde_yaml::Value) -> Result<Box<dyn CheckFunc>, TemplateError> {
38 let min_replicas = params
39 .get("minReplicas")
40 .and_then(|v| v.as_i64())
41 .unwrap_or(2) as i32;
42 Ok(Box::new(ReplicasCheck { min_replicas }))
43 }
44}
45
46struct ReplicasCheck {
47 min_replicas: i32,
48}
49
50impl CheckFunc for ReplicasCheck {
51 fn check(&self, object: &Object) -> Vec<Diagnostic> {
52 let mut diagnostics = Vec::new();
53
54 let replicas = match &object.k8s_object {
55 K8sObject::Deployment(d) => d.replicas,
56 K8sObject::StatefulSet(s) => s.replicas,
57 _ => None,
58 };
59
60 if let Some(replica_count) = replicas {
61 if replica_count < self.min_replicas {
62 diagnostics.push(Diagnostic {
63 message: format!(
64 "Object has only {} replicas, but minimum recommended is {}",
65 replica_count, self.min_replicas
66 ),
67 remediation: Some(format!(
68 "Increase replicas to at least {} for better availability.",
69 self.min_replicas
70 )),
71 });
72 }
73 } else {
74 if self.min_replicas > 1 {
76 diagnostics.push(Diagnostic {
77 message: format!(
78 "No replica count specified (defaults to 1), but minimum recommended is {}",
79 self.min_replicas
80 ),
81 remediation: Some(format!(
82 "Explicitly set replicas to at least {}.",
83 self.min_replicas
84 )),
85 });
86 }
87 }
88
89 diagnostics
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::analyzer::kubelint::parser::yaml::parse_yaml;
97
98 #[test]
99 fn test_low_replicas_detected() {
100 let yaml = r#"
101apiVersion: apps/v1
102kind: Deployment
103metadata:
104 name: single-replica
105spec:
106 replicas: 1
107 template:
108 spec:
109 containers:
110 - name: nginx
111 image: nginx:1.21.0
112"#;
113 let objects = parse_yaml(yaml).unwrap();
114 let check = ReplicasCheck { min_replicas: 2 };
115 let diagnostics = check.check(&objects[0]);
116 assert_eq!(diagnostics.len(), 1);
117 assert!(diagnostics[0].message.contains("only 1 replicas"));
118 }
119
120 #[test]
121 fn test_adequate_replicas_ok() {
122 let yaml = r#"
123apiVersion: apps/v1
124kind: Deployment
125metadata:
126 name: multi-replica
127spec:
128 replicas: 3
129 template:
130 spec:
131 containers:
132 - name: nginx
133 image: nginx:1.21.0
134"#;
135 let objects = parse_yaml(yaml).unwrap();
136 let check = ReplicasCheck { min_replicas: 2 };
137 let diagnostics = check.check(&objects[0]);
138 assert!(diagnostics.is_empty());
139 }
140
141 #[test]
142 fn test_no_replicas_detected() {
143 let yaml = r#"
144apiVersion: apps/v1
145kind: Deployment
146metadata:
147 name: no-replica-spec
148spec:
149 template:
150 spec:
151 containers:
152 - name: nginx
153 image: nginx:1.21.0
154"#;
155 let objects = parse_yaml(yaml).unwrap();
156 let check = ReplicasCheck { min_replicas: 2 };
157 let diagnostics = check.check(&objects[0]);
158 assert_eq!(diagnostics.len(), 1);
159 assert!(diagnostics[0].message.contains("defaults to 1"));
160 }
161}