Skip to main content

sk_core/k8s/
util.rs

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()), // everything should have a name (???)
24    );
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        // if the delete propagation policy is set to foreground, this will block
32        // the owner from being deleted until this object is deleted
33        // (note _both_ must be set, otherwise it doesn't work)
34        //
35        // https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion
36        block_owner_deletion: Some(true),
37
38        // Kubernetes "should" always set this and I'm tired of all the
39        // error propogation trying to check for this induces
40        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    // N.B. We do not sanitize owner references here, since we need them
117    // to compute owner chains in the TraceStore
118    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
191// The meanings of these operators is explained here:
192// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#set-based-requirement
193pub(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    // LabelSelectorRequirement is considered invalid if the Operator is "In" or NotIn"
203    // and there are no values; conversely for "Exists" and "DoesNotExist".
204    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}