Skip to main content

sk_core/k8s/
gvk.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::ops::Deref;
4
5use kube::api::GroupVersionKind;
6use serde::{
7    Deserialize,
8    Deserializer,
9    Serialize,
10    Serializer,
11    de,
12};
13
14use crate::errors::*;
15use crate::prelude::*;
16
17// GVK is a "newtype" wrapper around the metav1::GroupVersionKind object that lets me provide
18// custom serialization methods.  We also add some handy helper/conversion functions.
19//
20// Specifically for serialization/deserialization, we convert to the format "group/version.kind".
21// (unless the group is "core", and then we serialize to "version.kind", but can deserialize from
22// either "version.kind" or "/version.kind" for backwards compatibility)
23#[derive(Clone, Debug, Hash, Eq, PartialEq)]
24pub struct GVK(GroupVersionKind);
25
26impl GVK {
27    pub fn new(group: &str, version: &str, kind: &str) -> GVK {
28        GVK(GroupVersionKind::gvk(group, version, kind))
29    }
30
31    pub fn from_dynamic_obj(obj: &DynamicObject) -> anyhow::Result<GVK> {
32        match &obj.types {
33            Some(t) => Ok(GVK(t.try_into()?)),
34            None => bail!("no type data present"),
35        }
36    }
37
38    pub fn from_owner_ref(rf: &metav1::OwnerReference) -> anyhow::Result<GVK> {
39        let parts: Vec<_> = rf.api_version.split('/').collect();
40
41        if parts.len() == 1 {
42            Ok(GVK(GroupVersionKind::gvk("", parts[0], &rf.kind)))
43        } else if parts.len() == 2 {
44            Ok(GVK(GroupVersionKind::gvk(parts[0], parts[1], &rf.kind)))
45        } else {
46            bail!("invalid format for api_version: {}", rf.api_version);
47        }
48    }
49
50    pub fn into_type_meta(&self) -> TypeMeta {
51        TypeMeta {
52            api_version: self.0.api_version(),
53            kind: self.0.kind.clone(),
54        }
55    }
56}
57
58// Impl Deref lets a GVK act like a GroupVersionKind anywhere one of those is expected
59impl Deref for GVK {
60    type Target = GroupVersionKind;
61
62    fn deref(&self) -> &Self::Target {
63        &self.0
64    }
65}
66
67impl fmt::Display for GVK {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        let mut group = Cow::from(&self.0.group);
70        if !group.is_empty() {
71            group.to_mut().push('/');
72        }
73
74        write!(f, "{group}{}.{}", self.0.version, self.0.kind)
75    }
76}
77
78impl Serialize for GVK {
79    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
80    where
81        S: Serializer,
82    {
83        // reuse the display impl for serializing
84        serializer.serialize_str(&format!("{self}"))
85    }
86}
87
88struct GVKVisitor;
89
90impl de::Visitor<'_> for GVKVisitor {
91    type Value = GVK;
92
93    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94        formatter.write_str("a GroupVersionKind in the format group/version.kind")
95    }
96
97    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
98    where
99        E: de::Error,
100    {
101        let p1: Vec<_> = value.split('/').collect();
102        let (group, rest) = match p1.len() {
103            2 => (p1[0], p1[1]),
104            1 => ("", p1[0]),
105            _ => return Err(E::custom(format!("invalid format for gvk: {value}"))),
106        };
107        let p2: Vec<_> = rest.split('.').collect();
108        let (version, kind) = match p2.len() {
109            2 => (p2[0], p2[1]),
110            _ => return Err(E::custom(format!("invalid format for gvk: {value}"))),
111        };
112
113        Ok(GVK(GroupVersionKind::gvk(group, version, kind)))
114    }
115}
116
117impl<'de> Deserialize<'de> for GVK {
118    fn deserialize<D>(deserializer: D) -> Result<GVK, D::Error>
119    where
120        D: Deserializer<'de>,
121    {
122        deserializer.deserialize_str(GVKVisitor)
123    }
124}
125
126#[cfg(test)]
127mod test {
128    use assertables::*;
129    use serde::de::IntoDeserializer;
130    use serde::de::value::{
131        Error as SerdeError,
132        StrDeserializer,
133    };
134    use sk_testutils::*;
135
136    use super::*;
137
138    #[rstest]
139    fn test_serialize() {
140        // I had to think about this for a minute, but strings in JSON have to include quotes,
141        // which is why they're escaped out here.
142        assert_eq!(serde_json::to_string(&GVK::new("foo", "v1", "bar")).unwrap(), "\"foo/v1.bar\"");
143        assert_eq!(serde_json::to_string(&GVK::new("", "v1", "bar")).unwrap(), "\"v1.bar\"");
144    }
145
146    #[rstest]
147    fn test_deserialize() {
148        let d1: StrDeserializer<SerdeError> = "foo/v1.bar".into_deserializer();
149        assert_eq!(GVK::deserialize(d1).unwrap(), GVK::new("foo", "v1", "bar"));
150
151        let d2: StrDeserializer<SerdeError> = "/v1.bar".into_deserializer();
152        assert_eq!(GVK::deserialize(d2).unwrap(), GVK::new("", "v1", "bar"));
153
154        let d3: StrDeserializer<SerdeError> = "v1.bar".into_deserializer();
155        assert_eq!(GVK::deserialize(d3).unwrap(), GVK::new("", "v1", "bar"));
156
157        let d4: StrDeserializer<SerdeError> = "asdf".into_deserializer();
158        assert_err!(GVK::deserialize(d4));
159
160        let d5: StrDeserializer<SerdeError> = "foo/asdf/asdf".into_deserializer();
161        assert_err!(GVK::deserialize(d5));
162    }
163}