1use std::collections::BTreeMap;
2
3use kube::api::Resource;
4use serde_json::{
5 Map,
6 Value,
7};
8
9use super::*;
10use crate::constants::*;
11use crate::errors::*;
12
13const MAX_LABEL_LENGTH: usize = 63;
14
15pub fn add_common_metadata<K>(sim_name: &str, owner: &K, meta: &mut metav1::ObjectMeta)
16where
17 K: Resource<DynamicType = ()>,
18{
19 let labels = &mut meta.labels.get_or_insert_default();
20 labels.insert(SIMULATION_LABEL_KEY.into(), truncate_label(sim_name.into()));
21 labels.insert(
22 APP_KUBERNETES_IO_NAME_KEY.into(),
23 truncate_label(meta.name.clone().unwrap()), );
25
26 meta.owner_references.get_or_insert_default().push(metav1::OwnerReference {
27 api_version: K::api_version(&()).into(),
28 kind: K::kind(&()).into(),
29 name: owner.name_any(),
30
31 block_owner_deletion: Some(true),
37
38 uid: owner.uid().unwrap(),
41 ..Default::default()
42 });
43}
44
45pub fn build_deletable(gvk: &GVK, ns_name: &str) -> DynamicObject {
46 let (ns, name) = split_namespaced_name(ns_name);
47 DynamicObject {
48 metadata: metav1::ObjectMeta {
49 namespace: Some(ns),
50 name: Some(name),
51 ..Default::default()
52 },
53 types: Some(gvk.into_type_meta()),
54 data: Value::Null,
55 }
56}
57
58pub fn build_containment_label_selector(key: &str, labels: Vec<String>) -> metav1::LabelSelector {
59 metav1::LabelSelector {
60 match_expressions: Some(vec![metav1::LabelSelectorRequirement {
61 key: key.into(),
62 operator: "In".into(),
63 values: Some(labels),
64 }]),
65 ..Default::default()
66 }
67}
68
69pub fn build_global_object_meta<K>(name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
70where
71 K: Resource<DynamicType = ()>,
72{
73 build_object_meta_helper(None, name, sim_name, owner)
74}
75
76pub fn build_object_meta<K>(namespace: &str, name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
77where
78 K: Resource<DynamicType = ()>,
79{
80 build_object_meta_helper(Some(namespace.into()), name, sim_name, owner)
81}
82
83pub fn build_pod_self_owner_reference(pod_name: String) -> metav1::OwnerReference {
84 metav1::OwnerReference {
85 api_version: POD_GVK.version.clone(),
86 kind: POD_GVK.kind.clone(),
87 name: pod_name,
88 ..Default::default()
89 }
90}
91
92pub fn dyn_obj_spec(obj: &DynamicObject) -> Option<&Map<String, Value>> {
93 obj.data
94 .as_object()
95 .and_then(|data| data.get("spec").and_then(|spec| spec.as_object()))
96}
97
98pub fn dyn_obj_spec_mut(obj: &mut DynamicObject) -> Option<&mut Map<String, Value>> {
99 obj.data
100 .as_object_mut()
101 .and_then(|data| data.get_mut("spec").and_then(|spec| spec.as_object_mut()))
102}
103
104pub fn dyn_obj_type_str(obj: &DynamicObject) -> String {
105 obj.types
106 .as_ref()
107 .map(|tm| format!("{}.{}", tm.api_version, tm.kind))
108 .unwrap_or("<unknown type>".into())
109}
110
111pub fn format_gvk_name(gvk: &GVK, ns_name: &str) -> String {
112 format!("{gvk}:{ns_name}")
113}
114
115pub fn sanitize_obj<T: kube::Resource>(obj: &mut T) {
116 obj.meta_mut().creation_timestamp = None;
119 obj.meta_mut().deletion_timestamp = None;
120 obj.meta_mut().deletion_grace_period_seconds = None;
121 obj.meta_mut().generation = None;
122 obj.meta_mut().managed_fields = None;
123 obj.meta_mut().resource_version = None;
124 obj.meta_mut().uid = None;
125
126 obj.annotations_mut().remove(LAST_APPLIED_CONFIG_LABEL_KEY);
127 obj.annotations_mut().remove(DEPL_REVISION_LABEL_KEY);
128}
129
130pub fn pod_is_running(pod: &corev1::Pod) -> bool {
131 matches!(pod.status.as_ref(), Some(corev1::PodStatus{phase: Some(phase), ..}) if phase == "Running")
132}
133
134pub fn split_namespaced_name(name: &str) -> (String, String) {
135 match name.split_once('/') {
136 Some((namespace, name)) => (namespace.into(), name.into()),
137 None => ("".into(), name.into()),
138 }
139}
140
141pub fn truncate_label(mut value: String) -> String {
142 if value.len() > MAX_LABEL_LENGTH {
143 value.truncate(MAX_LABEL_LENGTH - 4);
144 value.push_str("XXXX");
145 }
146 value
147}
148
149impl<T: Resource> KubeResourceExt for T {
150 fn namespaced_name(&self) -> String {
151 match self.namespace() {
152 Some(ns) => format!("{}/{}", ns, self.name_any()),
153 None => self.name_any().clone(),
154 }
155 }
156
157 fn matches(&self, sel: &metav1::LabelSelector) -> anyhow::Result<bool> {
158 if let Some(exprs) = &sel.match_expressions {
159 for expr in exprs {
160 if !label_expr_match(self.labels(), expr)? {
161 return Ok(false);
162 }
163 }
164 }
165
166 if let Some(labels) = &sel.match_labels {
167 for (k, v) in labels {
168 if self.labels().get(k) != Some(v) {
169 return Ok(false);
170 }
171 }
172 }
173 Ok(true)
174 }
175}
176
177fn build_object_meta_helper<K>(namespace: Option<String>, name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
178where
179 K: Resource<DynamicType = ()>,
180{
181 let mut meta = metav1::ObjectMeta {
182 namespace,
183 name: Some(name.into()),
184 ..Default::default()
185 };
186
187 add_common_metadata(sim_name, owner, &mut meta);
188 meta
189}
190
191pub(super) const OPERATOR_IN: &str = "In";
194pub(super) const OPERATOR_NOT_IN: &str = "NotIn";
195pub(super) const OPERATOR_EXISTS: &str = "Exists";
196pub(super) const OPERATOR_DOES_NOT_EXIST: &str = "DoesNotExist";
197
198fn label_expr_match(
199 obj_labels: &BTreeMap<String, String>,
200 expr: &metav1::LabelSelectorRequirement,
201) -> anyhow::Result<bool> {
202 match expr.operator.as_str() {
205 OPERATOR_IN => match obj_labels.get(&expr.key) {
206 Some(v) => match &expr.values {
207 Some(values) if !values.is_empty() => Ok(values.contains(v)),
208 _ => bail!(KubernetesError::malformed_label_selector(expr)),
209 },
210 None => Ok(false),
211 },
212 OPERATOR_NOT_IN => match obj_labels.get(&expr.key) {
213 Some(v) => match &expr.values {
214 Some(values) if !values.is_empty() => Ok(!values.contains(v)),
215 _ => bail!(KubernetesError::malformed_label_selector(expr)),
216 },
217 None => Ok(true),
218 },
219 OPERATOR_EXISTS => match &expr.values {
220 Some(values) if !values.is_empty() => bail!(KubernetesError::malformed_label_selector(expr)),
221 _ => Ok(obj_labels.contains_key(&expr.key)),
222 },
223 OPERATOR_DOES_NOT_EXIST => match &expr.values {
224 Some(values) if !values.is_empty() => {
225 bail!(KubernetesError::malformed_label_selector(expr));
226 },
227 _ => Ok(!obj_labels.contains_key(&expr.key)),
228 },
229 _ => bail!("malformed label selector expression: {:?}", expr),
230 }
231}