Skip to main content

sk_core/k8s/
util.rs

1use std::collections::BTreeMap;
2
3use kube::api::Resource;
4use serde_json as json;
5
6use super::*;
7use crate::errors::*;
8use crate::prelude::*;
9
10pub fn add_common_metadata<K>(sim_name: &str, owner: &K, meta: &mut metav1::ObjectMeta)
11where
12    K: Resource<DynamicType = ()>,
13{
14    let labels = &mut meta.labels.get_or_insert_default();
15    labels.insert(SIMULATION_LABEL_KEY.into(), sim_name.into());
16    labels.insert(APP_KUBERNETES_IO_NAME_KEY.into(), meta.name.clone().unwrap());
17
18    meta.owner_references.get_or_insert_default().push(metav1::OwnerReference {
19        api_version: K::api_version(&()).into(),
20        kind: K::kind(&()).into(),
21        name: owner.name_any(),
22
23        // if the delete propagation policy is set to foreground, this will block
24        // the owner from being deleted until this object is deleted
25        // (note _both_ must be set, otherwise it doesn't work)
26        //
27        // https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion
28        block_owner_deletion: Some(true),
29
30        // Kubernetes "should" always set this and I'm tired of all the
31        // error propogation trying to check for this induces
32        uid: owner.uid().unwrap(),
33        ..Default::default()
34    });
35}
36
37pub fn build_deletable(gvk: &GVK, ns_name: &str) -> DynamicObject {
38    let (ns, name) = split_namespaced_name(ns_name);
39    DynamicObject {
40        metadata: metav1::ObjectMeta {
41            namespace: Some(ns),
42            name: Some(name),
43            ..Default::default()
44        },
45        types: Some(gvk.into_type_meta()),
46        data: json::Value::Null,
47    }
48}
49
50pub fn build_containment_label_selector(key: &str, labels: Vec<String>) -> metav1::LabelSelector {
51    metav1::LabelSelector {
52        match_expressions: Some(vec![metav1::LabelSelectorRequirement {
53            key: key.into(),
54            operator: "In".into(),
55            values: Some(labels),
56        }]),
57        ..Default::default()
58    }
59}
60
61pub fn build_global_object_meta<K>(name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
62where
63    K: Resource<DynamicType = ()>,
64{
65    build_object_meta_helper(None, name, sim_name, owner)
66}
67
68pub fn build_object_meta<K>(namespace: &str, name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
69where
70    K: Resource<DynamicType = ()>,
71{
72    build_object_meta_helper(Some(namespace.into()), name, sim_name, owner)
73}
74
75pub fn dyn_obj_type_str(obj: &DynamicObject) -> String {
76    obj.types
77        .as_ref()
78        .map(|tm| format!("{}.{}", tm.api_version, tm.kind))
79        .unwrap_or("<unknown type>".into())
80}
81
82pub fn format_gvk_name(gvk: &GVK, ns_name: &str) -> String {
83    format!("{gvk}:{ns_name}")
84}
85
86pub fn sanitize_obj(obj: &mut DynamicObject, api_version: &str, kind: &str) {
87    // N.B. We do not sanitize owner references here, since we need them
88    // to compute owner chains in the TraceStore
89    obj.metadata.creation_timestamp = None;
90    obj.metadata.deletion_timestamp = None;
91    obj.metadata.deletion_grace_period_seconds = None;
92    obj.metadata.generation = None;
93    obj.metadata.managed_fields = None;
94    obj.metadata.resource_version = None;
95    obj.metadata.uid = None;
96
97    if let Some(a) = obj.metadata.annotations.as_mut() {
98        a.remove(LAST_APPLIED_CONFIG_LABEL_KEY);
99        a.remove(DEPL_REVISION_LABEL_KEY);
100    }
101
102    obj.types = Some(TypeMeta { api_version: api_version.into(), kind: kind.into() });
103}
104
105pub fn split_namespaced_name(name: &str) -> (String, String) {
106    match name.split_once('/') {
107        Some((namespace, name)) => (namespace.into(), name.into()),
108        None => ("".into(), name.into()),
109    }
110}
111
112impl<T: Resource> KubeResourceExt for T {
113    fn namespaced_name(&self) -> String {
114        match self.namespace() {
115            Some(ns) => format!("{}/{}", ns, self.name_any()),
116            None => self.name_any().clone(),
117        }
118    }
119
120    fn matches(&self, sel: &metav1::LabelSelector) -> anyhow::Result<bool> {
121        if let Some(exprs) = &sel.match_expressions {
122            for expr in exprs {
123                if !label_expr_match(self.labels(), expr)? {
124                    return Ok(false);
125                }
126            }
127        }
128
129        if let Some(labels) = &sel.match_labels {
130            for (k, v) in labels {
131                if self.labels().get(k) != Some(v) {
132                    return Ok(false);
133                }
134            }
135        }
136        Ok(true)
137    }
138}
139
140fn build_object_meta_helper<K>(namespace: Option<String>, name: &str, sim_name: &str, owner: &K) -> metav1::ObjectMeta
141where
142    K: Resource<DynamicType = ()>,
143{
144    let mut meta = metav1::ObjectMeta {
145        namespace,
146        name: Some(name.into()),
147        ..Default::default()
148    };
149
150    add_common_metadata(sim_name, owner, &mut meta);
151    meta
152}
153
154// The meanings of these operators is explained here:
155// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#set-based-requirement
156pub(super) const OPERATOR_IN: &str = "In";
157pub(super) const OPERATOR_NOT_IN: &str = "NotIn";
158pub(super) const OPERATOR_EXISTS: &str = "Exists";
159pub(super) const OPERATOR_DOES_NOT_EXIST: &str = "DoesNotExist";
160
161fn label_expr_match(
162    obj_labels: &BTreeMap<String, String>,
163    expr: &metav1::LabelSelectorRequirement,
164) -> anyhow::Result<bool> {
165    // LabelSelectorRequirement is considered invalid if the Operator is "In" or NotIn"
166    // and there are no values; conversely for "Exists" and "DoesNotExist".
167    match expr.operator.as_str() {
168        OPERATOR_IN => match obj_labels.get(&expr.key) {
169            Some(v) => match &expr.values {
170                Some(values) if !values.is_empty() => Ok(values.contains(v)),
171                _ => bail!(KubernetesError::malformed_label_selector(expr)),
172            },
173            None => Ok(false),
174        },
175        OPERATOR_NOT_IN => match obj_labels.get(&expr.key) {
176            Some(v) => match &expr.values {
177                Some(values) if !values.is_empty() => Ok(!values.contains(v)),
178                _ => bail!(KubernetesError::malformed_label_selector(expr)),
179            },
180            None => Ok(true),
181        },
182        OPERATOR_EXISTS => match &expr.values {
183            Some(values) if !values.is_empty() => bail!(KubernetesError::malformed_label_selector(expr)),
184            _ => Ok(obj_labels.contains_key(&expr.key)),
185        },
186        OPERATOR_DOES_NOT_EXIST => match &expr.values {
187            Some(values) if !values.is_empty() => {
188                bail!(KubernetesError::malformed_label_selector(expr));
189            },
190            _ => Ok(!obj_labels.contains_key(&expr.key)),
191        },
192        _ => bail!("malformed label selector expression: {:?}", expr),
193    }
194}