kube_core/metadata.rs
1//! Metadata structs used in traits, lists, and dynamic objects.
2use std::{borrow::Cow, marker::PhantomData};
3
4pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ListMeta, ObjectMeta};
5use serde::{Deserialize, Serialize};
6
7use crate::{DynamicObject, Resource};
8
9/// Type information that is flattened into every kubernetes object
10#[derive(Deserialize, Serialize, Clone, Default, Debug, Eq, PartialEq, Hash)]
11#[serde(rename_all = "camelCase")]
12pub struct TypeMeta {
13 /// The version of the API
14 pub api_version: String,
15
16 /// The name of the API
17 pub kind: String,
18}
19
20impl TypeMeta {
21 /// Construct a new `TypeMeta` for the object list from the given resource.
22 ///
23 /// ```
24 /// # use k8s_openapi::api::core::v1::Pod;
25 /// # use kube_core::TypeMeta;
26 ///
27 /// let type_meta = TypeMeta::list::<Pod>();
28 /// assert_eq!(type_meta.kind, "PodList");
29 /// assert_eq!(type_meta.api_version, "v1");
30 /// ```
31 pub fn list<K: Resource<DynamicType = ()>>() -> Self {
32 TypeMeta {
33 api_version: K::api_version(&()).into(),
34 kind: K::kind(&()).to_string() + "List",
35 }
36 }
37
38 /// Construct a new `TypeMeta` for the object from the given resource.
39 ///
40 /// ```
41 /// # use k8s_openapi::api::core::v1::Pod;
42 /// # use kube_core::TypeMeta;
43 ///
44 /// let type_meta = TypeMeta::resource::<Pod>();
45 /// assert_eq!(type_meta.kind, "Pod");
46 /// assert_eq!(type_meta.api_version, "v1");
47 /// ```
48 pub fn resource<K: Resource<DynamicType = ()>>() -> Self {
49 TypeMeta {
50 api_version: K::api_version(&()).into(),
51 kind: K::kind(&()).into(),
52 }
53 }
54}
55
56/// A generic representation of any object with `ObjectMeta`.
57///
58/// It allows clients to get access to a particular `ObjectMeta`
59/// schema without knowing the details of the version.
60///
61/// See the [`PartialObjectMetaExt`] trait for how to construct one safely.
62#[derive(Deserialize, Serialize, Clone, Default, Debug)]
63#[serde(rename_all = "camelCase")]
64pub struct PartialObjectMeta<K = DynamicObject> {
65 /// The type fields, not always present
66 #[serde(flatten, default)]
67 pub types: Option<TypeMeta>,
68 /// Standard object's metadata
69 #[serde(default)]
70 pub metadata: ObjectMeta,
71 /// Type information for static dispatch
72 #[serde(skip, default)]
73 pub _phantom: PhantomData<K>,
74}
75
76mod private {
77 pub trait Sealed {}
78 impl Sealed for super::ObjectMeta {}
79}
80/// Helper trait for converting `ObjectMeta` into useful `PartialObjectMeta` variants
81pub trait PartialObjectMetaExt: private::Sealed {
82 /// Convert `ObjectMeta` into a Patch-serializable `PartialObjectMeta`
83 ///
84 /// This object can be passed to `Patch::Apply` and used with `Api::patch_metadata`,
85 /// for an `Api<K>` using the underlying types `TypeMeta`:
86 ///
87 /// ```
88 /// # use k8s_openapi::api::core::v1::Pod;
89 /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
90 /// let partial = ObjectMeta {
91 /// labels: Some([("key".to_string(), "value".to_string())].into()),
92 /// ..Default::default()
93 /// }.into_request_partial::<Pod>();
94 ///
95 /// // request partials are generally closer to patches than fully valid resources:
96 /// assert_eq!(partial.name_any(), "");
97 ///
98 /// // typemeta is re-used from K:
99 /// assert_eq!(partial.types.unwrap().kind, "Pod");
100 /// ```
101 fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K>;
102
103 /// Convert `ObjectMeta` into a response object for a specific `Resource`
104 ///
105 /// This object emulates a response object and **cannot** be used in request bodies
106 /// because it contains erased `TypeMeta` (and the apiserver is doing the erasing).
107 ///
108 /// This method is **mostly useful for unit testing** behaviour.
109 ///
110 /// ```
111 /// # use k8s_openapi::api::apps::v1::Deployment;
112 /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
113 /// let partial = ObjectMeta {
114 /// name: Some("my-deploy".to_string()),
115 /// namespace: Some("default".to_string()),
116 /// ..Default::default()
117 /// }.into_response_partial::<Deployment>();
118 ///
119 /// assert_eq!(partial.name_any(), "my-deploy");
120 /// assert_eq!(partial.types.unwrap().kind, "PartialObjectMetadata"); // NB: Pod erased
121 /// ```
122 fn into_response_partial<K>(self) -> PartialObjectMeta<K>;
123}
124
125impl PartialObjectMetaExt for ObjectMeta {
126 fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K> {
127 PartialObjectMeta {
128 types: Some(TypeMeta {
129 api_version: K::api_version(&()).into(),
130 kind: K::kind(&()).into(),
131 }),
132 metadata: self,
133 _phantom: PhantomData,
134 }
135 }
136
137 fn into_response_partial<K>(self) -> PartialObjectMeta<K> {
138 PartialObjectMeta {
139 types: Some(TypeMeta {
140 api_version: "meta.k8s.io/v1".to_string(),
141 kind: "PartialObjectMetadata".to_string(),
142 }),
143 metadata: self,
144 _phantom: PhantomData,
145 }
146 }
147}
148
149impl<K: Resource> Resource for PartialObjectMeta<K> {
150 type DynamicType = K::DynamicType;
151 type Scope = K::Scope;
152
153 fn kind(dt: &Self::DynamicType) -> Cow<'_, str> {
154 K::kind(dt)
155 }
156
157 fn group(dt: &Self::DynamicType) -> Cow<'_, str> {
158 K::group(dt)
159 }
160
161 fn version(dt: &Self::DynamicType) -> Cow<'_, str> {
162 K::version(dt)
163 }
164
165 fn plural(dt: &Self::DynamicType) -> Cow<'_, str> {
166 K::plural(dt)
167 }
168
169 fn meta(&self) -> &ObjectMeta {
170 &self.metadata
171 }
172
173 fn meta_mut(&mut self) -> &mut ObjectMeta {
174 &mut self.metadata
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use super::{ObjectMeta, PartialObjectMeta, PartialObjectMetaExt};
181 use crate::Resource;
182 use k8s_openapi::api::core::v1::Pod;
183
184 #[test]
185 fn can_convert_and_derive_partial_metadata() {
186 // can use generic type for static dispatch;
187 assert_eq!(PartialObjectMeta::<Pod>::kind(&()), "Pod");
188 assert_eq!(PartialObjectMeta::<Pod>::api_version(&()), "v1");
189
190 // can convert from objectmeta to partials for different use cases:
191 let meta = ObjectMeta {
192 name: Some("mypod".into()),
193 ..Default::default()
194 };
195 let request_pom = meta.clone().into_request_partial::<Pod>();
196 let response_pom = meta.into_response_partial::<Pod>();
197
198 // they both basically just inline the metadata;
199 assert_eq!(request_pom.metadata.name, Some("mypod".to_string()));
200 assert_eq!(response_pom.metadata.name, Some("mypod".to_string()));
201
202 // the request_pom will use the TypeMeta from K to support POST/PUT requests
203 assert_eq!(request_pom.types.as_ref().unwrap().api_version, "v1");
204 assert_eq!(request_pom.types.as_ref().unwrap().kind, "Pod");
205
206 // but the response_pom will use the type-erased kinds from the apiserver
207 assert_eq!(response_pom.types.as_ref().unwrap().api_version, "meta.k8s.io/v1");
208 assert_eq!(response_pom.types.as_ref().unwrap().kind, "PartialObjectMetadata");
209 }
210}