kube_core/
gvk.rs

1//! Type information structs for dynamic resources.
2use std::str::FromStr;
3
4use crate::TypeMeta;
5use k8s_openapi::{api::core::v1::ObjectReference, apimachinery::pkg::apis::meta::v1::OwnerReference};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10#[error("failed to parse group version: {0}")]
11/// Failed to parse group version
12pub struct ParseGroupVersionError(pub String);
13
14/// Core information about an API Resource.
15#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
16pub struct GroupVersionKind {
17    /// API group
18    pub group: String,
19    /// Version
20    pub version: String,
21    /// Kind
22    pub kind: String,
23}
24
25impl GroupVersionKind {
26    /// Construct from explicit group, version, and kind
27    pub fn gvk(group_: &str, version_: &str, kind_: &str) -> Self {
28        let version = version_.to_string();
29        let group = group_.to_string();
30        let kind = kind_.to_string();
31
32        Self { group, version, kind }
33    }
34}
35
36impl TryFrom<&TypeMeta> for GroupVersionKind {
37    type Error = ParseGroupVersionError;
38
39    fn try_from(tm: &TypeMeta) -> Result<Self, Self::Error> {
40        Ok(GroupVersion::from_str(&tm.api_version)?.with_kind(&tm.kind))
41    }
42}
43impl TryFrom<TypeMeta> for GroupVersionKind {
44    type Error = ParseGroupVersionError;
45
46    fn try_from(tm: TypeMeta) -> Result<Self, Self::Error> {
47        Ok(GroupVersion::from_str(&tm.api_version)?.with_kind(&tm.kind))
48    }
49}
50
51impl From<OwnerReference> for GroupVersionKind {
52    fn from(value: OwnerReference) -> Self {
53        let (group, version) = match value.api_version.split_once("/") {
54            Some((group, version)) => (group, version),
55            None => ("", value.api_version.as_str()),
56        };
57        Self {
58            group: group.into(),
59            version: version.into(),
60            kind: value.kind,
61        }
62    }
63}
64
65impl From<ObjectReference> for GroupVersionKind {
66    fn from(value: ObjectReference) -> Self {
67        let api_version = value.api_version.unwrap_or_default();
68        let (group, version) = match api_version.split_once("/") {
69            Some((group, version)) => (group, version),
70            None => ("", api_version.as_str()),
71        };
72        Self {
73            group: group.into(),
74            version: version.into(),
75            kind: value.kind.unwrap_or_default(),
76        }
77    }
78}
79
80/// Core information about a family of API Resources
81#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
82pub struct GroupVersion {
83    /// API group
84    pub group: String,
85    /// Version
86    pub version: String,
87}
88
89impl GroupVersion {
90    /// Construct from explicit group and version
91    pub fn gv(group_: &str, version_: &str) -> Self {
92        let version = version_.to_string();
93        let group = group_.to_string();
94        Self { group, version }
95    }
96
97    /// Upgrade a GroupVersion to a GroupVersionKind
98    pub fn with_kind(self, kind: &str) -> GroupVersionKind {
99        GroupVersionKind {
100            group: self.group,
101            version: self.version,
102            kind: kind.into(),
103        }
104    }
105}
106
107impl FromStr for GroupVersion {
108    type Err = ParseGroupVersionError;
109
110    fn from_str(gv: &str) -> Result<Self, Self::Err> {
111        let gvsplit = gv.splitn(2, '/').collect::<Vec<_>>();
112        let (group, version) = match *gvsplit.as_slice() {
113            [g, v] => (g.to_string(), v.to_string()), // standard case
114            [v] => ("".to_string(), v.to_string()),   // core v1 case
115            _ => return Err(ParseGroupVersionError(gv.into())),
116        };
117        Ok(Self { group, version })
118    }
119}
120
121impl GroupVersion {
122    /// Generate the apiVersion string used in a kind's yaml
123    pub fn api_version(&self) -> String {
124        if self.group.is_empty() {
125            self.version.clone()
126        } else {
127            format!("{}/{}", self.group, self.version)
128        }
129    }
130}
131impl GroupVersionKind {
132    /// Generate the apiVersion string used in a kind's yaml
133    pub fn api_version(&self) -> String {
134        if self.group.is_empty() {
135            self.version.clone()
136        } else {
137            format!("{}/{}", self.group, self.version)
138        }
139    }
140}
141
142/// Represents a type-erased object resource.
143#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
144pub struct GroupVersionResource {
145    /// API group
146    pub group: String,
147    /// Version
148    pub version: String,
149    /// Resource
150    pub resource: String,
151    /// Concatenation of group and version
152    #[serde(default)]
153    api_version: String,
154}
155
156impl GroupVersionResource {
157    /// Set the api group, version, and the plural resource name.
158    pub fn gvr(group_: &str, version_: &str, resource_: &str) -> Self {
159        let version = version_.to_string();
160        let group = group_.to_string();
161        let resource = resource_.to_string();
162        let api_version = if group.is_empty() {
163            version.to_string()
164        } else {
165            format!("{group}/{version}")
166        };
167
168        Self {
169            group,
170            version,
171            resource,
172            api_version,
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    #[test]
180    fn gvk_yaml() {
181        use crate::{GroupVersionKind, TypeMeta};
182        let input = r#"---
183apiVersion: kube.rs/v1
184kind: Example
185metadata:
186  name: doc1
187"#;
188        let tm: TypeMeta = serde_yaml::from_str(input).unwrap();
189        let gvk = GroupVersionKind::try_from(&tm).unwrap(); // takes ref
190        let gvk2: GroupVersionKind = tm.try_into().unwrap(); // takes value
191        assert_eq!(gvk.kind, "Example");
192        assert_eq!(gvk.group, "kube.rs");
193        assert_eq!(gvk.version, "v1");
194        assert_eq!(gvk.kind, gvk2.kind);
195    }
196}