1use std::collections::HashMap;
2use std::fs::File;
3use std::ops::Not;
4
5use serde::{
6 Deserialize,
7 Serialize,
8};
9use sk_core::k8s::GVK;
10use tracing::*;
11
12#[derive(Deserialize)]
13#[serde(rename_all = "camelCase")]
14struct TrackedObjectConfigWithDeprecatedFields {
15 #[deprecated]
16 pub pod_spec_template_path: Option<String>,
17 pub pod_spec_template_paths: Option<Vec<String>>,
18
19 #[serde(default)]
20 pub track_lifecycle: bool,
21}
22
23#[derive(Clone, Debug, Default, Deserialize, Serialize)]
24#[serde(rename_all = "camelCase", from = "TrackedObjectConfigWithDeprecatedFields")]
25pub struct TrackedObjectConfig {
26 pub pod_spec_template_paths: Option<Vec<String>>,
27
28 #[serde(skip_serializing_if = "<&bool>::not")]
29 pub track_lifecycle: bool,
30}
31
32impl From<TrackedObjectConfigWithDeprecatedFields> for TrackedObjectConfig {
33 fn from(input: TrackedObjectConfigWithDeprecatedFields) -> Self {
34 let mut output = TrackedObjectConfig {
35 pod_spec_template_paths: input.pod_spec_template_paths.clone(),
36 track_lifecycle: input.track_lifecycle,
37 };
38
39 #[allow(deprecated)]
40 if let Some(pstp) = input.pod_spec_template_path {
41 warn!(
42 "tracked object config field podSpecTemplatePath is deprecated \
43 and will be removed in a future version of SimKube. Please use \
44 podSpecTemplatePaths instead."
45 );
46
47 if input.pod_spec_template_paths.as_ref().is_some_and(|p| !p.is_empty()) {
48 warn!(
49 "both podSpecTemplatePath and podSpecTemplatePaths are set; \
50 ignoring the deprecated field."
51 );
52 } else {
53 output.pod_spec_template_paths = Some(vec![pstp]);
54 }
55 }
56
57 output
58 }
59}
60
61#[derive(Clone, Debug, Default, Deserialize, Serialize)]
62#[serde(rename_all = "camelCase")]
63pub struct TracerConfig {
64 pub tracked_objects: HashMap<GVK, TrackedObjectConfig>,
65}
66
67impl TracerConfig {
68 pub fn load(filename: &str) -> anyhow::Result<TracerConfig> {
69 Ok(serde_yaml::from_reader(File::open(filename)?)?)
70 }
71
72 pub fn pod_spec_template_paths(&self, gvk: &GVK) -> Option<&[String]> {
73 self.tracked_objects.get(gvk)?.pod_spec_template_paths.as_deref()
74 }
75
76 pub fn track_lifecycle_for(&self, gvk: &GVK) -> bool {
77 self.tracked_objects.get(gvk).is_some_and(|obj| obj.track_lifecycle)
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use sk_testutils::*;
84
85 use super::*;
86
87 #[rstest]
88 #[case::none(None, vec!["/foo/bar".into()])]
89 #[case::empty(Some(vec![]), vec!["/foo/bar".into()])]
90 #[case::full(Some(vec!["/asdf".into()]), vec!["/asdf".into()])]
91 fn test_deprecated_config(#[case] pod_spec_template_paths: Option<Vec<String>>, #[case] expected: Vec<String>) {
92 let gvk = GVK::new("fake", "v1", "Resource");
93 let mut config_yml = "
94---
95trackedObjects:
96 fake/v1.Resource:
97 podSpecTemplatePath: /foo/bar
98"
99 .to_string();
100
101 if let Some(pstps) = pod_spec_template_paths {
102 if pstps.len() > 0 {
103 let pstp = pstps[0].clone();
104 config_yml.push_str(&format!(" podSpecTemplatePaths:\n - {pstp}"));
105 }
106 }
107
108 let config: TracerConfig = serde_yaml::from_str(&config_yml).unwrap();
109
110 assert_eq!(config.tracked_objects[&gvk].pod_spec_template_paths, Some(expected));
111 }
112
113 #[rstest]
114 fn test_correct_config() {
115 let gvk = GVK::new("fake", "v1", "Resource");
116 let config_yml = "
117---
118trackedObjects:
119 fake/v1.Resource:
120 podSpecTemplatePaths:
121 - /foo/bar
122"
123 .to_string();
124
125 let config: TracerConfig = serde_yaml::from_str(&config_yml).unwrap();
126 assert_eq!(config.tracked_objects[&gvk].pod_spec_template_paths, Some(vec!["/foo/bar".into()]));
127 }
128}