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: &corev1::Pod) -> metav1::OwnerReference {
84 metav1::OwnerReference {
85 api_version: "v1".into(),
86 kind: POD_GVK.kind.clone(),
87 name: pod.name_any(),
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(gvk: &GVK, obj: &mut DynamicObject) {
116 obj.types = Some(gvk.into_type_meta());
128
129 obj.metadata.creation_timestamp = None;
132 obj.metadata.deletion_timestamp = None;
133 obj.metadata.deletion_grace_period_seconds = None;
134 obj.metadata.generation = None;
135 obj.metadata.managed_fields = None;
136 obj.metadata.resource_version = None;
137 obj.metadata.uid = None;
138
139 obj.annotations_mut().remove(LAST_APPLIED_CONFIG_LABEL_KEY);
140 obj.annotations_mut().remove(DEPL_REVISION_LABEL_KEY);
141
142 if gvk == &*POD_GVK {
145 dyn_obj_spec_mut(obj).map(|spec| spec.remove("nodeName"));
146 }
147}
148
149pub fn split_namespaced_name(name: &str) -> (String, String) {
150 match name.split_once('/') {
151 Some((namespace, name)) => (namespace.into(), name.into()),
152 None => ("".into(), name.into()),
153 }
154}
155
156pub fn truncate_label(mut value: String) -> String {
157 if value.len() > MAX_LABEL_LENGTH {
158 value.truncate(MAX_LABEL_LENGTH - 4);
159 value.push_str("XXXX");
160 }
161 value
162}
163
164impl<T: Resource> KubeResourceExt for T {
165 fn namespaced_name(&self) -> String {
166 match self.namespace() {
167 Some(ns) => format!("{}/{}", ns, self.name_any()),
168 None => self.name_any().clone(),
169 }
170 }
171
172 fn matches(&self, sel: &metav1::LabelSelector) -> anyhow::Result<bool> {
173 if let Some(exprs) = &sel.match_expressions {
174 for expr in exprs {
175 if !label_expr_match(self.labels(), expr)? {
176 return Ok(false);
177 }
178 }
179 }
180
181 if let Some(labels) = &sel.match_labels {
182 for (k, v) in labels {
183 if self.labels().get(k) != Some(v) {
184 return Ok(false);
185 }
186 }
187 }
188 Ok(true)
189 }
190}
191
192fn build_object_meta_helper<K>(namespace: Option<String>, name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
193where
194 K: Resource<DynamicType = ()>,
195{
196 let mut meta = metav1::ObjectMeta {
197 namespace,
198 name: Some(name.into()),
199 ..Default::default()
200 };
201
202 add_common_metadata(sim_name, owner, &mut meta);
203 meta
204}
205
206pub(super) const OPERATOR_IN: &str = "In";
209pub(super) const OPERATOR_NOT_IN: &str = "NotIn";
210pub(super) const OPERATOR_EXISTS: &str = "Exists";
211pub(super) const OPERATOR_DOES_NOT_EXIST: &str = "DoesNotExist";
212
213fn label_expr_match(
214 obj_labels: &BTreeMap<String, String>,
215 expr: &metav1::LabelSelectorRequirement,
216) -> anyhow::Result<bool> {
217 match expr.operator.as_str() {
220 OPERATOR_IN => match obj_labels.get(&expr.key) {
221 Some(v) => match &expr.values {
222 Some(values) if !values.is_empty() => Ok(values.contains(v)),
223 _ => bail!(KubernetesError::malformed_label_selector(expr)),
224 },
225 None => Ok(false),
226 },
227 OPERATOR_NOT_IN => match obj_labels.get(&expr.key) {
228 Some(v) => match &expr.values {
229 Some(values) if !values.is_empty() => Ok(!values.contains(v)),
230 _ => bail!(KubernetesError::malformed_label_selector(expr)),
231 },
232 None => Ok(true),
233 },
234 OPERATOR_EXISTS => match &expr.values {
235 Some(values) if !values.is_empty() => bail!(KubernetesError::malformed_label_selector(expr)),
236 _ => Ok(obj_labels.contains_key(&expr.key)),
237 },
238 OPERATOR_DOES_NOT_EXIST => match &expr.values {
239 Some(values) if !values.is_empty() => {
240 bail!(KubernetesError::malformed_label_selector(expr));
241 },
242 _ => Ok(!obj_labels.contains_key(&expr.key)),
243 },
244 _ => bail!("malformed label selector expression: {:?}", expr),
245 }
246}