syncable_cli/analyzer/k8s_optimize/
pragma.rs

1//! Annotation-based rule ignoring for k8s-optimize.
2//!
3//! Supports `ignore-check.k8s-optimize.io/<check-code>` annotations
4//! to disable specific optimization checks for individual objects.
5//!
6//! # Example
7//!
8//! ```yaml
9//! apiVersion: apps/v1
10//! kind: Deployment
11//! metadata:
12//!   name: my-app
13//!   annotations:
14//!     # Ignore the high CPU request check for this deployment
15//!     ignore-check.k8s-optimize.io/K8S-OPT-005: "Batch processing requires high CPU"
16//!     # Ignore the excessive CPU ratio check
17//!     ignore-check.k8s-optimize.io/K8S-OPT-007: ""
18//! spec:
19//!   # ...
20//! ```
21
22use std::collections::HashSet;
23
24/// Prefix for k8s-optimize ignore annotations.
25pub const IGNORE_ANNOTATION_PREFIX: &str = "ignore-check.k8s-optimize.io/";
26
27/// Extract the set of ignored rule codes from an object's annotations.
28///
29/// # Arguments
30///
31/// * `annotations` - Optional map of annotations from the object metadata
32///
33/// # Returns
34///
35/// A set of rule codes (e.g., "K8S-OPT-001", "K8S-OPT-005") that should be ignored.
36pub fn get_ignored_rules(
37    annotations: Option<&std::collections::BTreeMap<String, String>>,
38) -> HashSet<String> {
39    let mut ignored = HashSet::new();
40
41    if let Some(annotations) = annotations {
42        for key in annotations.keys() {
43            if let Some(rule_code) = key.strip_prefix(IGNORE_ANNOTATION_PREFIX) {
44                ignored.insert(rule_code.to_string());
45            }
46        }
47    }
48
49    ignored
50}
51
52/// Check if a specific rule should be ignored for an object.
53///
54/// # Arguments
55///
56/// * `annotations` - Optional map of annotations from the object metadata
57/// * `rule_code` - The rule code to check (e.g., "K8S-OPT-001")
58///
59/// # Returns
60///
61/// `true` if the rule should be ignored, `false` otherwise.
62pub fn should_ignore_rule(
63    annotations: Option<&std::collections::BTreeMap<String, String>>,
64    rule_code: &str,
65) -> bool {
66    if let Some(annotations) = annotations {
67        let annotation_key = format!("{}{}", IGNORE_ANNOTATION_PREFIX, rule_code);
68        annotations.contains_key(&annotation_key)
69    } else {
70        false
71    }
72}
73
74/// Extract annotations from a YAML value's metadata.
75pub fn extract_annotations(
76    yaml: &serde_yaml::Value,
77) -> Option<std::collections::BTreeMap<String, String>> {
78    let metadata = yaml.get("metadata")?;
79    let annotations = metadata.get("annotations")?;
80    let annotations_map = annotations.as_mapping()?;
81
82    let mut result = std::collections::BTreeMap::new();
83    for (key, value) in annotations_map {
84        if let (Some(k), Some(v)) = (key.as_str(), value.as_str()) {
85            result.insert(k.to_string(), v.to_string());
86        }
87    }
88
89    if result.is_empty() {
90        None
91    } else {
92        Some(result)
93    }
94}
95
96/// Get the reason for ignoring a rule (if provided in the annotation value).
97pub fn get_ignore_reason(
98    annotations: Option<&std::collections::BTreeMap<String, String>>,
99    rule_code: &str,
100) -> Option<String> {
101    let annotations = annotations?;
102    let annotation_key = format!("{}{}", IGNORE_ANNOTATION_PREFIX, rule_code);
103    let value = annotations.get(&annotation_key)?;
104
105    if value.is_empty() {
106        None
107    } else {
108        Some(value.clone())
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use std::collections::BTreeMap;
116
117    #[test]
118    fn test_get_ignored_rules() {
119        let mut annotations = BTreeMap::new();
120        annotations.insert(
121            "ignore-check.k8s-optimize.io/K8S-OPT-001".to_string(),
122            "".to_string(),
123        );
124        annotations.insert(
125            "ignore-check.k8s-optimize.io/K8S-OPT-005".to_string(),
126            "Batch job needs high CPU".to_string(),
127        );
128        annotations.insert("other-annotation".to_string(), "value".to_string());
129
130        let ignored = get_ignored_rules(Some(&annotations));
131
132        assert!(ignored.contains("K8S-OPT-001"));
133        assert!(ignored.contains("K8S-OPT-005"));
134        assert!(!ignored.contains("K8S-OPT-002"));
135        assert_eq!(ignored.len(), 2);
136    }
137
138    #[test]
139    fn test_should_ignore_rule() {
140        let mut annotations = BTreeMap::new();
141        annotations.insert(
142            "ignore-check.k8s-optimize.io/K8S-OPT-001".to_string(),
143            "".to_string(),
144        );
145
146        assert!(should_ignore_rule(Some(&annotations), "K8S-OPT-001"));
147        assert!(!should_ignore_rule(Some(&annotations), "K8S-OPT-002"));
148        assert!(!should_ignore_rule(None, "K8S-OPT-001"));
149    }
150
151    #[test]
152    fn test_get_ignore_reason() {
153        let mut annotations = BTreeMap::new();
154        annotations.insert(
155            "ignore-check.k8s-optimize.io/K8S-OPT-001".to_string(),
156            "".to_string(),
157        );
158        annotations.insert(
159            "ignore-check.k8s-optimize.io/K8S-OPT-005".to_string(),
160            "Batch job needs high CPU".to_string(),
161        );
162
163        assert_eq!(get_ignore_reason(Some(&annotations), "K8S-OPT-001"), None);
164        assert_eq!(
165            get_ignore_reason(Some(&annotations), "K8S-OPT-005"),
166            Some("Batch job needs high CPU".to_string())
167        );
168        assert_eq!(get_ignore_reason(Some(&annotations), "K8S-OPT-002"), None);
169    }
170
171    #[test]
172    fn test_extract_annotations() {
173        let yaml = serde_yaml::from_str::<serde_yaml::Value>(
174            r#"
175apiVersion: apps/v1
176kind: Deployment
177metadata:
178  name: test
179  annotations:
180    ignore-check.k8s-optimize.io/K8S-OPT-001: ""
181    other: value
182"#,
183        )
184        .unwrap();
185
186        let annotations = extract_annotations(&yaml);
187        assert!(annotations.is_some());
188
189        let annotations = annotations.unwrap();
190        assert!(annotations.contains_key("ignore-check.k8s-optimize.io/K8S-OPT-001"));
191        assert!(annotations.contains_key("other"));
192    }
193}